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
(cherry picked from commit 3c5245914f)
This commit is contained in:
+21
-276
@@ -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
|
||||
|
||||
@@ -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,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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user