monitor: keep workspace monitor bindings on full reconnect (#13384)

When all monitors disconnect, non-active workspaces could migrate to the wrong output after reconnect. Preserve workspace ownership and re-apply assigned monitor bindings on connect.
This commit is contained in:
Zynix 2026-03-01 00:49:47 +03:00 committed by GitHub
parent 82729db330
commit c2bed4103c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 11 deletions

View file

@ -3067,6 +3067,27 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vector<SWorkspace
}
}
void CCompositor::ensureWorkspacesOnAssignedMonitors() {
for (auto const& ws : getWorkspacesCopy()) {
if (!valid(ws) || ws->m_isSpecialWorkspace)
continue;
const auto RULE = g_pConfigManager->getWorkspaceRuleFor(ws);
if (RULE.monitor.empty())
continue;
const auto PMONITOR = getMonitorFromString(RULE.monitor);
if (!PMONITOR)
continue;
if (ws->m_monitor == PMONITOR)
continue;
Log::logger->log(Log::DEBUG, "ensureWorkspacesOnAssignedMonitors: moving workspace {} to {}", ws->m_name, PMONITOR->m_name);
moveWorkspaceToMonitor(ws, PMONITOR, true);
}
}
std::optional<unsigned int> CCompositor::getVTNr() {
if (!m_aqBackend->hasSession())
return std::nullopt;

View file

@ -161,6 +161,7 @@ class CCompositor {
void updateSuspendedStates();
void onNewMonitor(SP<Aquamarine::IOutput> output);
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
void ensureWorkspacesOnAssignedMonitors();
std::optional<unsigned int> getVTNr();
bool isVRRActiveOnAnyMonitor() const;

View file

@ -82,7 +82,10 @@ void CMonitor::onConnect(bool noRule) {
m_zoomAnimProgress->setValueAndWarp(0.F);
m_zoomAnimFrameCounter = 0;
g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
g_pEventLoopManager->doLater([] {
g_pConfigManager->ensurePersistentWorkspacesPresent();
g_pCompositor->ensureWorkspacesOnAssignedMonitors();
});
m_listeners.frame = m_output->events.frame.listen([this] {
if (m_frameScheduler)
@ -290,10 +293,16 @@ void CMonitor::onConnect(bool noRule) {
if (!valid(ws))
continue;
if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) {
const auto CURRENTMON = ws->m_monitor.lock();
const bool ORPHANED = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; });
const bool RETURNING = ws->m_lastMonitor == m_name;
const bool RECOVERY = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces
if (RETURNING || RECOVERY) {
g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock());
g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
ws->m_lastMonitor = "";
if (RETURNING)
ws->m_lastMonitor = "";
}
}
@ -429,19 +438,24 @@ void CMonitor::onDisconnect(bool destroy) {
m_enabled = false;
m_renderingInitPassed = false;
std::vector<PHLWORKSPACE> wspToMove;
for (auto const& w : g_pCompositor->getWorkspaces()) {
if (w->m_monitor == m_self || !w->m_monitor)
wspToMove.emplace_back(w.lock());
}
// Preserve ownership across cascaded monitor disconnects.
// The first disconnected monitor "owns" where a workspace should return.
for (auto const& w : wspToMove) {
if (w && w->m_lastMonitor.empty())
w->m_lastMonitor = m_name;
}
if (BACKUPMON) {
// snap cursor
g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true);
// move workspaces
std::vector<PHLWORKSPACE> wspToMove;
for (auto const& w : g_pCompositor->getWorkspaces()) {
if (w->m_monitor == m_self || !w->m_monitor)
wspToMove.emplace_back(w.lock());
}
for (auto const& w : wspToMove) {
w->m_lastMonitor = m_name;
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
}