1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-02 03:28:28 -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.OSD
import qs.Modules.ProcessList
import qs.Modules.DankBar
import qs.Modules.DankBar.Popouts
import qs.Modules.Frame
import qs.Modules.WorkspaceOverlays
import qs.Modules.Settings.DisplayConfig
import qs.Services
@@ -32,7 +30,9 @@ Item {
readonly property var log: Log.scoped("DMSShell")
readonly property var _sessionsServiceRef: SessionsService
property bool osdSurfacesLoaded: true
property var core: null
property bool osdSurfacesLoaded: false
property int pendingOsdResumeReloads: 0
function recreateOsdSurfaces() {
@@ -41,16 +41,6 @@ Item {
osdSurfaceReloadTimer.restart();
}
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
DesktopWidgetLayer {}
Lock {
@@ -149,132 +139,18 @@ Item {
}
}
property bool barSurfacesLoaded: true
property int pendingFrameTransitionRevision: 0
Connections {
target: root.core
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;
}
}
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") {
function on_BarLayoutStateJsonChanged() {
dockRecreateDebounce.restart();
}
}
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();
}
}
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();
}
}
}
function onSurfaceRecoveryPass() {
root.dockEnabled = false;
Qt.callLater(() => {
root.dockEnabled = true;
});
}
}
@@ -329,132 +205,17 @@ Item {
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 {
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
id: osdStartupTimer
interval: 1000
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.dockEnabled = false;
Qt.callLater(() => {
root.dockEnabled = true;
});
if (pass < 2) {
interval = 2000;
restart();
} else {
pass = 0;
interval = 800;
}
}
onTriggered: root.osdSurfacesLoaded = true
}
Component.onCompleted: {
dockRecreateDebounce.start();
loginSoundTimer.start();
osdStartupTimer.start();
// These are dummy references just to trigger the singletons onCompleted to trigger
PolkitService.polkitAvailable;
@@ -1035,21 +796,9 @@ Item {
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, "dockEnabled:", root.dockEnabled);
root.pendingOsdResumeReloads = 2;
osdResumeRecreateTimer.interval = 400;
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
Component.onCompleted: {
PopoutService.powerMenuModalLoader = powerMenuModalLoader;
}
PowerMenuModal {
id: powerMenuModal
@@ -1292,8 +1045,8 @@ Item {
dankDashPopoutLoader: dankDashPopoutLoader
notepadSlideoutVariants: notepadSlideoutVariants
hyprKeybindsModalLoader: hyprKeybindsModalLoader
dankBarRepeater: dankBarRepeater
hyprlandOverviewLoader: hyprlandOverviewLoader
dankBarRepeater: root.core?.dankBarRepeater ?? null
hyprlandOverviewLoader: root.core?.hyprlandOverviewLoader ?? null
workspaceRenameModalLoader: workspaceRenameModalLoader
windowRuleModalLoader: windowRuleModalLoader
}
@@ -1389,14 +1142,6 @@ Item {
}
}
LazyLoader {
id: hyprlandOverviewLoader
active: CompositorService.isHyprland
component: HyprlandOverview {
id: hyprlandOverview
}
}
LazyLoader {
id: niriOverviewOverlayLoader
active: CompositorService.isNiri && SettingsData.niriOverviewOverlayEnabled
+61 -54
View File
@@ -894,11 +894,11 @@ Item {
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent)
parentScreen: barWindow.screen
popoutTarget: clipboardHistoryPopoutLoader.item ?? null
popoutTarget: PopoutService.clipboardHistoryPopoutLoader?.item ?? null
function openClipboardPopout(initialTab, mode) {
openWidgetPopout({
loader: clipboardHistoryPopoutLoader,
loader: PopoutService.clipboardHistoryPopoutLoader,
widgetItem: clipboardWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "clipboard",
@@ -915,8 +915,11 @@ Item {
onShowSavedItemsRequested: openClipboardPopout("saved")
onClearAllRequested: {
clipboardHistoryPopoutLoader.active = true;
const popout = clipboardHistoryPopoutLoader.item;
const loader = PopoutService.clipboardHistoryPopoutLoader;
if (!loader)
return;
loader.active = true;
const popout = loader.item;
if (!popout?.confirmDialog) {
return;
}
@@ -941,16 +944,17 @@ Item {
section: topBarContent.getWidgetSection(parent)
parentScreen: barWindow.screen
onClicked: {
if (!powerMenuModalLoader)
const loader = PopoutService.powerMenuModalLoader;
if (!loader)
return;
powerMenuModalLoader.active = true;
if (!powerMenuModalLoader.item)
loader.active = true;
if (!loader.item)
return;
if (powerMenuModalLoader.item.shouldBeVisible) {
powerMenuModalLoader.item.close();
if (loader.item.shouldBeVisible) {
loader.item.close();
return;
}
powerMenuModalLoader.item.openCentered();
loader.item.openCentered();
}
}
}
@@ -964,23 +968,26 @@ Item {
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
section: topBarContent.getWidgetSection(parent)
popoutTarget: appDrawerLoader.item
popoutTarget: PopoutService.appDrawerLoader?.item
parentScreen: barWindow.screen
hyprlandOverviewLoader: barWindow ? barWindow.hyprlandOverviewLoader : null
function _preparePopout() {
appDrawerLoader.active = true;
if (!appDrawerLoader.item)
const loader = PopoutService.appDrawerLoader;
if (!loader)
return false;
loader.active = true;
if (!loader.item)
return false;
const effectiveBarConfig = topBarContent.barConfig;
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
if (appDrawerLoader.item.setBarContext)
appDrawerLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
if (appDrawerLoader.item.setTriggerPosition) {
if (loader.item.setBarContext)
loader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
if (loader.item.setTriggerPosition) {
const globalPos = launcherButton.visualContent.mapToItem(null, 0, 0);
const currentScreen = barWindow.screen;
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;
}
@@ -988,30 +995,30 @@ Item {
function openWithMode(mode) {
if (!_preparePopout())
return;
appDrawerLoader.item.openWithMode(mode);
PopoutService.appDrawerLoader.item.openWithMode(mode);
}
function toggleWithMode(mode) {
if (!_preparePopout())
return;
appDrawerLoader.item.toggleWithMode(mode);
PopoutService.appDrawerLoader.item.toggleWithMode(mode);
}
function openWithQuery(query) {
if (!_preparePopout())
return;
appDrawerLoader.item.openWithQuery(query);
PopoutService.appDrawerLoader.item.openWithQuery(query);
}
function toggleWithQuery(query) {
if (!_preparePopout())
return;
appDrawerLoader.item.toggleWithQuery(query);
PopoutService.appDrawerLoader.item.toggleWithQuery(query);
}
onClicked: {
topBarContent.openWidgetPopout({
loader: appDrawerLoader,
loader: PopoutService.appDrawerLoader,
widgetItem: launcherButton,
section: launcherButton.section,
triggerSource: "appDrawer",
@@ -1090,7 +1097,7 @@ Item {
barThickness: barWindow.effectiveBarThickness
widgetThickness: barWindow.widgetThickness
section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: dankDashPopoutLoader.item ?? null
popoutTarget: PopoutService.dankDashPopoutLoader?.item ?? null
parentScreen: barWindow.screen
Component.onCompleted: {
@@ -1106,7 +1113,7 @@ Item {
onClockClicked: {
const section = topBarContent.getWidgetSection(parent) || "center";
topBarContent.openWidgetPopout({
loader: dankDashPopoutLoader,
loader: PopoutService.dankDashPopoutLoader,
widgetItem: clockWidget,
section,
triggerSource: topBarContent._dashTriggerSource(section, "overview"),
@@ -1129,12 +1136,12 @@ Item {
barThickness: barWindow.effectiveBarThickness
widgetThickness: barWindow.widgetThickness
section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: dankDashPopoutLoader.item ?? null
popoutTarget: PopoutService.dankDashPopoutLoader?.item ?? null
parentScreen: barWindow.screen
onClicked: {
const section = topBarContent.getWidgetSection(parent) || "center";
topBarContent.openWidgetPopout({
loader: dankDashPopoutLoader,
loader: PopoutService.dankDashPopoutLoader,
widgetItem: mediaWidget,
section,
triggerSource: topBarContent._dashTriggerSource(section, "media"),
@@ -1156,12 +1163,12 @@ Item {
barThickness: barWindow.effectiveBarThickness
widgetThickness: barWindow.widgetThickness
section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: dankDashPopoutLoader.item ?? null
popoutTarget: PopoutService.dankDashPopoutLoader?.item ?? null
parentScreen: barWindow.screen
onClicked: {
const section = topBarContent.getWidgetSection(parent) || "center";
topBarContent.openWidgetPopout({
loader: dankDashPopoutLoader,
loader: PopoutService.dankDashPopoutLoader,
widgetItem: weatherWidget,
section,
triggerSource: topBarContent._dashTriggerSource(section, "weather"),
@@ -1211,12 +1218,12 @@ Item {
widgetThickness: barWindow.widgetThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null
popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen
widgetData: parent.widgetData
onCpuClicked: {
topBarContent.openWidgetPopout({
loader: processListPopoutLoader,
loader: PopoutService.processListPopoutLoader,
widgetItem: cpuWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "cpu",
@@ -1235,12 +1242,12 @@ Item {
widgetThickness: barWindow.widgetThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null
popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen
widgetData: parent.widgetData
onRamClicked: {
topBarContent.openWidgetPopout({
loader: processListPopoutLoader,
loader: PopoutService.processListPopoutLoader,
widgetItem: ramWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "memory",
@@ -1273,12 +1280,12 @@ Item {
widgetThickness: barWindow.widgetThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null
popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen
widgetData: parent.widgetData
onCpuTempClicked: {
topBarContent.openWidgetPopout({
loader: processListPopoutLoader,
loader: PopoutService.processListPopoutLoader,
widgetItem: cpuTempWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "cpu_temp",
@@ -1297,12 +1304,12 @@ Item {
widgetThickness: barWindow.widgetThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: processListPopoutLoader.item ?? null
popoutTarget: PopoutService.processListPopoutLoader?.item ?? null
parentScreen: barWindow.screen
widgetData: parent.widgetData
onGpuTempClicked: {
topBarContent.openWidgetPopout({
loader: processListPopoutLoader,
loader: PopoutService.processListPopoutLoader,
widgetItem: gpuTempWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "gpu_temp",
@@ -1324,16 +1331,16 @@ Item {
NotificationCenterButton {
id: notificationButton
hasUnread: barWindow.notificationCount > 0
isActive: notificationCenterLoader.item ? notificationCenterLoader.item.shouldBeVisible : false
isActive: PopoutService.notificationCenterLoader?.item ? PopoutService.notificationCenterLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: notificationCenterLoader.item ?? null
popoutTarget: PopoutService.notificationCenterLoader?.item ?? null
parentScreen: barWindow.screen
onClicked: {
topBarContent.openWidgetPopout({
loader: notificationCenterLoader,
loader: PopoutService.notificationCenterLoader,
widgetItem: notificationButton,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "notifications",
@@ -1349,18 +1356,18 @@ Item {
Battery {
id: batteryWidget
batteryPopupVisible: batteryPopoutLoader.item ? batteryPopoutLoader.item.shouldBeVisible : false
batteryPopupVisible: PopoutService.batteryPopoutLoader?.item ? PopoutService.batteryPopoutLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
barSpacing: barConfig?.spacing ?? 4
barConfig: topBarContent.barConfig
popoutTarget: batteryPopoutLoader.item ?? null
popoutTarget: PopoutService.batteryPopoutLoader?.item ?? null
parentScreen: barWindow.screen
onToggleBatteryPopup: {
topBarContent.openWidgetPopout({
loader: batteryPopoutLoader,
loader: PopoutService.batteryPopoutLoader,
widgetItem: batteryWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "battery",
@@ -1375,16 +1382,16 @@ Item {
DWLLayout {
id: layoutWidget
layoutPopupVisible: layoutPopoutLoader.item ? layoutPopoutLoader.item.shouldBeVisible : false
layoutPopupVisible: PopoutService.layoutPopoutLoader?.item ? PopoutService.layoutPopoutLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "center"
popoutTarget: layoutPopoutLoader.item ?? null
popoutTarget: PopoutService.layoutPopoutLoader?.item ?? null
parentScreen: barWindow.screen
onToggleLayoutPopup: {
topBarContent.openWidgetPopout({
loader: layoutPopoutLoader,
loader: PopoutService.layoutPopoutLoader,
widgetItem: layoutWidget,
section: topBarContent.getWidgetSection(parent) || "center",
triggerSource: "layout",
@@ -1406,11 +1413,11 @@ Item {
barSpacing: barConfig?.spacing ?? 4
barConfig: topBarContent.barConfig
isAutoHideBar: topBarContent.barConfig?.autoHide ?? false
popoutTarget: vpnPopoutLoader.item ?? null
popoutTarget: PopoutService.vpnPopoutLoader?.item ?? null
parentScreen: barWindow.screen
onToggleVpnPopup: {
topBarContent.openWidgetPopout({
loader: vpnPopoutLoader,
loader: PopoutService.vpnPopoutLoader,
widgetItem: vpnWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "vpn",
@@ -1425,12 +1432,12 @@ Item {
ControlCenterButton {
id: controlCenterButton
isActive: controlCenterLoader.item ? controlCenterLoader.item.shouldBeVisible : false
isActive: PopoutService.controlCenterLoader?.item ? PopoutService.controlCenterLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: controlCenterLoader.item ?? null
popoutTarget: PopoutService.controlCenterLoader?.item ?? null
parentScreen: barWindow.screen
screenName: barWindow.screen?.name || ""
screenModel: barWindow.screen?.model || ""
@@ -1448,14 +1455,14 @@ Item {
onClicked: {
topBarContent.openWidgetPopout({
loader: controlCenterLoader,
loader: PopoutService.controlCenterLoader,
widgetItem: controlCenterButton,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "controlCenter",
mode: "click",
setTriggerScreen: true
});
if (controlCenterLoader.item?.shouldBeVisible && NetworkService.wifiEnabled)
if (PopoutService.controlCenterLoader?.item?.shouldBeVisible && NetworkService.wifiEnabled)
NetworkService.scanWifi();
}
}
@@ -1567,12 +1574,12 @@ Item {
SystemUpdate {
id: systemUpdateWidget
isActive: systemUpdateLoader.item ? systemUpdateLoader.item.shouldBeVisible : false
isActive: PopoutService.systemUpdateLoader?.item ? PopoutService.systemUpdateLoader?.item.shouldBeVisible : false
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
axis: barWindow.axis
section: topBarContent.getWidgetSection(parent) || "right"
popoutTarget: systemUpdateLoader.item ?? null
popoutTarget: PopoutService.systemUpdateLoader?.item ?? null
parentScreen: barWindow.screen
Component.onCompleted: {
@@ -1586,7 +1593,7 @@ Item {
onClicked: {
topBarContent.openWidgetPopout({
loader: systemUpdateLoader,
loader: PopoutService.systemUpdateLoader,
widgetItem: systemUpdateWidget,
section: topBarContent.getWidgetSection(parent) || "right",
triggerSource: "systemUpdate",
+27 -18
View File
@@ -27,10 +27,13 @@ PanelWindow {
property var systemUpdateButtonRef: null
function triggerSystemUpdate() {
systemUpdateLoader.active = true;
if (!systemUpdateLoader.item)
const loader = PopoutService.systemUpdateLoader;
if (!loader)
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));
if (systemUpdateButtonRef && popout.setTriggerPosition) {
const screenPos = systemUpdateButtonRef.mapToItem(null, 0, 0);
@@ -44,35 +47,41 @@ PanelWindow {
}
function triggerControlCenter() {
controlCenterLoader.active = true;
if (!controlCenterLoader.item) {
const loader = PopoutService.controlCenterLoader;
if (!loader)
return;
loader.active = true;
if (!loader.item) {
return;
}
if (controlCenterButtonRef && controlCenterLoader.item.setTriggerPosition) {
if (controlCenterButtonRef && loader.item.setTriggerPosition) {
const screenPos = controlCenterButtonRef.mapToItem(null, 0, 0);
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 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 {
controlCenterLoader.item.triggerScreen = barWindow.screen;
loader.item.triggerScreen = barWindow.screen;
}
controlCenterLoader.item.toggle();
if (controlCenterLoader.item.shouldBeVisible && NetworkService.wifiEnabled) {
loader.item.toggle();
if (loader.item.shouldBeVisible && NetworkService.wifiEnabled) {
NetworkService.scanWifi();
}
}
function triggerDashTab(tabId) {
dankDashPopoutLoader.active = true;
if (!dankDashPopoutLoader.item) {
const loader = PopoutService.dankDashPopoutLoader;
if (!loader)
return false;
loader.active = true;
if (!loader.item) {
return false;
}
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));
section = clockButtonRef.section || "center";
@@ -98,14 +107,14 @@ PanelWindow {
}
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 {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
loader.item.triggerScreen = barWindow.screen;
}
if (dankDashPopoutLoader.item.requestTab)
dankDashPopoutLoader.item.requestTab(tabId);
PopoutManager.requestPopout(dankDashPopoutLoader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId);
if (loader.item.requestTab)
loader.item.requestTab(tabId);
PopoutManager.requestPopout(loader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId);
return true;
}
@@ -14,21 +14,22 @@ BasePill {
readonly property string targetScreenName: parentScreen?.name || focusedScreenName
function resolveNotepadInstance() {
if (typeof notepadSlideoutVariants === "undefined" || !notepadSlideoutVariants || !notepadSlideoutVariants.instances) {
const slideouts = PopoutService.notepadSlideouts;
if (!slideouts || slideouts.length === 0) {
return null;
}
const targetScreen = targetScreenName;
if (targetScreen) {
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
var slideout = notepadSlideoutVariants.instances[i];
for (var i = 0; i < slideouts.length; i++) {
var slideout = slideouts[i];
if (slideout.modelData && slideout.modelData.name === targetScreen) {
return slideout;
}
}
}
return notepadSlideoutVariants.instances.length > 0 ? notepadSlideoutVariants.instances[0] : null;
return slideouts[0];
}
readonly property var notepadInstance: resolveNotepadInstance()
+1
View File
@@ -38,6 +38,7 @@ Singleton {
property var spotlightBarModal: null
property var spotlightBarModalLoader: null
property var powerMenuModal: null
property var powerMenuModalLoader: null
property var processListModal: null
property var processListModalLoader: 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 Quickshell
import qs.Common
import qs.Modules
import qs.Services
ShellRoot {
id: entrypoint
@@ -21,16 +24,40 @@ ShellRoot {
}
Loader {
id: dmsShellLoader
asynchronous: false
sourceComponent: DMSShell {}
id: wallpaperLoader
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 {
id: dmsGreeterLoader
asynchronous: false
sourceComponent: DMSGreeter {}
active: entrypoint.runGreeter
asynchronous: false
source: "DMSGreeter.qml"
}
}