mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
refactor(compositor): reduce unnecessary UI updates
- Defer updates when window state hasn't actually changed - Cache frame-blocked state per screen instead of recalculating constantly - keep popouts cache so reopening them is instant & clean up otherwise
This commit is contained in:
@@ -135,11 +135,34 @@ Singleton {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
_sortScheduled = false;
|
||||
sortedToplevels = computeSortedToplevels();
|
||||
const next = computeSortedToplevels();
|
||||
// Avoid reassigning (and invalidating bindings) when contents are equivalent.
|
||||
if (!_toplevelListEquivalent(next, sortedToplevels))
|
||||
sortedToplevels = next;
|
||||
_recomputeFrameBlocked();
|
||||
toplevelsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function _toplevelListEquivalent(a, b) {
|
||||
if (!a || !b || a.length !== b.length)
|
||||
return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const x = a[i];
|
||||
const y = b[i];
|
||||
if (x === y)
|
||||
continue;
|
||||
if (!x || !y)
|
||||
return false;
|
||||
// Only niri's enriched snapshots support value comparison.
|
||||
if (x.niriWindowId === undefined || y.niriWindowId === undefined)
|
||||
return false;
|
||||
if (x.niriWindowId !== y.niriWindowId || x.niriWorkspaceId !== y.niriWorkspaceId || x.appId !== y.appId || x.title !== y.title || !!x.activated !== !!y.activated || !!x.fullscreen !== !!y.fullscreen || !!x.maximized !== !!y.maximized || !!x.minimized !== !!y.minimized)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function scheduleSort() {
|
||||
if (_sortScheduled)
|
||||
return;
|
||||
@@ -164,8 +187,10 @@ Singleton {
|
||||
if (event.name === "workspace" || event.name === "workspacev2" || event.name === "focusedmon" || event.name === "focusedmonv2" || event.name === "activespecial")
|
||||
Hyprland.refreshMonitors();
|
||||
} catch (e) {}
|
||||
if (event.name === "activespecial")
|
||||
if (event.name === "activespecial") {
|
||||
root.updateHyprlandVisibleSpecialWorkspaces(event);
|
||||
root._recomputeFrameBlocked();
|
||||
}
|
||||
root.scheduleSort();
|
||||
}
|
||||
}
|
||||
@@ -175,6 +200,10 @@ Singleton {
|
||||
function onWindowsChanged() {
|
||||
root.scheduleSort();
|
||||
}
|
||||
// Workspace switches affect the fullscreen check's active workspace.
|
||||
function onAllWorkspacesChanged() {
|
||||
root._recomputeFrameBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -182,6 +211,7 @@ Singleton {
|
||||
detectCompositor();
|
||||
updateHyprlandVisibleSpecialWorkspaces(null);
|
||||
scheduleSort();
|
||||
_recomputeFrameBlocked();
|
||||
Qt.callLater(() => {
|
||||
NiriService.generateNiriLayoutConfig();
|
||||
HyprlandService.generateLayoutConfig();
|
||||
@@ -592,10 +622,74 @@ Singleton {
|
||||
return false;
|
||||
}
|
||||
|
||||
function connectedFrameBlockedOnScreen(screenOrName) {
|
||||
if (hasFullscreenToplevelOnScreen(screenOrName))
|
||||
// Per-screen cache for connectedFrameBlockedOnScreen to avoid recomputing on every consumer binding.
|
||||
property var frameBlockedByScreen: ({})
|
||||
|
||||
function _computeConnectedFrameBlocked(screenName) {
|
||||
if (hasFullscreenToplevelOnScreen(screenName))
|
||||
return true;
|
||||
return hyprlandSpecialWorkspaceBlocksConnectedFrame(screenOrName);
|
||||
return hyprlandSpecialWorkspaceBlocksConnectedFrame(screenName);
|
||||
}
|
||||
|
||||
function _recomputeFrameBlocked() {
|
||||
const screens = Quickshell.screens || [];
|
||||
const next = {};
|
||||
let changed = false;
|
||||
for (let i = 0; i < screens.length; i++) {
|
||||
const name = screens[i]?.name;
|
||||
if (!name)
|
||||
continue;
|
||||
const blocked = _computeConnectedFrameBlocked(name);
|
||||
next[name] = blocked;
|
||||
if (frameBlockedByScreen[name] !== blocked)
|
||||
changed = true;
|
||||
}
|
||||
if (!changed) {
|
||||
for (const name in frameBlockedByScreen) {
|
||||
if (!(name in next)) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
frameBlockedByScreen = next;
|
||||
}
|
||||
|
||||
function connectedFrameBlockedOnScreen(screenOrName) {
|
||||
const screenName = _screenName(screenOrName);
|
||||
if (!screenName)
|
||||
return false;
|
||||
const cached = frameBlockedByScreen[screenName];
|
||||
if (cached !== undefined)
|
||||
return cached;
|
||||
return _computeConnectedFrameBlocked(screenName);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ToplevelManager
|
||||
function onActiveToplevelChanged() {
|
||||
root._recomputeFrameBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
// Track active toplevel's fullscreen/activated state directly (no per-property signals from ToplevelManager).
|
||||
Connections {
|
||||
target: ToplevelManager.activeToplevel
|
||||
ignoreUnknownSignals: true
|
||||
function onFullscreenChanged() {
|
||||
root._recomputeFrameBlocked();
|
||||
}
|
||||
function onActivatedChanged() {
|
||||
root._recomputeFrameBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
root._recomputeFrameBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
function _screenForName(screenOrName) {
|
||||
|
||||
@@ -435,26 +435,31 @@ Singleton {
|
||||
function handleWindowFocusChanged(data) {
|
||||
const focusedWindowId = data.id;
|
||||
|
||||
// Only clone windows whose focus flag changes; skip reassignment if nothing changed.
|
||||
let focusedWindow = null;
|
||||
const updatedWindows = [];
|
||||
let changed = false;
|
||||
const updatedWindows = new Array(windows.length);
|
||||
|
||||
for (var i = 0; i < windows.length; i++) {
|
||||
const w = windows[i];
|
||||
const updatedWindow = {};
|
||||
|
||||
for (let prop in w) {
|
||||
updatedWindow[prop] = w[prop];
|
||||
const isFocused = w.id === focusedWindowId;
|
||||
if (!!w.is_focused === isFocused) {
|
||||
updatedWindows[i] = w;
|
||||
if (isFocused)
|
||||
focusedWindow = w;
|
||||
continue;
|
||||
}
|
||||
|
||||
updatedWindow.is_focused = (w.id === focusedWindowId);
|
||||
if (updatedWindow.is_focused) {
|
||||
const updatedWindow = Object.assign({}, w, {
|
||||
"is_focused": isFocused
|
||||
});
|
||||
if (isFocused)
|
||||
focusedWindow = updatedWindow;
|
||||
}
|
||||
|
||||
updatedWindows.push(updatedWindow);
|
||||
updatedWindows[i] = updatedWindow;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
windows = updatedWindows;
|
||||
if (changed)
|
||||
windows = updatedWindows;
|
||||
|
||||
if (focusedWindow) {
|
||||
const ws = root.workspaces[focusedWindow.workspace_id];
|
||||
@@ -490,26 +495,29 @@ Singleton {
|
||||
setWorkspaces(updatedWorkspaces);
|
||||
}
|
||||
|
||||
const updatedWindows = [];
|
||||
let changed = false;
|
||||
const updatedWindows = new Array(windows.length);
|
||||
|
||||
for (var i = 0; i < windows.length; i++) {
|
||||
const w = windows[i];
|
||||
const updatedWindow = {};
|
||||
|
||||
for (let prop in w) {
|
||||
updatedWindow[prop] = w[prop];
|
||||
}
|
||||
|
||||
let isFocused;
|
||||
if (data.active_window_id !== null && data.active_window_id !== undefined) {
|
||||
updatedWindow.is_focused = (w.id == data.active_window_id);
|
||||
isFocused = (w.id == data.active_window_id);
|
||||
} else {
|
||||
updatedWindow.is_focused = w.workspace_id == data.workspace_id ? false : w.is_focused;
|
||||
isFocused = w.workspace_id == data.workspace_id ? false : !!w.is_focused;
|
||||
}
|
||||
|
||||
updatedWindows.push(updatedWindow);
|
||||
if (!!w.is_focused === isFocused) {
|
||||
updatedWindows[i] = w;
|
||||
continue;
|
||||
}
|
||||
updatedWindows[i] = Object.assign({}, w, {
|
||||
"is_focused": isFocused
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
|
||||
windows = updatedWindows;
|
||||
if (changed)
|
||||
windows = updatedWindows;
|
||||
}
|
||||
|
||||
function handleWindowsChanged(data) {
|
||||
|
||||
@@ -58,6 +58,65 @@ Singleton {
|
||||
property string pendingThemeInstall: ""
|
||||
property string pendingPluginInstall: ""
|
||||
|
||||
// Deferred unload: keep popouts warm while the session is active and reclaim them on lock/monitors-off.
|
||||
property var _pendingUnloads: ({})
|
||||
|
||||
Connections {
|
||||
target: SessionService
|
||||
function onSessionLocked() {
|
||||
root._flushPendingUnloads();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleService
|
||||
function onMonitorsOffChanged() {
|
||||
if (IdleService.monitorsOff)
|
||||
root._flushPendingUnloads();
|
||||
}
|
||||
}
|
||||
|
||||
function _scheduleUnload(key) {
|
||||
_pendingUnloads[key] = true;
|
||||
}
|
||||
|
||||
function _flushPendingUnloads() {
|
||||
const keys = Object.keys(_pendingUnloads);
|
||||
_pendingUnloads = ({});
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const unload = _deferredUnloaders[keys[i]];
|
||||
if (unload)
|
||||
unload();
|
||||
}
|
||||
}
|
||||
|
||||
function _popoutStillPresented(popout) {
|
||||
return !!popout && (popout.shouldBeVisible === true || popout.isClosing === true);
|
||||
}
|
||||
|
||||
function _unloadPopoutNow(popoutName, loaderName) {
|
||||
const loader = root[loaderName];
|
||||
if (!loader)
|
||||
return;
|
||||
if (_popoutStillPresented(root[popoutName]))
|
||||
return;
|
||||
root[popoutName] = null;
|
||||
loader.active = false;
|
||||
}
|
||||
|
||||
readonly property var _deferredUnloaders: ({
|
||||
"controlCenter": () => _unloadPopoutNow("controlCenterPopout", "controlCenterLoader"),
|
||||
"notificationCenter": () => _unloadPopoutNow("notificationCenterPopout", "notificationCenterLoader"),
|
||||
"appDrawer": () => _unloadPopoutNow("appDrawerPopout", "appDrawerLoader"),
|
||||
"processList": () => _unloadPopoutNow("processListPopout", "processListPopoutLoader"),
|
||||
"battery": () => _unloadPopoutNow("batteryPopout", "batteryPopoutLoader"),
|
||||
"vpn": () => _unloadPopoutNow("vpnPopout", "vpnPopoutLoader"),
|
||||
"systemUpdate": () => _unloadPopoutNow("systemUpdatePopout", "systemUpdateLoader"),
|
||||
"layout": () => _unloadPopoutNow("layoutPopout", "layoutPopoutLoader"),
|
||||
"clipboardHistory": () => _unloadPopoutNow("clipboardHistoryPopout", "clipboardHistoryPopoutLoader"),
|
||||
"settings": () => _unloadSettingsNow()
|
||||
})
|
||||
|
||||
function setPosition(popout, x, y, width, section, screen) {
|
||||
if (popout && popout.setTriggerPosition && arguments.length >= 6) {
|
||||
popout.setTriggerPosition(x, y, width, section, screen);
|
||||
@@ -76,10 +135,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadControlCenter() {
|
||||
if (!controlCenterLoader)
|
||||
return;
|
||||
controlCenterPopout = null;
|
||||
controlCenterLoader.active = false;
|
||||
_scheduleUnload("controlCenter");
|
||||
}
|
||||
|
||||
function toggleControlCenter(x, y, width, section, screen) {
|
||||
@@ -101,10 +157,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadNotificationCenter() {
|
||||
if (!notificationCenterLoader)
|
||||
return;
|
||||
notificationCenterPopout = null;
|
||||
notificationCenterLoader.active = false;
|
||||
_scheduleUnload("notificationCenter");
|
||||
}
|
||||
|
||||
function toggleNotificationCenter(x, y, width, section, screen) {
|
||||
@@ -126,10 +179,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadAppDrawer() {
|
||||
if (!appDrawerLoader)
|
||||
return;
|
||||
appDrawerPopout = null;
|
||||
appDrawerLoader.active = false;
|
||||
_scheduleUnload("appDrawer");
|
||||
}
|
||||
|
||||
function toggleAppDrawer(x, y, width, section, screen) {
|
||||
@@ -151,10 +201,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadProcessListPopout() {
|
||||
if (!processListPopoutLoader)
|
||||
return;
|
||||
processListPopout = null;
|
||||
processListPopoutLoader.active = false;
|
||||
_scheduleUnload("processList");
|
||||
}
|
||||
|
||||
function toggleProcessList(x, y, width, section, screen) {
|
||||
@@ -268,10 +315,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadBattery() {
|
||||
if (!batteryPopoutLoader)
|
||||
return;
|
||||
batteryPopout = null;
|
||||
batteryPopoutLoader.active = false;
|
||||
_scheduleUnload("battery");
|
||||
}
|
||||
|
||||
function toggleBattery(x, y, width, section, screen) {
|
||||
@@ -293,10 +337,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadVpn() {
|
||||
if (!vpnPopoutLoader)
|
||||
return;
|
||||
vpnPopout = null;
|
||||
vpnPopoutLoader.active = false;
|
||||
_scheduleUnload("vpn");
|
||||
}
|
||||
|
||||
function toggleVpn(x, y, width, section, screen) {
|
||||
@@ -319,10 +360,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadSystemUpdate() {
|
||||
if (!systemUpdateLoader)
|
||||
return;
|
||||
systemUpdatePopout = null;
|
||||
systemUpdateLoader.active = false;
|
||||
_scheduleUnload("systemUpdate");
|
||||
}
|
||||
|
||||
function toggleSystemUpdate(x, y, width, section, screen) {
|
||||
@@ -441,10 +479,16 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadSettings() {
|
||||
if (settingsModalLoader) {
|
||||
settingsModal = null;
|
||||
settingsModalLoader.active = false;
|
||||
}
|
||||
_scheduleUnload("settings");
|
||||
}
|
||||
|
||||
function _unloadSettingsNow() {
|
||||
if (!settingsModalLoader)
|
||||
return;
|
||||
if (settingsModal && settingsModal.visible)
|
||||
return;
|
||||
settingsModal = null;
|
||||
settingsModalLoader.active = false;
|
||||
}
|
||||
|
||||
function _onSettingsModalLoaded() {
|
||||
@@ -484,17 +528,11 @@ Singleton {
|
||||
}
|
||||
|
||||
function unloadClipboardHistoryPopout() {
|
||||
if (!clipboardHistoryPopoutLoader)
|
||||
return;
|
||||
clipboardHistoryPopout = null;
|
||||
clipboardHistoryPopoutLoader.active = false;
|
||||
_scheduleUnload("clipboardHistory");
|
||||
}
|
||||
|
||||
function unloadLayoutPopout() {
|
||||
if (!layoutPopoutLoader)
|
||||
return;
|
||||
layoutPopout = null;
|
||||
layoutPopoutLoader.active = false;
|
||||
_scheduleUnload("layout");
|
||||
}
|
||||
|
||||
property bool _dankLauncherV2WantsOpen: false
|
||||
|
||||
@@ -134,12 +134,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onToplevelsChanged() {
|
||||
root._maybeResolveBackend();
|
||||
}
|
||||
}
|
||||
// Backend re-resolution on toplevel activity is covered by CompositorService.frameBlockedByScreen.
|
||||
|
||||
function _usesConnectedBackendForScreen(targetScreen) {
|
||||
return CompositorService.usesConnectedFrameChromeForScreen(targetScreen);
|
||||
|
||||
@@ -38,6 +38,7 @@ Item {
|
||||
property bool contentHandlesKeys: false
|
||||
property bool fullHeightSurface: false
|
||||
property bool _primeContent: false
|
||||
property bool _contentWarm: false
|
||||
property bool _resizeActive: false
|
||||
property real _chromeAnimTravelX: 1
|
||||
property real _chromeAnimTravelY: 1
|
||||
@@ -472,6 +473,7 @@ Item {
|
||||
isClosing = false;
|
||||
animationsEnabled = false;
|
||||
_primeContent = true;
|
||||
_contentWarm = true;
|
||||
_supersededClose = false;
|
||||
|
||||
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
|
||||
@@ -1334,7 +1336,8 @@ Item {
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
||||
// _contentWarm keeps the tree loaded across close for fast re-open; reclaimed by PopoutService on lock/idle.
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible || root._contentWarm
|
||||
asynchronous: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ Item {
|
||||
property bool contentHandlesKeys: false
|
||||
property bool fullHeightSurface: false
|
||||
property bool _primeContent: false
|
||||
property bool _contentWarm: false
|
||||
property bool _resizeActive: false
|
||||
property real _surfaceMarginLeft: 0
|
||||
property real _surfaceMarginTop: 0
|
||||
@@ -305,6 +306,7 @@ Item {
|
||||
isClosing = false;
|
||||
animationsEnabled = false;
|
||||
_primeContent = true;
|
||||
_contentWarm = true;
|
||||
|
||||
_frozenMaskX = maskX;
|
||||
_frozenMaskY = maskY;
|
||||
@@ -891,7 +893,8 @@ Item {
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
||||
// _contentWarm keeps the tree loaded across close for fast re-open; reclaimed by PopoutService on lock/idle.
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible || root._contentWarm
|
||||
asynchronous: false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user