From b4ce687ba4bf08bc0be7791cef3ad4ae8bbdd2f4 Mon Sep 17 00:00:00 2001 From: Anshul Singh Date: Tue, 23 Jun 2026 18:31:06 +0530 Subject: [PATCH] Add live window position backup for crash recovery Periodically saves current window positions to disk so they can be restored even after unexpected shutdowns (battery drain, force poweroff). - New config option: liveBackupInterval (Int, 0-3600, default 30s) - Timer fires every N seconds, snapshots all open valid windows - Data stored in separate settings key (rememberwindowpositions_liveBackups) - On startup, live backup data is used as fallback when no proper close-saved data exists for an app - Timestamp comparison ensures newer live backups take precedence over older normal saves (e.g. when an app was reopened and rearranged after a previous close) --- src/contents/config/main.xml | 5 ++ src/contents/ui/config.ui | 86 +++++++++++++++++++-- src/contents/ui/main.qml | 144 +++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 7 deletions(-) diff --git a/src/contents/config/main.xml b/src/contents/config/main.xml index 37b3a15..1659a30 100644 --- a/src/contents/config/main.xml +++ b/src/contents/config/main.xml @@ -169,5 +169,10 @@ waterfox-default false + + 30 + 0 + 3600 + diff --git a/src/contents/ui/config.ui b/src/contents/ui/config.ui index a202d43..98e9046 100644 --- a/src/contents/ui/config.ui +++ b/src/contents/ui/config.ui @@ -1183,13 +1183,85 @@ When upgrading from an earlier version, a reboot might be required to enable the - - - - Only save during shutdown (on script closure) - enable this if you experience lag when using the script - this could be caused by using a hard disk drive (HDD) as your /home drive - - - + + + + Only save during shutdown (on script closure) - enable this if you experience lag when using the script - this could be caused by using a hard disk drive (HDD) as your /home drive + + + + + + + Live Window Position Backup + + + + + + Periodically saves the current positions of all open windows to disk. If the system shuts down unexpectedly (battery runs out, power button forced shutdown), window positions can still be restored from the last backup. Set to 0 to disable. Higher values reduce disk writes but increase the risk of losing recent window changes. + + + true + + + + + + + 0 + + + 3600 + + + 30 + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::TicksBelow + + + 5 + + + + + + + + + 0 (off) + + + + + + + seconds between backups + + + Qt::AlignmentFlag::AlignCenter + + + + + + + 3600 + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + diff --git a/src/contents/ui/main.qml b/src/contents/ui/main.qml index 689ed90..0a801d8 100644 --- a/src/contents/ui/main.qml +++ b/src/contents/ui/main.qml @@ -187,6 +187,7 @@ Item { printApplicationNameToLog: KWin.readConfig("printApplicationNameToLog", true), printMonitorInfoToLog: KWin.readConfig("printMonitorInfoToLog", false), onlySaveOnShutdown: KWin.readConfig("onlySaveOnShutdown", false), + liveBackupInterval: KWin.readConfig("liveBackupInterval", 30), // confidence confidence: [ { caption: 100, matchingDimentions: 2, allowHeightShrinking: false }, // Caption and size match @@ -1358,6 +1359,14 @@ Item { } } + Timer { + id: liveBackupTimer + + repeat: true + running: false + onTriggered: saveLiveBackup() + } + function clearSessionRestoreSaves() { while (sessionRestoreSaves.length > 0) { if (sessionRestoreSaves[0].closeTime < Date.now() - config.sessionRestoreTime * 1000) { @@ -1594,6 +1603,74 @@ Item { } } + logE('Loaded ' + Object.keys(convertedWindows).length + ' apps from settings'); + + let liveBackups = JSON.parse(settings.rememberwindowpositions_liveBackups); + let liveCount = 0; + for (let key in liveBackups) { + let liveBackupIsNewer = liveBackups[key].l && (!convertedWindows[key] || liveBackups[key].l >= convertedWindows[key].lastAccessTime); + if (!convertedWindows[key] || convertedWindows[key].saved.length === 0 || liveBackupIsNewer) { + let live = liveBackups[key]; + if (convertedWindows[key] && convertedWindows[key].saved.length > 0 && liveBackupIsNewer) { + logE('Live backup is newer than saved data for: ' + key + ' - using live backup'); + } else { + logE('Using live backup for: ' + key + ' windowCountLastSession: ' + live.w); + } + convertedWindows[key] = { + saved : [], + lastAccessTime : live.l || Date.now(), + windowCountLastSession : live.w, + windowCount : 0, + instantMatchRestored : 0, + restoredTotal : 0, + loading : [], + closed : [] + }; + for (let i = 0; i < live.s.length; i++) { + let save = live.s[i]; + convertedWindows[key].saved.push({ + caption : save.c, + x : save.x, + y : save.y, + width : save.w, + height : save.h, + minimized : save.m == 1, + keepAbove : save.k == 1, + keepBelow : save.b == 1, + stackingOrder : save.s, + desktopNumber : save.d, + activities : save.a, + rememberAlways : save.r == 1, + singleWindow : save.n == 1, + position : save.p ? { + x : save.p.x, + y : save.p.y, + serialNumber : save.p.s, + name : save.p.n + } : undefined, + sessionRestore : save.z == 1, + alreadyMatched : false, + tile : save.t ? { + quick : save.t.q == 1, + x : save.t.x, + y : save.t.y, + width : save.t.w, + height : save.t.h, + left : save.t.l, + right : save.t.r, + top : save.t.t, + bottom : save.t.b + } : undefined, + mouseTilerAuto : save.o + }); + } + liveCount++; + } + } + if (liveCount > 0) { + logE('Loaded ' + liveCount + ' apps from live backup'); + } + //log('Load - converted windows: ' + JSON.stringify(convertedWindows)); config.windows = convertedWindows; } @@ -1669,6 +1746,66 @@ Item { log('Windows saved!'); } + function saveLiveBackup() { + if (config.liveBackupInterval <= 0) return; + let liveData = {}; + const clients = Workspace.stackingOrder; + for (let i = 0; i < clients.length; i++) { + let client = clients[i]; + if (!isValidWindow(client)) continue; + let currentConfig = getCurrentConfig(client); + if (currentConfig.blocked || currentConfig.rememberNever) continue; + + let name = client.resourceClass; + if (!liveData[name]) { + liveData[name] = { + s: [], + l: Date.now(), + w: 0 + }; + } + liveData[name].w++; + + let convertedPosition = client.output.mapFromGlobal(client.pos); + let tileData = convertTileData(client); + liveData[name].s.push({ + c: client.caption.toString(), + x: client.x, + y: client.y, + w: client.width, + h: client.height, + m: client.minimized ? 1 : 0, + k: client.keepAbove ? 1 : 0, + b: client.keepBelow ? 1 : 0, + s: i, + d: client.onAllDesktops ? -1 : client.desktops[0].x11DesktopNumber, + a: [...client.activities], + r: 0, + n: 0, + p: { + x: convertedPosition.x, + y: convertedPosition.y, + s: client.output.serialNumber, + n: client.output.name + }, + z: 0, + t: tileData ? { + q: tileData.quick ? 1 : 0, + x: tileData.x, + y: tileData.y, + w: tileData.width, + h: tileData.height, + l: tileData.left, + r: tileData.right, + t: tileData.top, + b: tileData.bottom + } : undefined, + o: client.mt_autoRestore ? client.mt_autoRestore : 0 + }); + } + settings.rememberwindowpositions_liveBackups = JSON.stringify(liveData); + } + function addDefaultOverrides() { // settings.rememberwindowpositions_currentDefaultOverrideCount = 0; const defaultOverrides = [ @@ -1842,6 +1979,7 @@ Item { id: settings property string rememberwindowpositions_windows: "{}" property string rememberwindowpositions_configOverrides: "{}" + property string rememberwindowpositions_liveBackups: "{}" property int rememberwindowpositions_currentDefaultOverrideCount: 0 // property bool rememberwindowpositions_autoShowMainMenu: true } @@ -1884,6 +2022,11 @@ Item { // Clear expired apps to reduce used save-file space clearExpiredApps(); + if (config.liveBackupInterval > 0) { + liveBackupTimer.interval = config.liveBackupInterval * 1000; + liveBackupTimer.start(); + } + // if (settings.rememberwindowpositions_autoShowMainMenu) { // showMainMenu(); // } @@ -1893,6 +2036,7 @@ Item { log('Closing...'); updateSessionRestoreSaves(); saveWindowsToSettings(true); + saveLiveBackup(); saveOverridesToSettings(); }