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:
John Mylchreest 2026-01-08 21:27:00 +00:00 committed by GitHub
parent eb623bd91d
commit 5b1b79c29c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 7 deletions

View file

@ -1026,8 +1026,8 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() {
static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate");
// skip scheduling extra frames for fullsreen apps with vrr
const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) &&
m_output->state->state().adaptiveSync;
const auto FS_WINDOW = getFullscreenWindow();
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
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_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + 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();
@ -1396,6 +1402,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + 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;
g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE,
PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
@ -1409,6 +1421,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
m_activeSpecialWorkspace = pWorkspace;
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)
POLDSPECIAL->m_events.activeChanged.emit();
@ -2017,16 +2035,28 @@ bool CMonitor::inHDR() {
}
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;
}
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() {
if (!inFullscreenMode())
return {};
const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow();
const auto FS_WINDOW = getFullscreenWindow();
if (!FS_WINDOW)
return {}; // should be unreachable
return {};
const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();
const auto SURF = ROOT_SURF->findWithCM();

View file

@ -327,8 +327,10 @@ class CMonitor {
bool inHDR();
/// Has an active workspace with a real fullscreen window
bool inFullscreenMode();
/// Has an active workspace with a real fullscreen window (includes special workspace)
bool inFullscreenMode();
/// Get fullscreen window from active or special workspace
PHLWINDOW getFullscreenWindow();
std::optional<NColorManagement::PImageDescription> getFSImageDescription();
bool needsCM();

View file

@ -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);
bool wantHDR = configuredHDR;
const auto FS_WINDOW = pMonitor->inFullscreenMode() ? pMonitor->m_activeWorkspace->getFullscreenWindow() : nullptr;
const auto FS_WINDOW = pMonitor->getFullscreenWindow();
if (pMonitor->supportsHDR()) {
// HDR metadata determined by