fix: handle fullscreen windows on special workspaces (#12851)
* fix: handle fullscreen windows on special workspaces inFullscreenMode() only checked m_activeWorkspace, missing fullscreen windows on special workspaces. This caused crashes and incorrect behavior when fullscreen windows were on special workspaces. Changes: - inFullscreenMode() now checks special workspace first since it renders on top of regular workspaces - Added getFullscreenWindow() helper to safely get fullscreen window from either active or special workspace - Updated callers (shouldSkipScheduleFrameOnMouseEvent, Renderer, getFSImageDescription) to use the new helper - Reset m_aboveFullscreen for layer surfaces when opening, closing, or stealing special workspaces between monitors * test: add special workspace fullscreen detection tests Add tests for the new special workspace fullscreen handling introduced in the previous commit. The tests cover: 1. Fullscreen detection on special workspace - verifies that a window made fullscreen on a special workspace is correctly detected 2. Special workspace fullscreen precedence - verifies that when both regular and special workspaces have fullscreen windows, the special workspace window can be focused when the special workspace is opened 3. Toggle special workspace behavior - verifies that toggling the special workspace off properly hides it and returns focus to the regular workspace's fullscreen window These tests exercise the key code paths modified in the fix: - inFullscreenMode() checking special workspace first - getFullscreenWindow() helper returning correct window - Layer surface m_aboveFullscreen reset on special workspace toggle
This commit is contained in:
parent
eb623bd91d
commit
5b1b79c29c
4 changed files with 126 additions and 7 deletions
|
|
@ -20,6 +20,91 @@ using namespace Hyprutils::Utils;
|
||||||
#define UP CUniquePointer
|
#define UP CUniquePointer
|
||||||
#define SP CSharedPointer
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
static bool testSpecialWorkspaceFullscreen() {
|
||||||
|
NLog::log("{}Testing special workspace fullscreen detection", Colors::YELLOW);
|
||||||
|
|
||||||
|
CScopeGuard guard = {[&]() {
|
||||||
|
NLog::log("{}Cleaning up special workspace fullscreen test", Colors::YELLOW);
|
||||||
|
// Close special workspace if open
|
||||||
|
auto monitors = getFromSocket("/monitors");
|
||||||
|
if (monitors.contains("(special:test_fs_special)") && !monitors.contains("special workspace: 0 ()"))
|
||||||
|
getFromSocket("/dispatch togglespecialworkspace test_fs_special");
|
||||||
|
Tests::killAllWindows();
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
getFromSocket("/dispatch workspace 1");
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
NLog::log("{}Test 1: Fullscreen detection on special workspace", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch workspace special:test_fs_special"));
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("kitty_special"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "class: kitty_special");
|
||||||
|
EXPECT_CONTAINS(str, "(special:test_fs_special)");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/monitors");
|
||||||
|
EXPECT_CONTAINS(str, "(special:test_fs_special)");
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Test 2: Special workspace fullscreen precedence", Colors::YELLOW);
|
||||||
|
|
||||||
|
// Close special workspace before spawning on regular workspace
|
||||||
|
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
|
||||||
|
getFromSocket("/dispatch workspace 1");
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("kitty_regular"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "class: kitty_regular");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_special"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "class: kitty_special");
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Test 3: Toggle special workspace hides it", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_regular"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "class: kitty_regular");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/monitors");
|
||||||
|
EXPECT_CONTAINS(str, "special workspace: 0 ()");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool testAsymmetricGaps() {
|
static bool testAsymmetricGaps() {
|
||||||
NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW);
|
NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW);
|
||||||
{
|
{
|
||||||
|
|
@ -449,6 +534,8 @@ static bool test() {
|
||||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
Tests::killAllWindows();
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
testSpecialWorkspaceFullscreen();
|
||||||
|
|
||||||
testAsymmetricGaps();
|
testAsymmetricGaps();
|
||||||
|
|
||||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
|
|
||||||
|
|
@ -1026,8 +1026,8 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() {
|
||||||
static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate");
|
static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate");
|
||||||
|
|
||||||
// skip scheduling extra frames for fullsreen apps with vrr
|
// skip scheduling extra frames for fullsreen apps with vrr
|
||||||
const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) &&
|
const auto FS_WINDOW = getFullscreenWindow();
|
||||||
m_output->state->state().adaptiveSync;
|
const bool shouldSkip = FS_WINDOW && (*PNOBREAK == 1 || (*PNOBREAK == 2 && FS_WINDOW->getContentType() == CONTENT_TYPE_GAME)) && m_output->state->state().adaptiveSync;
|
||||||
|
|
||||||
// keep requested minimum refresh rate
|
// keep requested minimum refresh rate
|
||||||
if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) {
|
if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) {
|
||||||
|
|
@ -1357,6 +1357,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
||||||
g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);
|
g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);
|
||||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name});
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name});
|
||||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + m_name});
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + m_name});
|
||||||
|
|
||||||
|
// Reset layer surface state when closing special workspace
|
||||||
|
for (auto const& ls : g_pCompositor->m_layers) {
|
||||||
|
if (ls->m_monitor == m_self)
|
||||||
|
ls->m_aboveFullscreen = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_activeSpecialWorkspace.reset();
|
m_activeSpecialWorkspace.reset();
|
||||||
|
|
||||||
|
|
@ -1396,6 +1402,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
||||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
|
||||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name});
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name});
|
||||||
|
|
||||||
|
// Reset layer surfaces on the old monitor when special workspace is stolen
|
||||||
|
for (auto const& ls : g_pCompositor->m_layers) {
|
||||||
|
if (ls->m_monitor == PMWSOWNER)
|
||||||
|
ls->m_aboveFullscreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace;
|
const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace;
|
||||||
g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE,
|
g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE,
|
||||||
PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
|
PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
|
||||||
|
|
@ -1409,6 +1421,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
||||||
m_activeSpecialWorkspace = pWorkspace;
|
m_activeSpecialWorkspace = pWorkspace;
|
||||||
m_activeSpecialWorkspace->m_visible = true;
|
m_activeSpecialWorkspace->m_visible = true;
|
||||||
|
|
||||||
|
// Reset layer surface state when opening special workspace
|
||||||
|
for (auto const& ls : g_pCompositor->m_layers) {
|
||||||
|
if (ls->m_monitor == m_self)
|
||||||
|
ls->m_aboveFullscreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (POLDSPECIAL)
|
if (POLDSPECIAL)
|
||||||
POLDSPECIAL->m_events.activeChanged.emit();
|
POLDSPECIAL->m_events.activeChanged.emit();
|
||||||
|
|
||||||
|
|
@ -2017,16 +2035,28 @@ bool CMonitor::inHDR() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CMonitor::inFullscreenMode() {
|
bool CMonitor::inFullscreenMode() {
|
||||||
|
// Check special workspace first since it renders on top of regular workspaces
|
||||||
|
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
|
||||||
|
return true;
|
||||||
return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN;
|
return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PHLWINDOW CMonitor::getFullscreenWindow() {
|
||||||
|
// Check special workspace first since it renders on top of regular workspaces
|
||||||
|
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
|
||||||
|
return m_activeSpecialWorkspace->getFullscreenWindow();
|
||||||
|
if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
|
||||||
|
return m_activeWorkspace->getFullscreenWindow();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<NColorManagement::PImageDescription> CMonitor::getFSImageDescription() {
|
std::optional<NColorManagement::PImageDescription> CMonitor::getFSImageDescription() {
|
||||||
if (!inFullscreenMode())
|
if (!inFullscreenMode())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow();
|
const auto FS_WINDOW = getFullscreenWindow();
|
||||||
if (!FS_WINDOW)
|
if (!FS_WINDOW)
|
||||||
return {}; // should be unreachable
|
return {};
|
||||||
|
|
||||||
const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();
|
const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();
|
||||||
const auto SURF = ROOT_SURF->findWithCM();
|
const auto SURF = ROOT_SURF->findWithCM();
|
||||||
|
|
|
||||||
|
|
@ -327,8 +327,10 @@ class CMonitor {
|
||||||
|
|
||||||
bool inHDR();
|
bool inHDR();
|
||||||
|
|
||||||
/// Has an active workspace with a real fullscreen window
|
/// Has an active workspace with a real fullscreen window (includes special workspace)
|
||||||
bool inFullscreenMode();
|
bool inFullscreenMode();
|
||||||
|
/// Get fullscreen window from active or special workspace
|
||||||
|
PHLWINDOW getFullscreenWindow();
|
||||||
std::optional<NColorManagement::PImageDescription> getFSImageDescription();
|
std::optional<NColorManagement::PImageDescription> getFSImageDescription();
|
||||||
|
|
||||||
bool needsCM();
|
bool needsCM();
|
||||||
|
|
|
||||||
|
|
@ -1567,7 +1567,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
|
||||||
const bool configuredHDR = (pMonitor->m_cmType == NCMType::CM_HDR_EDID || pMonitor->m_cmType == NCMType::CM_HDR);
|
const bool configuredHDR = (pMonitor->m_cmType == NCMType::CM_HDR_EDID || pMonitor->m_cmType == NCMType::CM_HDR);
|
||||||
bool wantHDR = configuredHDR;
|
bool wantHDR = configuredHDR;
|
||||||
|
|
||||||
const auto FS_WINDOW = pMonitor->inFullscreenMode() ? pMonitor->m_activeWorkspace->getFullscreenWindow() : nullptr;
|
const auto FS_WINDOW = pMonitor->getFullscreenWindow();
|
||||||
|
|
||||||
if (pMonitor->supportsHDR()) {
|
if (pMonitor->supportsHDR()) {
|
||||||
// HDR metadata determined by
|
// HDR metadata determined by
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue