1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-07 22:18:30 -04:00

qs: use asynchronous loaders to load shell core

This commit is contained in:
bbedward
2026-07-14 22:22:14 -04:00
parent 89814a2c65
commit 3c5245914f
7 changed files with 436 additions and 357 deletions
+21 -276
View File
@@ -20,9 +20,7 @@ import qs.Widgets
import qs.Modules.Notifications.Popup import qs.Modules.Notifications.Popup
import qs.Modules.OSD import qs.Modules.OSD
import qs.Modules.ProcessList import qs.Modules.ProcessList
import qs.Modules.DankBar
import qs.Modules.DankBar.Popouts import qs.Modules.DankBar.Popouts
import qs.Modules.Frame
import qs.Modules.WorkspaceOverlays import qs.Modules.WorkspaceOverlays
import qs.Modules.Settings.DisplayConfig import qs.Modules.Settings.DisplayConfig
import qs.Services import qs.Services
@@ -32,7 +30,9 @@ Item {
readonly property var log: Log.scoped("DMSShell") readonly property var log: Log.scoped("DMSShell")
readonly property var _sessionsServiceRef: SessionsService readonly property var _sessionsServiceRef: SessionsService
property bool osdSurfacesLoaded: true property var core: null
property bool osdSurfacesLoaded: false
property int pendingOsdResumeReloads: 0 property int pendingOsdResumeReloads: 0
function recreateOsdSurfaces() { function recreateOsdSurfaces() {
@@ -41,16 +41,6 @@ Item {
osdSurfaceReloadTimer.restart(); osdSurfaceReloadTimer.restart();
} }
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
DesktopWidgetLayer {} DesktopWidgetLayer {}
Lock { Lock {
@@ -149,132 +139,18 @@ Item {
} }
} }
property bool barSurfacesLoaded: true Connections {
property int pendingFrameTransitionRevision: 0 target: root.core
function recreateBarSurfaces() { function on_BarLayoutStateJsonChanged() {
log.info("Recreating bar surfaces, screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","));
if (barSurfacesLoaded)
barSurfacesLoaded = false;
barSurfaceReloadAction.schedule();
}
// Holds the bar rebuild until the compositor applies the layout, so the swap lands in one pass
function runPendingFrameTransition() {
if (pendingFrameTransitionRevision <= 0 || !CompositorService.frameCompositorLayoutReady)
return;
recreateBarSurfaces();
}
DeferredAction {
id: barSurfaceReloadAction
onTriggered: {
// Ack first so the latch flips and new bars build directly in the post-transition state
if (root.pendingFrameTransitionRevision > 0 && CompositorService.frameCompositorLayoutReady) {
FrameTransitionState.acknowledge(root.pendingFrameTransitionRevision);
root.pendingFrameTransitionRevision = 0;
}
root.barSurfacesLoaded = true;
}
}
property string _barLayoutStateJson: {
if (!barSurfacesLoaded)
return "[]";
const configs = SettingsData.barConfigs;
const mapped = configs.map(c => ({
id: c.id,
position: c.position,
autoHide: c.autoHide,
visible: c.visible
})).sort((a, b) => {
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
if (aVertical !== bVertical) {
return aVertical - bVertical;
}
return String(a.id).localeCompare(String(b.id));
});
return JSON.stringify(mapped);
}
on_BarLayoutStateJsonChanged: {
if (typeof dockRecreateDebounce !== "undefined") {
dockRecreateDebounce.restart(); dockRecreateDebounce.restart();
} }
}
Connections { function onSurfaceRecoveryPass() {
target: FrameTransitionState root.dockEnabled = false;
function onTransitionRequested(revision) { Qt.callLater(() => {
root.pendingFrameTransitionRevision = Math.max(root.pendingFrameTransitionRevision, revision); root.dockEnabled = true;
root.runPendingFrameTransition(); });
}
}
Connections {
target: CompositorService
function onFrameCompositorLayoutReadyChanged() {
root.runPendingFrameTransition();
}
}
Connections {
target: SettingsData
function onForceDankBarLayoutRefresh() {
root.recreateBarSurfaces();
}
}
property bool frameSurfacesLoaded: true
Loader {
active: root.frameSurfacesLoaded
asynchronous: false
sourceComponent: Frame {}
}
Loader {
active: FrameTransitionState.effectiveFrameEnabled && SettingsData.frameLauncherEdgeHover
asynchronous: false
sourceComponent: FrameLauncherHoverZone {}
}
DeferredAction {
id: frameSurfaceReloadAction
onTriggered: root.frameSurfacesLoaded = true
}
Repeater {
id: dankBarRepeater
model: ScriptModel {
id: barRepeaterModel
values: JSON.parse(root._barLayoutStateJson)
}
Component.onCompleted: BarWidgetService.dankBarRepeater = dankBarRepeater
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
delegate: Loader {
id: barLoader
required property var modelData
property var barConfig: SettingsData.barConfigs.find(cfg => cfg.id === modelData.id) || null
active: root.barSurfacesLoaded && (barConfig?.enabled ?? false)
asynchronous: false
sourceComponent: DankBar {
barConfig: barLoader.barConfig
hyprlandOverviewLoader: dankBarRepeater.hyprlandOverviewLoaderRef
onColorPickerRequested: {
if (colorPickerModal.shouldBeVisible) {
colorPickerModal.close();
} else {
colorPickerModal.show();
}
}
}
} }
} }
@@ -329,132 +205,17 @@ Item {
onTriggered: root.osdSurfacesLoaded = true onTriggered: root.osdSurfacesLoaded = true
} }
property bool hadRealScreen: true
property var previousRealScreenNames: []
// Guards for the screen-reconnect recovery path (see scheduleScreenReconnectRecovery).
property bool _screenRecoveryCooldown: false
property bool _screenRecoveryPending: false
function _getRealScreenNames() {
const names = [];
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name.length > 0)
names.push(Quickshell.screens[i].name);
}
return names;
}
function _hasRealScreen() {
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name.length > 0)
return true;
}
return false;
}
function triggerSurfaceRecovery(source) {
log.info("Surface recovery triggered by:", source, "screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","), "barLoaded:", root.barSurfacesLoaded, "frameLoaded:", root.frameSurfacesLoaded, "dockEnabled:", root.dockEnabled);
surfaceResumeRecoveryTimer.pass = 0;
surfaceResumeRecoveryTimer.interval = 800;
surfaceResumeRecoveryTimer.restart();
}
Connections {
target: Quickshell
function onScreensChanged() {
const hasReal = root._hasRealScreen();
const currentNames = root._getRealScreenNames();
log.info("Screens changed:", Quickshell.screens.length, Quickshell.screens.map(s => "'" + s.name + "'").join(","), "hasReal:", hasReal, "hadReal:", root.hadRealScreen);
const fullReconnect = !root.hadRealScreen && hasReal;
const partialReconnect = root.previousRealScreenNames.length > 0 && currentNames.some(name => !root.previousRealScreenNames.includes(name));
if (fullReconnect || partialReconnect) {
log.info("Screen reconnect detected, scheduling surface recovery", "full:", fullReconnect, "partial:", partialReconnect);
root.scheduleScreenReconnectRecovery();
}
root.hadRealScreen = hasReal;
root.previousRealScreenNames = currentNames;
}
}
// A DPMS off/on cycle removes an output from the screen list and re-adds it,
// which is indistinguishable here from a hotplug. Recovering immediately on
// every such event lets a flapping monitor (or a recovery that itself perturbs
// the output) drive an endless recovery storm that power-cycles the display
// (#2642). Debounce a burst of changes into a single pass, then hold a cooldown
// so repeated flaps trigger at most one recovery per window. Recovery still runs
// once per resume, so a partial DPMS resume keeps redrawing its surfaces (#2579).
function scheduleScreenReconnectRecovery() {
if (root._screenRecoveryCooldown) {
root._screenRecoveryPending = true;
return;
}
screenReconnectDebounce.restart();
}
Timer { Timer {
id: screenReconnectDebounce id: osdStartupTimer
// Wide enough to collapse the output-remove + output-re-add pair that one interval: 1000
// DPMS off/on cycle emits as two near-simultaneous events into one recovery.
interval: 450
repeat: false repeat: false
onTriggered: { onTriggered: root.osdSurfacesLoaded = true
root._screenRecoveryCooldown = true;
root._screenRecoveryPending = false;
screenReconnectCooldown.restart();
root.triggerSurfaceRecovery("screen-reconnect");
}
}
Timer {
id: screenReconnectCooldown
// Must exceed the full two-pass surfaceResumeRecoveryTimer sequence
// (800 + 2000 ms) so the cooldown still covers an in-flight recovery;
// raise this if those passes are lengthened.
interval: 4000
repeat: false
onTriggered: {
root._screenRecoveryCooldown = false;
if (root._screenRecoveryPending) {
root._screenRecoveryPending = false;
screenReconnectDebounce.restart();
}
}
}
Timer {
id: surfaceResumeRecoveryTimer
interval: 800
repeat: false
property int pass: 0
onTriggered: {
pass++;
log.info("Surface recovery pass", pass, "screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","));
root.recreateBarSurfaces();
if (root.frameSurfacesLoaded) {
root.frameSurfacesLoaded = false;
frameSurfaceReloadAction.schedule();
}
root.dockEnabled = false;
Qt.callLater(() => {
root.dockEnabled = true;
});
if (pass < 2) {
interval = 2000;
restart();
} else {
pass = 0;
interval = 800;
}
}
} }
Component.onCompleted: { Component.onCompleted: {
dockRecreateDebounce.start(); dockRecreateDebounce.start();
loginSoundTimer.start(); loginSoundTimer.start();
osdStartupTimer.start();
// These are dummy references just to trigger the singletons onCompleted to trigger // These are dummy references just to trigger the singletons onCompleted to trigger
PolkitService.polkitAvailable; PolkitService.polkitAvailable;
@@ -1035,21 +796,9 @@ Item {
target: SessionService target: SessionService
function onSessionResumed() { function onSessionResumed() {
log.info("Session resumed: screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","), "barLoaded:", root.barSurfacesLoaded, "frameLoaded:", root.frameSurfacesLoaded, "dockEnabled:", root.dockEnabled);
root.pendingOsdResumeReloads = 2; root.pendingOsdResumeReloads = 2;
osdResumeRecreateTimer.interval = 400; osdResumeRecreateTimer.interval = 400;
osdResumeRecreateTimer.restart(); osdResumeRecreateTimer.restart();
// This path runs its own recovery directly, so drop any queued or
// in-flight screen-reconnect recovery to avoid a redundant pass once
// its cooldown expires.
screenReconnectDebounce.stop();
screenReconnectCooldown.stop();
root._screenRecoveryCooldown = false;
root._screenRecoveryPending = false;
root.triggerSurfaceRecovery("sessionResumed");
} }
} }
@@ -1200,6 +949,10 @@ Item {
active: false active: false
Component.onCompleted: {
PopoutService.powerMenuModalLoader = powerMenuModalLoader;
}
PowerMenuModal { PowerMenuModal {
id: powerMenuModal id: powerMenuModal
@@ -1292,8 +1045,8 @@ Item {
dankDashPopoutLoader: dankDashPopoutLoader dankDashPopoutLoader: dankDashPopoutLoader
notepadSlideoutVariants: notepadSlideoutVariants notepadSlideoutVariants: notepadSlideoutVariants
hyprKeybindsModalLoader: hyprKeybindsModalLoader hyprKeybindsModalLoader: hyprKeybindsModalLoader
dankBarRepeater: dankBarRepeater dankBarRepeater: root.core?.dankBarRepeater ?? null
hyprlandOverviewLoader: hyprlandOverviewLoader hyprlandOverviewLoader: root.core?.hyprlandOverviewLoader ?? null
workspaceRenameModalLoader: workspaceRenameModalLoader workspaceRenameModalLoader: workspaceRenameModalLoader
windowRuleModalLoader: windowRuleModalLoader windowRuleModalLoader: windowRuleModalLoader
} }
@@ -1389,14 +1142,6 @@ Item {
} }
} }
LazyLoader {
id: hyprlandOverviewLoader
active: CompositorService.isHyprland
component: HyprlandOverview {
id: hyprlandOverview
}
}
LazyLoader { LazyLoader {
id: niriOverviewOverlayLoader id: niriOverviewOverlayLoader
active: CompositorService.isNiri && SettingsData.niriOverviewOverlayEnabled active: CompositorService.isNiri && SettingsData.niriOverviewOverlayEnabled
+61 -54
View File
@@ -894,11 +894,11 @@ Item {
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) section: topBarContent.getWidgetSection(parent)
parentScreen: barWindow.screen parentScreen: barWindow.screen
popoutTarget: clipboardHistoryPopoutLoader.item ?? null popoutTarget: PopoutService.clipboardHistoryPopoutLoader?.item ?? null
function openClipboardPopout(initialTab, mode) { function openClipboardPopout(initialTab, mode) {
openWidgetPopout({ openWidgetPopout({
loader: clipboardHistoryPopoutLoader, loader: PopoutService.clipboardHistoryPopoutLoader,
widgetItem: clipboardWidget, widgetItem: clipboardWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "clipboard", triggerSource: "clipboard",
@@ -915,8 +915,11 @@ Item {
onShowSavedItemsRequested: openClipboardPopout("saved") onShowSavedItemsRequested: openClipboardPopout("saved")
onClearAllRequested: { onClearAllRequested: {
clipboardHistoryPopoutLoader.active = true; const loader = PopoutService.clipboardHistoryPopoutLoader;
const popout = clipboardHistoryPopoutLoader.item; if (!loader)
return;
loader.active = true;
const popout = loader.item;
if (!popout?.confirmDialog) { if (!popout?.confirmDialog) {
return; return;
} }
@@ -941,16 +944,17 @@ Item {
section: topBarContent.getWidgetSection(parent) section: topBarContent.getWidgetSection(parent)
parentScreen: barWindow.screen parentScreen: barWindow.screen
onClicked: { onClicked: {
if (!powerMenuModalLoader) const loader = PopoutService.powerMenuModalLoader;
if (!loader)
return; return;
powerMenuModalLoader.active = true; loader.active = true;
if (!powerMenuModalLoader.item) if (!loader.item)
return; return;
if (powerMenuModalLoader.item.shouldBeVisible) { if (loader.item.shouldBeVisible) {
powerMenuModalLoader.item.close(); loader.item.close();
return; return;
} }
powerMenuModalLoader.item.openCentered(); loader.item.openCentered();
} }
} }
} }
@@ -964,23 +968,26 @@ Item {
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
section: topBarContent.getWidgetSection(parent) section: topBarContent.getWidgetSection(parent)
popoutTarget: appDrawerLoader.item popoutTarget: PopoutService.appDrawerLoader?.item
parentScreen: barWindow.screen parentScreen: barWindow.screen
hyprlandOverviewLoader: barWindow ? barWindow.hyprlandOverviewLoader : null hyprlandOverviewLoader: barWindow ? barWindow.hyprlandOverviewLoader : null
function _preparePopout() { function _preparePopout() {
appDrawerLoader.active = true; const loader = PopoutService.appDrawerLoader;
if (!appDrawerLoader.item) if (!loader)
return false;
loader.active = true;
if (!loader.item)
return false; return false;
const effectiveBarConfig = topBarContent.barConfig; const effectiveBarConfig = topBarContent.barConfig;
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1)); const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
if (appDrawerLoader.item.setBarContext) if (loader.item.setBarContext)
appDrawerLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0); loader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
if (appDrawerLoader.item.setTriggerPosition) { if (loader.item.setTriggerPosition) {
const globalPos = launcherButton.visualContent.mapToItem(null, 0, 0); const globalPos = launcherButton.visualContent.mapToItem(null, 0, 0);
const currentScreen = barWindow.screen; const currentScreen = barWindow.screen;
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, launcherButton.visualWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig); const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, launcherButton.visualWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
appDrawerLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, launcherButton.section, currentScreen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig); loader.item.setTriggerPosition(pos.x, pos.y, pos.width, launcherButton.section, currentScreen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
} }
return true; return true;
} }
@@ -988,30 +995,30 @@ Item {
function openWithMode(mode) { function openWithMode(mode) {
if (!_preparePopout()) if (!_preparePopout())
return; return;
appDrawerLoader.item.openWithMode(mode); PopoutService.appDrawerLoader.item.openWithMode(mode);
} }
function toggleWithMode(mode) { function toggleWithMode(mode) {
if (!_preparePopout()) if (!_preparePopout())
return; return;
appDrawerLoader.item.toggleWithMode(mode); PopoutService.appDrawerLoader.item.toggleWithMode(mode);
} }
function openWithQuery(query) { function openWithQuery(query) {
if (!_preparePopout()) if (!_preparePopout())
return; return;
appDrawerLoader.item.openWithQuery(query); PopoutService.appDrawerLoader.item.openWithQuery(query);
} }
function toggleWithQuery(query) { function toggleWithQuery(query) {
if (!_preparePopout()) if (!_preparePopout())
return; return;
appDrawerLoader.item.toggleWithQuery(query); PopoutService.appDrawerLoader.item.toggleWithQuery(query);
} }
onClicked: { onClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: appDrawerLoader, loader: PopoutService.appDrawerLoader,
widgetItem: launcherButton, widgetItem: launcherButton,
section: launcherButton.section, section: launcherButton.section,
triggerSource: "appDrawer", triggerSource: "appDrawer",
@@ -1090,7 +1097,7 @@ Item {
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
section: topBarContent.getWidgetSection(parent) || "center" section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: dankDashPopoutLoader.item ?? null popoutTarget: PopoutService.dankDashPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
Component.onCompleted: { Component.onCompleted: {
@@ -1106,7 +1113,7 @@ Item {
onClockClicked: { onClockClicked: {
const section = topBarContent.getWidgetSection(parent) || "center"; const section = topBarContent.getWidgetSection(parent) || "center";
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: dankDashPopoutLoader, loader: PopoutService.dankDashPopoutLoader,
widgetItem: clockWidget, widgetItem: clockWidget,
section, section,
triggerSource: topBarContent._dashTriggerSource(section, "overview"), triggerSource: topBarContent._dashTriggerSource(section, "overview"),
@@ -1129,12 +1136,12 @@ Item {
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
section: topBarContent.getWidgetSection(parent) || "center" section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: dankDashPopoutLoader.item ?? null popoutTarget: PopoutService.dankDashPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
onClicked: { onClicked: {
const section = topBarContent.getWidgetSection(parent) || "center"; const section = topBarContent.getWidgetSection(parent) || "center";
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: dankDashPopoutLoader, loader: PopoutService.dankDashPopoutLoader,
widgetItem: mediaWidget, widgetItem: mediaWidget,
section, section,
triggerSource: topBarContent._dashTriggerSource(section, "media"), triggerSource: topBarContent._dashTriggerSource(section, "media"),
@@ -1156,12 +1163,12 @@ Item {
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
section: topBarContent.getWidgetSection(parent) || "center" section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: dankDashPopoutLoader.item ?? null popoutTarget: PopoutService.dankDashPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
onClicked: { onClicked: {
const section = topBarContent.getWidgetSection(parent) || "center"; const section = topBarContent.getWidgetSection(parent) || "center";
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: dankDashPopoutLoader, loader: PopoutService.dankDashPopoutLoader,
widgetItem: weatherWidget, widgetItem: weatherWidget,
section, section,
triggerSource: topBarContent._dashTriggerSource(section, "weather"), triggerSource: topBarContent._dashTriggerSource(section, "weather"),
@@ -1211,12 +1218,12 @@ Item {
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
widgetData: parent.widgetData widgetData: parent.widgetData
onCpuClicked: { onCpuClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: processListPopoutLoader, loader: PopoutService.processListPopoutLoader,
widgetItem: cpuWidget, widgetItem: cpuWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "cpu", triggerSource: "cpu",
@@ -1235,12 +1242,12 @@ Item {
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
widgetData: parent.widgetData widgetData: parent.widgetData
onRamClicked: { onRamClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: processListPopoutLoader, loader: PopoutService.processListPopoutLoader,
widgetItem: ramWidget, widgetItem: ramWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "memory", triggerSource: "memory",
@@ -1273,12 +1280,12 @@ Item {
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
widgetData: parent.widgetData widgetData: parent.widgetData
onCpuTempClicked: { onCpuTempClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: processListPopoutLoader, loader: PopoutService.processListPopoutLoader,
widgetItem: cpuTempWidget, widgetItem: cpuTempWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "cpu_temp", triggerSource: "cpu_temp",
@@ -1297,12 +1304,12 @@ Item {
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
widgetData: parent.widgetData widgetData: parent.widgetData
onGpuTempClicked: { onGpuTempClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: processListPopoutLoader, loader: PopoutService.processListPopoutLoader,
widgetItem: gpuTempWidget, widgetItem: gpuTempWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "gpu_temp", triggerSource: "gpu_temp",
@@ -1324,16 +1331,16 @@ Item {
NotificationCenterButton { NotificationCenterButton {
id: notificationButton id: notificationButton
hasUnread: barWindow.notificationCount > 0 hasUnread: barWindow.notificationCount > 0
isActive: notificationCenterLoader.item ? notificationCenterLoader.item.shouldBeVisible : false isActive: PopoutService.notificationCenterLoader?.item ? PopoutService.notificationCenterLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: notificationCenterLoader.item ?? null popoutTarget: PopoutService.notificationCenterLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
onClicked: { onClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: notificationCenterLoader, loader: PopoutService.notificationCenterLoader,
widgetItem: notificationButton, widgetItem: notificationButton,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "notifications", triggerSource: "notifications",
@@ -1349,18 +1356,18 @@ Item {
Battery { Battery {
id: batteryWidget id: batteryWidget
batteryPopupVisible: batteryPopoutLoader.item ? batteryPopoutLoader.item.shouldBeVisible : false batteryPopupVisible: PopoutService.batteryPopoutLoader?.item ? PopoutService.batteryPopoutLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
barSpacing: barConfig?.spacing ?? 4 barSpacing: barConfig?.spacing ?? 4
barConfig: topBarContent.barConfig barConfig: topBarContent.barConfig
popoutTarget: batteryPopoutLoader.item ?? null popoutTarget: PopoutService.batteryPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
onToggleBatteryPopup: { onToggleBatteryPopup: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: batteryPopoutLoader, loader: PopoutService.batteryPopoutLoader,
widgetItem: batteryWidget, widgetItem: batteryWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "battery", triggerSource: "battery",
@@ -1375,16 +1382,16 @@ Item {
DWLLayout { DWLLayout {
id: layoutWidget id: layoutWidget
layoutPopupVisible: layoutPopoutLoader.item ? layoutPopoutLoader.item.shouldBeVisible : false layoutPopupVisible: PopoutService.layoutPopoutLoader?.item ? PopoutService.layoutPopoutLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "center" section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: layoutPopoutLoader.item ?? null popoutTarget: PopoutService.layoutPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
onToggleLayoutPopup: { onToggleLayoutPopup: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: layoutPopoutLoader, loader: PopoutService.layoutPopoutLoader,
widgetItem: layoutWidget, widgetItem: layoutWidget,
section: topBarContent.getWidgetSection(parent) || "center", section: topBarContent.getWidgetSection(parent) || "center",
triggerSource: "layout", triggerSource: "layout",
@@ -1406,11 +1413,11 @@ Item {
barSpacing: barConfig?.spacing ?? 4 barSpacing: barConfig?.spacing ?? 4
barConfig: topBarContent.barConfig barConfig: topBarContent.barConfig
isAutoHideBar: topBarContent.barConfig?.autoHide ?? false isAutoHideBar: topBarContent.barConfig?.autoHide ?? false
popoutTarget: vpnPopoutLoader.item ?? null popoutTarget: PopoutService.vpnPopoutLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
onToggleVpnPopup: { onToggleVpnPopup: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: vpnPopoutLoader, loader: PopoutService.vpnPopoutLoader,
widgetItem: vpnWidget, widgetItem: vpnWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "vpn", triggerSource: "vpn",
@@ -1425,12 +1432,12 @@ Item {
ControlCenterButton { ControlCenterButton {
id: controlCenterButton id: controlCenterButton
isActive: controlCenterLoader.item ? controlCenterLoader.item.shouldBeVisible : false isActive: PopoutService.controlCenterLoader?.item ? PopoutService.controlCenterLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: controlCenterLoader.item ?? null popoutTarget: PopoutService.controlCenterLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
screenName: barWindow.screen?.name || "" screenName: barWindow.screen?.name || ""
screenModel: barWindow.screen?.model || "" screenModel: barWindow.screen?.model || ""
@@ -1448,14 +1455,14 @@ Item {
onClicked: { onClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: controlCenterLoader, loader: PopoutService.controlCenterLoader,
widgetItem: controlCenterButton, widgetItem: controlCenterButton,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "controlCenter", triggerSource: "controlCenter",
mode: "click", mode: "click",
setTriggerScreen: true setTriggerScreen: true
}); });
if (controlCenterLoader.item?.shouldBeVisible && NetworkService.wifiEnabled) if (PopoutService.controlCenterLoader?.item?.shouldBeVisible && NetworkService.wifiEnabled)
NetworkService.scanWifi(); NetworkService.scanWifi();
} }
} }
@@ -1567,12 +1574,12 @@ Item {
SystemUpdate { SystemUpdate {
id: systemUpdateWidget id: systemUpdateWidget
isActive: systemUpdateLoader.item ? systemUpdateLoader.item.shouldBeVisible : false isActive: PopoutService.systemUpdateLoader?.item ? PopoutService.systemUpdateLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right" section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: systemUpdateLoader.item ?? null popoutTarget: PopoutService.systemUpdateLoader?.item ?? null
parentScreen: barWindow.screen parentScreen: barWindow.screen
Component.onCompleted: { Component.onCompleted: {
@@ -1586,7 +1593,7 @@ Item {
onClicked: { onClicked: {
topBarContent.openWidgetPopout({ topBarContent.openWidgetPopout({
loader: systemUpdateLoader, loader: PopoutService.systemUpdateLoader,
widgetItem: systemUpdateWidget, widgetItem: systemUpdateWidget,
section: topBarContent.getWidgetSection(parent) || "right", section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "systemUpdate", triggerSource: "systemUpdate",
+27 -18
View File
@@ -27,10 +27,13 @@ PanelWindow {
property var systemUpdateButtonRef: null property var systemUpdateButtonRef: null
function triggerSystemUpdate() { function triggerSystemUpdate() {
systemUpdateLoader.active = true; const loader = PopoutService.systemUpdateLoader;
if (!systemUpdateLoader.item) if (!loader)
return; return;
const popout = systemUpdateLoader.item; loader.active = true;
if (!loader.item)
return;
const popout = loader.item;
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1)); const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
if (systemUpdateButtonRef && popout.setTriggerPosition) { if (systemUpdateButtonRef && popout.setTriggerPosition) {
const screenPos = systemUpdateButtonRef.mapToItem(null, 0, 0); const screenPos = systemUpdateButtonRef.mapToItem(null, 0, 0);
@@ -44,35 +47,41 @@ PanelWindow {
} }
function triggerControlCenter() { function triggerControlCenter() {
controlCenterLoader.active = true; const loader = PopoutService.controlCenterLoader;
if (!controlCenterLoader.item) { if (!loader)
return;
loader.active = true;
if (!loader.item) {
return; return;
} }
if (controlCenterButtonRef && controlCenterLoader.item.setTriggerPosition) { if (controlCenterButtonRef && loader.item.setTriggerPosition) {
const screenPos = controlCenterButtonRef.mapToItem(null, 0, 0); const screenPos = controlCenterButtonRef.mapToItem(null, 0, 0);
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1)); const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
const pos = SettingsData.getPopupTriggerPosition(screenPos, barWindow.screen, barWindow.effectiveBarThickness, controlCenterButtonRef.width, barConfig?.spacing ?? 4, barPosition, barConfig); const pos = SettingsData.getPopupTriggerPosition(screenPos, barWindow.screen, barWindow.effectiveBarThickness, controlCenterButtonRef.width, barConfig?.spacing ?? 4, barPosition, barConfig);
const section = controlCenterButtonRef.section || "right"; const section = controlCenterButtonRef.section || "right";
controlCenterLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig); loader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
} else { } else {
controlCenterLoader.item.triggerScreen = barWindow.screen; loader.item.triggerScreen = barWindow.screen;
} }
controlCenterLoader.item.toggle(); loader.item.toggle();
if (controlCenterLoader.item.shouldBeVisible && NetworkService.wifiEnabled) { if (loader.item.shouldBeVisible && NetworkService.wifiEnabled) {
NetworkService.scanWifi(); NetworkService.scanWifi();
} }
} }
function triggerDashTab(tabId) { function triggerDashTab(tabId) {
dankDashPopoutLoader.active = true; const loader = PopoutService.dankDashPopoutLoader;
if (!dankDashPopoutLoader.item) { if (!loader)
return false;
loader.active = true;
if (!loader.item) {
return false; return false;
} }
let section = "center"; let section = "center";
if (clockButtonRef && clockButtonRef.visualContent && dankDashPopoutLoader.item.setTriggerPosition) { if (clockButtonRef && clockButtonRef.visualContent && loader.item.setTriggerPosition) {
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1)); const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
section = clockButtonRef.section || "center"; section = clockButtonRef.section || "center";
@@ -98,14 +107,14 @@ PanelWindow {
} }
const pos = SettingsData.getPopupTriggerPosition(triggerPos, barWindow.screen, barWindow.effectiveBarThickness, triggerWidth, barConfig?.spacing ?? 4, barPosition, barConfig); const pos = SettingsData.getPopupTriggerPosition(triggerPos, barWindow.screen, barWindow.effectiveBarThickness, triggerWidth, barConfig?.spacing ?? 4, barPosition, barConfig);
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig); loader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
} else { } else {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen; loader.item.triggerScreen = barWindow.screen;
} }
if (dankDashPopoutLoader.item.requestTab) if (loader.item.requestTab)
dankDashPopoutLoader.item.requestTab(tabId); loader.item.requestTab(tabId);
PopoutManager.requestPopout(dankDashPopoutLoader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId); PopoutManager.requestPopout(loader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId);
return true; return true;
} }
@@ -14,21 +14,22 @@ BasePill {
readonly property string targetScreenName: parentScreen?.name || focusedScreenName readonly property string targetScreenName: parentScreen?.name || focusedScreenName
function resolveNotepadInstance() { function resolveNotepadInstance() {
if (typeof notepadSlideoutVariants === "undefined" || !notepadSlideoutVariants || !notepadSlideoutVariants.instances) { const slideouts = PopoutService.notepadSlideouts;
if (!slideouts || slideouts.length === 0) {
return null; return null;
} }
const targetScreen = targetScreenName; const targetScreen = targetScreenName;
if (targetScreen) { if (targetScreen) {
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) { for (var i = 0; i < slideouts.length; i++) {
var slideout = notepadSlideoutVariants.instances[i]; var slideout = slideouts[i];
if (slideout.modelData && slideout.modelData.name === targetScreen) { if (slideout.modelData && slideout.modelData.name === targetScreen) {
return slideout; return slideout;
} }
} }
} }
return notepadSlideoutVariants.instances.length > 0 ? notepadSlideoutVariants.instances[0] : null; return slideouts[0];
} }
readonly property var notepadInstance: resolveNotepadInstance() readonly property var notepadInstance: resolveNotepadInstance()
+1
View File
@@ -38,6 +38,7 @@ Singleton {
property var spotlightBarModal: null property var spotlightBarModal: null
property var spotlightBarModalLoader: null property var spotlightBarModalLoader: null
property var powerMenuModal: null property var powerMenuModal: null
property var powerMenuModalLoader: null
property var processListModal: null property var processListModal: null
property var processListModalLoader: null property var processListModalLoader: null
property var colorPickerModal: null property var colorPickerModal: null
+289
View File
@@ -0,0 +1,289 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Modules.DankBar
import qs.Modules.Frame
import qs.Modules.WorkspaceOverlays
import qs.Services
Item {
id: root
readonly property var log: Log.scoped("ShellCore")
property bool barSurfacesLoaded: true
property int pendingFrameTransitionRevision: 0
property bool frameSurfacesLoaded: true
property alias dankBarRepeater: dankBarRepeater
property alias hyprlandOverviewLoader: hyprlandOverviewLoader
signal surfaceRecoveryPass
property string _barLayoutStateJson: {
if (!barSurfacesLoaded)
return "[]";
const configs = SettingsData.barConfigs;
const mapped = configs.map(c => ({
id: c.id,
position: c.position,
autoHide: c.autoHide,
visible: c.visible
})).sort((a, b) => {
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
if (aVertical !== bVertical) {
return aVertical - bVertical;
}
return String(a.id).localeCompare(String(b.id));
});
return JSON.stringify(mapped);
}
function recreateBarSurfaces() {
log.info("Recreating bar surfaces, screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","));
if (barSurfacesLoaded)
barSurfacesLoaded = false;
barSurfaceReloadAction.schedule();
}
// Holds the bar rebuild until the compositor applies the layout, so the swap lands in one pass
function runPendingFrameTransition() {
if (pendingFrameTransitionRevision <= 0 || !CompositorService.frameCompositorLayoutReady)
return;
recreateBarSurfaces();
}
DeferredAction {
id: barSurfaceReloadAction
onTriggered: {
// Ack first so the latch flips and new bars build directly in the post-transition state
if (root.pendingFrameTransitionRevision > 0 && CompositorService.frameCompositorLayoutReady) {
FrameTransitionState.acknowledge(root.pendingFrameTransitionRevision);
root.pendingFrameTransitionRevision = 0;
}
root.barSurfacesLoaded = true;
}
}
Connections {
target: FrameTransitionState
function onTransitionRequested(revision) {
root.pendingFrameTransitionRevision = Math.max(root.pendingFrameTransitionRevision, revision);
root.runPendingFrameTransition();
}
}
Connections {
target: CompositorService
function onFrameCompositorLayoutReadyChanged() {
root.runPendingFrameTransition();
}
}
Connections {
target: SettingsData
function onForceDankBarLayoutRefresh() {
root.recreateBarSurfaces();
}
}
Loader {
active: root.frameSurfacesLoaded
asynchronous: false
sourceComponent: Frame {}
}
Loader {
active: FrameTransitionState.effectiveFrameEnabled && SettingsData.frameLauncherEdgeHover
asynchronous: false
sourceComponent: FrameLauncherHoverZone {}
}
DeferredAction {
id: frameSurfaceReloadAction
onTriggered: root.frameSurfacesLoaded = true
}
Repeater {
id: dankBarRepeater
model: ScriptModel {
id: barRepeaterModel
values: JSON.parse(root._barLayoutStateJson)
}
Component.onCompleted: BarWidgetService.dankBarRepeater = dankBarRepeater
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
delegate: Loader {
id: barLoader
required property var modelData
property var barConfig: SettingsData.barConfigs.find(cfg => cfg.id === modelData.id) || null
active: root.barSurfacesLoaded && (barConfig?.enabled ?? false)
asynchronous: false
sourceComponent: DankBar {
barConfig: barLoader.barConfig
hyprlandOverviewLoader: dankBarRepeater.hyprlandOverviewLoaderRef
onColorPickerRequested: {
const modal = PopoutService.colorPickerModal;
if (!modal)
return;
if (modal.shouldBeVisible) {
modal.close();
} else {
modal.show();
}
}
}
}
}
property bool hadRealScreen: true
property var previousRealScreenNames: []
// Guards for the screen-reconnect recovery path (see scheduleScreenReconnectRecovery).
property bool _screenRecoveryCooldown: false
property bool _screenRecoveryPending: false
function _getRealScreenNames() {
const names = [];
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name.length > 0)
names.push(Quickshell.screens[i].name);
}
return names;
}
function _hasRealScreen() {
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name.length > 0)
return true;
}
return false;
}
function triggerSurfaceRecovery(source) {
log.info("Surface recovery triggered by:", source, "screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","), "barLoaded:", root.barSurfacesLoaded, "frameLoaded:", root.frameSurfacesLoaded);
surfaceResumeRecoveryTimer.pass = 0;
surfaceResumeRecoveryTimer.interval = 800;
surfaceResumeRecoveryTimer.restart();
}
Connections {
target: Quickshell
function onScreensChanged() {
const hasReal = root._hasRealScreen();
const currentNames = root._getRealScreenNames();
log.info("Screens changed:", Quickshell.screens.length, Quickshell.screens.map(s => "'" + s.name + "'").join(","), "hasReal:", hasReal, "hadReal:", root.hadRealScreen);
const fullReconnect = !root.hadRealScreen && hasReal;
const partialReconnect = root.previousRealScreenNames.length > 0 && currentNames.some(name => !root.previousRealScreenNames.includes(name));
if (fullReconnect || partialReconnect) {
log.info("Screen reconnect detected, scheduling surface recovery", "full:", fullReconnect, "partial:", partialReconnect);
root.scheduleScreenReconnectRecovery();
}
root.hadRealScreen = hasReal;
root.previousRealScreenNames = currentNames;
}
}
// A DPMS off/on cycle removes an output from the screen list and re-adds it,
// which is indistinguishable here from a hotplug. Recovering immediately on
// every such event lets a flapping monitor (or a recovery that itself perturbs
// the output) drive an endless recovery storm that power-cycles the display
// (#2642). Debounce a burst of changes into a single pass, then hold a cooldown
// so repeated flaps trigger at most one recovery per window. Recovery still runs
// once per resume, so a partial DPMS resume keeps redrawing its surfaces (#2579).
function scheduleScreenReconnectRecovery() {
if (root._screenRecoveryCooldown) {
root._screenRecoveryPending = true;
return;
}
screenReconnectDebounce.restart();
}
Timer {
id: screenReconnectDebounce
// Wide enough to collapse the output-remove + output-re-add pair that one
// DPMS off/on cycle emits as two near-simultaneous events into one recovery.
interval: 450
repeat: false
onTriggered: {
root._screenRecoveryCooldown = true;
root._screenRecoveryPending = false;
screenReconnectCooldown.restart();
root.triggerSurfaceRecovery("screen-reconnect");
}
}
Timer {
id: screenReconnectCooldown
// Must exceed the full two-pass surfaceResumeRecoveryTimer sequence
// (800 + 2000 ms) so the cooldown still covers an in-flight recovery;
// raise this if those passes are lengthened.
interval: 4000
repeat: false
onTriggered: {
root._screenRecoveryCooldown = false;
if (root._screenRecoveryPending) {
root._screenRecoveryPending = false;
screenReconnectDebounce.restart();
}
}
}
Timer {
id: surfaceResumeRecoveryTimer
interval: 800
repeat: false
property int pass: 0
onTriggered: {
pass++;
log.info("Surface recovery pass", pass, "screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","));
root.recreateBarSurfaces();
if (root.frameSurfacesLoaded) {
root.frameSurfacesLoaded = false;
frameSurfaceReloadAction.schedule();
}
root.surfaceRecoveryPass();
if (pass < 2) {
interval = 2000;
restart();
} else {
pass = 0;
interval = 800;
}
}
}
Connections {
target: SessionService
function onSessionResumed() {
log.info("Session resumed: screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","), "barLoaded:", root.barSurfacesLoaded, "frameLoaded:", root.frameSurfacesLoaded);
// This path runs its own recovery directly, so drop any queued or
// in-flight screen-reconnect recovery to avoid a redundant pass once
// its cooldown expires.
screenReconnectDebounce.stop();
screenReconnectCooldown.stop();
root._screenRecoveryCooldown = false;
root._screenRecoveryPending = false;
root.triggerSurfaceRecovery("sessionResumed");
}
}
LazyLoader {
id: hyprlandOverviewLoader
active: CompositorService.isHyprland
component: HyprlandOverview {
id: hyprlandOverview
}
}
}
+32 -5
View File
@@ -9,6 +9,9 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Common
import qs.Modules
import qs.Services
ShellRoot { ShellRoot {
id: entrypoint id: entrypoint
@@ -21,16 +24,40 @@ ShellRoot {
} }
Loader { Loader {
id: dmsShellLoader id: wallpaperLoader
asynchronous: false
sourceComponent: DMSShell {}
active: !entrypoint.runGreeter active: !entrypoint.runGreeter
asynchronous: false
sourceComponent: Scope {
WallpaperBackground {}
Loader {
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
}
}
Loader {
id: shellCoreLoader
active: !entrypoint.runGreeter
asynchronous: true
source: "ShellCore.qml"
onLoaded: dmsShellLoader.setSource("DMSShell.qml", {
core: item
})
}
Loader {
id: dmsShellLoader
asynchronous: true
} }
Loader { Loader {
id: dmsGreeterLoader id: dmsGreeterLoader
asynchronous: false
sourceComponent: DMSGreeter {}
active: entrypoint.runGreeter active: entrypoint.runGreeter
asynchronous: false
source: "DMSGreeter.qml"
} }
} }