From 46d66f4bcc95d9fb7fede18ad5c2c043796b9071 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Sun, 24 Sep 2023 18:04:38 +0100 Subject: [PATCH] internal: Further unsafe state improvements (#3404) Instead of allowing Hyprland to sit in a state where there are no monitors, which various parts of the code don't like, we create a fake headless output on all monitor disconnect, and then remove it when a monitor appears --- src/Compositor.cpp | 40 ++++++++++++++++++++++++++++++++++++ src/Compositor.hpp | 5 ++++- src/config/ConfigManager.cpp | 3 --- src/events/Monitors.cpp | 24 ++++++++++++++++------ src/helpers/Monitor.cpp | 2 +- 5 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 92545faf..4ddac879 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -2604,3 +2604,43 @@ void CCompositor::arrangeMonitors() { m->xwaylandScale = 1.f; } } + +void CCompositor::enterUnsafeState() { + if (m_bUnsafeState) + return; + + Debug::log(LOG, "Entering unsafe state"); + + m_bUnsafeState = true; + + // create a backup monitor + wlr_backend* headless = nullptr; + wlr_multi_for_each_backend( + m_sWLRBackend, + [](wlr_backend* b, void* data) { + if (wlr_backend_is_headless(b)) + *((wlr_backend**)data) = b; + }, + &headless); + + if (!headless) { + Debug::log(WARN, "Entering an unsafe state without a headless backend"); + return; + } + + m_pUnsafeOutput = wlr_headless_add_output(headless, 1920, 1080); +} + +void CCompositor::leaveUnsafeState() { + if (!m_bUnsafeState) + return; + + Debug::log(LOG, "Leaving unsafe state"); + + m_bUnsafeState = false; + + if (m_pUnsafeOutput) + wlr_output_destroy(m_pUnsafeOutput); + + m_pUnsafeOutput = nullptr; +} diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 27a27e3b..06d15c3c 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -119,7 +119,8 @@ class CCompositor { bool m_bReadyToProcess = false; bool m_bSessionActive = true; bool m_bDPMSStateON = true; - bool m_bUnsafeState = false; // unsafe state is when there is no monitors. + bool m_bUnsafeState = false; // unsafe state is when there is no monitors. + wlr_output* m_pUnsafeOutput = nullptr; // fallback output for the unsafe state bool m_bIsShuttingDown = false; // ------------------------------------------------- // @@ -202,6 +203,8 @@ class CCompositor { void notifyIdleActivity(); void setIdleActivityInhibit(bool inhibit); void arrangeMonitors(); + void enterUnsafeState(); + void leaveUnsafeState(); std::string explicitConfigPath; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index ceff4414..3d231dbd 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -2020,9 +2020,6 @@ void CConfigManager::performMonitorReload() { if (overAgain) performMonitorReload(); - if (!g_pCompositor->m_vMonitors.empty()) // reset unsafe state if we have monitors - g_pCompositor->m_bUnsafeState = false; - m_bWantsMonitorReload = false; EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index 88ba612d..947f9e7f 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -87,13 +87,21 @@ void Events::listener_newOutput(wl_listener* listener, void* data) { if ((!g_pHyprRenderer->m_pMostHzMonitor || PNEWMONITOR->refreshRate > g_pHyprRenderer->m_pMostHzMonitor->refreshRate) && PNEWMONITOR->m_bEnabled) g_pHyprRenderer->m_pMostHzMonitor = PNEWMONITOR; - // ready to process cuz we have a monitor - if (PNEWMONITOR->m_bEnabled) { + // wlroots will instantly call this handler before we get a return to the wlr_output* in CCompositor::enterUnsafeState + const bool PROBABLYFALLBACK = (g_pCompositor->m_bUnsafeState && !g_pCompositor->m_pUnsafeOutput) || OUTPUT == g_pCompositor->m_pUnsafeOutput; + // ready to process if we have a real monitor + if (PNEWMONITOR->m_bEnabled && !PROBABLYFALLBACK) { + // leave unsafe state if (g_pCompositor->m_bUnsafeState) { // recover workspaces + std::vector wsp; for (auto& ws : g_pCompositor->m_vWorkspaces) { - g_pCompositor->moveWorkspaceToMonitor(ws.get(), PNEWMONITOR); + wsp.push_back(ws.get()); + } + for (auto& ws : wsp) { + // because this can realloc the vec + g_pCompositor->moveWorkspaceToMonitor(ws, PNEWMONITOR); } g_pHyprRenderer->m_pMostHzMonitor = PNEWMONITOR; @@ -104,7 +112,6 @@ void Events::listener_newOutput(wl_listener* listener, void* data) { } g_pCompositor->m_bReadyToProcess = true; - g_pCompositor->m_bUnsafeState = false; } g_pConfigManager->m_bWantsMonitorReload = true; @@ -131,8 +138,10 @@ void Events::listener_monitorFrame(void* owner, void* data) { if ((g_pCompositor->m_sWLRSession && !g_pCompositor->m_sWLRSession->active) || !g_pCompositor->m_bSessionActive || g_pCompositor->m_bUnsafeState) { Debug::log(WARN, "Attempted to render frame on inactive session!"); - if (g_pCompositor->m_bUnsafeState) - g_pConfigManager->performMonitorReload(); + if (g_pCompositor->m_bUnsafeState && PMONITOR->output != g_pCompositor->m_pUnsafeOutput) { + // restore from unsafe state + g_pCompositor->leaveUnsafeState(); + } return; // cannot draw on session inactive (different tty) } @@ -196,6 +205,9 @@ void Events::listener_monitorDestroy(void* owner, void* data) { pMonitor->output = nullptr; pMonitor->m_bRenderingInitPassed = false; + if (g_pCompositor->m_pUnsafeOutput == OUTPUT) + g_pCompositor->m_pUnsafeOutput = nullptr; + Debug::log(LOG, "Removing monitor {} from realMonitors", pMonitor->szName); std::erase_if(g_pCompositor->m_vRealMonitors, [&](std::shared_ptr& el) { return el.get() == pMonitor; }); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 97a8fe20..dd9580d1 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -243,7 +243,7 @@ void CMonitor::onDisconnect() { if (!BACKUPMON) { Debug::log(WARN, "Unplugged last monitor, entering an unsafe state. Good luck my friend."); - g_pCompositor->m_bUnsafeState = true; + g_pCompositor->enterUnsafeState(); } if (BACKUPMON) {