From 4330c95216d79c9b466987b331d1be261712ba1c Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 17 Mar 2025 20:53:17 -0700 Subject: [PATCH 01/46] refactor TryInheritWindow(), making it safe to be called for both new and captured window --- .../Common/PersistentWindowProcessor.cs | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 797409c..223aa03 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1396,7 +1396,17 @@ namespace PersistentWindows.Common if (!monitorApplications.ContainsKey(display_key)) monitorApplications[display_key] = new Dictionary>(); + List app_list = null; + if (monitorApplications[display_key].ContainsKey(hwnd)) + { + app_list = monitorApplications[display_key][hwnd]; + monitorApplications[display_key].Remove(hwnd); + } monitorApplications[display_key][hwnd] = deadApps[display_key][kid]; + if (app_list != null) + { + monitorApplications[display_key][hwnd].AddRange(app_list); + } deadApps[display_key].Remove(kid); //replace prev zorder reference of dead_hwnd with hwnd in monitorApplication @@ -3041,6 +3051,33 @@ namespace PersistentWindows.Common return true; } + private bool TryInheritWindow(IntPtr hwnd, IntPtr realHwnd, IntPtr kid, ApplicationDisplayMetrics curDisplayMetrics) + { + if (kid == IntPtr.Zero) + { + ResolveWindowHandleCollision(hwnd); + } + else + { + var prevDisplayMetrics = InheritKilledWindow(hwnd, realHwnd, kid); + if (hwnd != kid) + { + if (prevDisplayMetrics.Title != curDisplayMetrics.Title) + Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title} with different title {curDisplayMetrics.Title}"); + else + Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title}"); + ResolveWindowHandleCollision(hwnd); + } + else + Log.Error($"Inherit position data from existing window 0x{kid.ToString("X")} for {curDisplayMetrics.Title}"); + + if (initialized && autoRestoreNewWindowToLastCapture) + return true; + } + + return false; + + } private bool IsWindowMoved(string displayKey, IntPtr hwnd, User32Events eventType, DateTime time, out ApplicationDisplayMetrics curDisplayMetrics, out ApplicationDisplayMetrics prevDisplayMetrics) { @@ -3137,28 +3174,7 @@ namespace PersistentWindows.Common return false; IntPtr kid = FindMatchingKilledWindow(hwnd); - bool restore_last = false; - if (kid == IntPtr.Zero) - { - ResolveWindowHandleCollision(hwnd); - } - else - { - prevDisplayMetrics = InheritKilledWindow(hwnd, realHwnd, kid); - if (hwnd != kid) - { - if (prevDisplayMetrics.Title != curDisplayMetrics.Title) - Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title} with different title {curDisplayMetrics.Title}"); - else - Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title}"); - ResolveWindowHandleCollision(hwnd); - } - else - Log.Error($"Inherit position data from existing window 0x{kid.ToString("X")} for {curDisplayMetrics.Title}"); - - if (initialized && autoRestoreNewWindowToLastCapture) - restore_last = true; - } + bool restore_last = TryInheritWindow(hwnd, realHwnd, kid, curDisplayMetrics); //newly created window or new display setting curDisplayMetrics.WindowId = (uint)realHwnd; From 05a740a21f1ac4125262085cdb227586262c8063 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Wed, 19 Mar 2025 15:35:44 -0700 Subject: [PATCH 02/46] unlock unknown display session when any window is moved/resized/minimize/unminimized --- .../Common/PersistentWindowProcessor.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 223aa03..46a978a 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2120,6 +2120,12 @@ namespace PersistentWindows.Common break; case User32Events.EVENT_SYSTEM_MOVESIZESTART: + if (freezeCapture) + { + Log.Event($"recognize {curDisplayKey} as user session"); + freezeCapture = false; //unlock unknown display session as normal + } + if ((User32.GetKeyState(0x11) & 0x8000) != 0 //ctrl key pressed && (User32.GetKeyState(0x10) & 0x8000) != 0) //shift key pressed { @@ -2133,6 +2139,12 @@ namespace PersistentWindows.Common lastUnminimizeWindow = hwnd; tidyTabWindows.Remove(hwnd); //no longer hidden by tidytab + if (freezeCapture) + { + Log.Event($"recognize {curDisplayKey} as user session"); + freezeCapture = false; //unlock unknown display session as normal + } + if (monitorApplications.ContainsKey(curDisplayKey) && monitorApplications[curDisplayKey].ContainsKey(hwnd)) { //treat unminimized window as foreground @@ -2161,6 +2173,12 @@ namespace PersistentWindows.Common if (enableMinimizeToTray) MinimizeToTray.Create(hwnd); + if (freezeCapture) + { + Log.Event($"recognize {curDisplayKey} as user session"); + freezeCapture = false; //unlock unknown display session as normal + } + goto case User32Events.EVENT_SYSTEM_MOVESIZEEND; case User32Events.EVENT_SYSTEM_MOVESIZEEND: if (eventType == User32Events.EVENT_SYSTEM_MOVESIZEEND) From 0adf762fa08135ab14b78ac5a77507caf91c7137 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 24 Mar 2025 10:35:28 -0700 Subject: [PATCH 03/46] fix wrong capture due to improper unfreeze --- .../Common/PersistentWindowProcessor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 46a978a..8653bf4 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2173,11 +2173,13 @@ namespace PersistentWindows.Common if (enableMinimizeToTray) MinimizeToTray.Create(hwnd); + /* if (freezeCapture) { Log.Event($"recognize {curDisplayKey} as user session"); freezeCapture = false; //unlock unknown display session as normal } + */ goto case User32Events.EVENT_SYSTEM_MOVESIZEEND; case User32Events.EVENT_SYSTEM_MOVESIZEEND: From 62452f3e0b80a35233ef3e870b2b341df253c302 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 14 Apr 2025 16:23:33 -0700 Subject: [PATCH 04/46] fix dead-loop in TrimQueue(), causing auto-restore to hang, possibly due to wrong inheritance --- .../Common/PersistentWindowProcessor.cs | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 8653bf4..73561c8 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2216,13 +2216,36 @@ namespace PersistentWindows.Common { while (monitorApplications[displayKey][hwnd].Count > MaxHistoryQueueLength) { - // limit length of capture history + // limit length of snapshot capture history + ulong acc_flags = 0; + for (int i = monitorApplications[displayKey][hwnd].Count - 1; i >= 0; --i) + { + ulong snapshot_flags = monitorApplications[displayKey][hwnd][i].SnapShotFlags; + if (snapshot_flags != 0) + { + if ((snapshot_flags | acc_flags) == acc_flags) + { + Log.Event($"trim redundant snapshot record for {windowTitle[hwnd]}"); + monitorApplications[displayKey][hwnd].RemoveAt(i); + break; + } + acc_flags |= snapshot_flags; + } + } + } + + while (monitorApplications[displayKey][hwnd].Count > MaxHistoryQueueLength) + { + // limit length of non-snapshot capture history for (int i = 0; i < monitorApplications[displayKey][hwnd].Count; ++i) { - if (monitorApplications[displayKey][hwnd][i].SnapShotFlags != 0) - continue; //preserve snapshot record + ulong snapshot_flags = monitorApplications[displayKey][hwnd][i].SnapShotFlags; + if (snapshot_flags != 0) + continue; + + Log.Event($"trim regular record for {windowTitle[hwnd]}"); monitorApplications[displayKey][hwnd].RemoveAt(i); - break; //remove one record at one time + break; //remove one record in each iteration } } } From de1b815d5712c26753bb7b9120e81d95f87c6c28 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 14 Apr 2025 21:20:21 -0700 Subject: [PATCH 05/46] fix dead-loop in TrimQueue() --- .../Common/PersistentWindowProcessor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 73561c8..91d8df1 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2214,7 +2214,7 @@ namespace PersistentWindows.Common private void TrimQueue(string displayKey, IntPtr hwnd) { - while (monitorApplications[displayKey][hwnd].Count > MaxHistoryQueueLength) + if (monitorApplications[displayKey][hwnd].Count > MaxHistoryQueueLength) { // limit length of snapshot capture history ulong acc_flags = 0; @@ -2227,7 +2227,6 @@ namespace PersistentWindows.Common { Log.Event($"trim redundant snapshot record for {windowTitle[hwnd]}"); monitorApplications[displayKey][hwnd].RemoveAt(i); - break; } acc_flags |= snapshot_flags; } From 3b5813bb35f1728399acd340854d625f93f960ba Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Fri, 18 Apr 2025 13:20:28 -0700 Subject: [PATCH 06/46] reduce event loggging of regular trimming of position history --- .../Common/PersistentWindowProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 91d8df1..ede5905 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2242,7 +2242,7 @@ namespace PersistentWindows.Common if (snapshot_flags != 0) continue; - Log.Event($"trim regular record for {windowTitle[hwnd]}"); + Log.Trace($"trim regular record for {windowTitle[hwnd]}"); monitorApplications[displayKey][hwnd].RemoveAt(i); break; //remove one record in each iteration } From fb5f538b88bd12d496509c2cba114279eacdf415 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Fri, 18 Apr 2025 14:29:29 -0700 Subject: [PATCH 07/46] improve re-entrance protection using lock --- .../Common/PersistentWindowProcessor.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index ede5905..0b2d31b 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -107,7 +107,7 @@ namespace PersistentWindows.Common public bool restoringFromDB = false; // manual restore from DB public bool autoInitialRestoreFromDB = false; public bool restoringSnapshot = false; // implies restoringFromMem - private bool restoringFullScreenWindow = false; + private Object restoringFullScreenWindow = new object(); public bool showDesktop = false; // show desktop when display changes public int fixZorder = 1; // 1 means restore z-order only for snapshot; 2 means restore z-order for all; 0 means no z-order restore at all public int fixZorderMethod = 5; // bit i represent restore method for pass i @@ -1690,6 +1690,7 @@ namespace PersistentWindows.Common //restore fullscreen window only applies if screen resolution has changed since minimize/normalize if (prevDisplayMetrics.CaptureTime < lastDisplayChangeTime) + lock(restoringFullScreenWindow) RestoreFullScreenWindow(hwnd, target_rect); return; } @@ -3565,11 +3566,6 @@ namespace PersistentWindows.Common */ } - if (restoringFullScreenWindow) - return; - - restoringFullScreenWindow = true; - bool wrong_screen = false; RECT cur_rect = new RECT(); User32.GetWindowRect(hwnd, ref cur_rect); @@ -3604,8 +3600,6 @@ namespace PersistentWindows.Common Thread.Sleep(3 * double_clck_interval); - restoringFullScreenWindow = false; - style = User32.GetWindowLong(hwnd, User32.GWL_STYLE); if ((style & (long)WindowStyleFlags.CAPTION) == 0L) { @@ -4376,6 +4370,7 @@ namespace PersistentWindows.Common if (restore_fullscreen) { if (restoreTimes > 0 && sWindow == null) //#246, let other windows restore first + lock(restoringFullScreenWindow) RestoreFullScreenWindow(hWnd, rect); } else if (restoreTimes >= MinRestoreTimes - 1) From e4be317e771cac6862ebe27733739d05b250cc09 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Fri, 25 Apr 2025 23:39:44 -0700 Subject: [PATCH 08/46] improve accuracy of matching killed window based on position similarity --- .../Common/PersistentWindowProcessor.cs | 31 ++++++++++++++++--- .../Common/WinApiBridge/WindowsPosition.cs | 5 +++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 0b2d31b..e09d164 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1470,7 +1470,11 @@ namespace PersistentWindows.Common { int title_match_cnt = 0; IntPtr title_match_hid = IntPtr.Zero; + int pos_match_cnt = 0; IntPtr pos_match_hid = IntPtr.Zero; + int similar_pos_cnt = 0; + int diff_size = 1000; + IntPtr similar_pos_hid = IntPtr.Zero; var deadAppPos = deadApps[curDisplayKey]; lock(captureLock) @@ -1501,15 +1505,32 @@ namespace PersistentWindows.Common title_match_hid = kid; ++title_match_cnt; } - else if (rect.Equals(r)) + + if (rect.Equals(r)) + { + pos_match_cnt++; pos_match_hid = kid; + } + + if (r.Diff(rect) < diff_size) + { + diff_size = r.Diff(rect); + similar_pos_cnt++; + similar_pos_hid = kid; + } } if (title_match_cnt == 1) return title_match_hid; - if (pos_match_hid != IntPtr.Zero) + if (pos_match_cnt == 1) return pos_match_hid; + + if (similar_pos_cnt == 1 || diff_size < 200) + { + Log.Event($"found similar match with pos diff of {diff_size}"); + return similar_pos_hid; + } } return IntPtr.Zero; @@ -2044,12 +2065,14 @@ namespace PersistentWindows.Common if (freezeCapture || !monitorApplications.ContainsKey(curDisplayKey)) return; + /* //try to inherit from killed window database if (FindMatchingKilledWindow(hwnd) != IntPtr.Zero) { - userMove = true; - StartCaptureTimer(UserMoveLatency / 2); } + */ + userMove = true; + StartCaptureTimer(UserMoveLatency * 4); } break; diff --git a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs index f028dee..949d34c 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs @@ -61,5 +61,10 @@ namespace PersistentWindows.Common.WinApiBridge { return string.Format("({0}, {1}), {2} x {3}", Left, Top, Width, Height); } + + public int Diff(RECT r) + { + return Math.Abs(Left - r.Left) + Math.Abs(Right - r.Right) + Math.Abs(Top - r.Top) + Math.Abs(Bottom - r.Bottom); + } } } From 2ba0a5395bcb4024701f869a899a8a198e95148d Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 26 Apr 2025 15:25:31 -0700 Subject: [PATCH 09/46] move inherit killed window out of IsWindowMoved() to CaptureWindow() --- .../Common/PersistentWindowProcessor.cs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index e09d164..8c2b176 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2696,7 +2696,18 @@ namespace PersistentWindows.Common if (new_window) { - monitorApplications[displayKey].Add(hWnd, new List()); + IntPtr kid = FindMatchingKilledWindow(hWnd); + bool restore_last = TryInheritWindow(hWnd, curDisplayMetrics.HWnd, kid, curDisplayMetrics); + if (restore_last && prevDisplayMetrics != null && !restoringFromDB && IsResizableWindow(hWnd)) + { + Log.Trace($"restore {windowTitle[hWnd]} to last captured position"); + restoringFromMem = true; + RestoreApplicationsOnCurrentDisplays(displayKey, hWnd, prevDisplayMetrics.CaptureTime); + restoringFromMem = false; + } + + if (kid == IntPtr.Zero) + monitorApplications[displayKey].Add(hWnd, new List()); } else { @@ -3239,9 +3250,6 @@ namespace PersistentWindows.Common if (noRestoreWindows.Contains(hwnd)) return false; - IntPtr kid = FindMatchingKilledWindow(hwnd); - bool restore_last = TryInheritWindow(hwnd, realHwnd, kid, curDisplayMetrics); - //newly created window or new display setting curDisplayMetrics.WindowId = (uint)realHwnd; @@ -3263,18 +3271,7 @@ namespace PersistentWindows.Common if (curDisplayMetrics.IsMinimized && prevDisplayMetrics != null && prevDisplayMetrics.IsMinimized) moved = false; else - { moved = true; - - if (restore_last && prevDisplayMetrics != null && !restoringFromDB && IsResizableWindow(hwnd)) - { - Log.Trace($"restore {windowTitle[hwnd]} to last captured position"); - restoringFromMem = true; - RestoreApplicationsOnCurrentDisplays(curDisplayKey, hwnd, prevDisplayMetrics.CaptureTime); - restoringFromMem = false; - return false; - } - } } else if (!monitorApplications[displayKey].ContainsKey(hwnd)) { From 769f35b681f6479325e3e5c189f7572019849e28 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 26 Apr 2025 18:37:18 -0700 Subject: [PATCH 10/46] debug print inherited window handle --- .../Common/PersistentWindowProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 8c2b176..1514345 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -3140,9 +3140,9 @@ namespace PersistentWindows.Common if (hwnd != kid) { if (prevDisplayMetrics.Title != curDisplayMetrics.Title) - Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title} with different title {curDisplayMetrics.Title}"); + Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title} with different title {curDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); else - Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title}"); + Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); ResolveWindowHandleCollision(hwnd); } else From e41caba22022fb83692de5a2209585cb696c4f94 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 26 Apr 2025 22:29:16 -0700 Subject: [PATCH 11/46] improve debug message --- .../Common/PersistentWindowProcessor.cs | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 1514345..7187d05 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1858,6 +1858,9 @@ namespace PersistentWindows.Common if (!initialized) return; + if (hwnd == IntPtr.Zero) + return; + { switch (eventType) { @@ -2674,22 +2677,6 @@ namespace PersistentWindows.Common ApplicationDisplayMetrics prevDisplayMetrics; if (IsWindowMoved(displayKey, hWnd, eventType, now, out curDisplayMetrics, out prevDisplayMetrics)) { - if (debugWindows.Contains(hWnd)) - { - string log = string.Format("Captured {0,-8} at {1} '{2}' fullscreen:{3} minimized:{4}", - curDisplayMetrics, - curDisplayMetrics.ScreenPosition.ToString(), - curDisplayMetrics.Title, - curDisplayMetrics.IsFullScreen, - curDisplayMetrics.IsMinimized - ); - Log.Event(log); - - string log2 = string.Format(" WindowPlacement.NormalPosition at {0}", - curDisplayMetrics.WindowPlacement.NormalPosition.ToString()); - Log.Event(log2); - } - bool new_window = !monitorApplications[displayKey].ContainsKey(hWnd); if (eventType != 0 || new_window) curDisplayMetrics.IsValid = true; @@ -2714,6 +2701,24 @@ namespace PersistentWindows.Common TrimQueue(displayKey, hWnd); } + if (debugWindows.Contains(hWnd)) + { + string log = string.Format("Captured {0} '{1}' fullscreen:{2} minimized:{3} visible:{4} at {5} {6, -8}", + curDisplayMetrics.HWnd.ToString("X"), + curDisplayMetrics.Title, + curDisplayMetrics.IsFullScreen, + curDisplayMetrics.IsMinimized, + !curDisplayMetrics.IsInvisible, + curDisplayMetrics.ScreenPosition.ToString(), + curDisplayMetrics + ); + Log.Event(log); + + string log2 = string.Format(" WindowPlacement.NormalPosition at {0}", + curDisplayMetrics.WindowPlacement.NormalPosition.ToString()); + Log.Event(log2); + } + monitorApplications[displayKey][hWnd].Add(curDisplayMetrics); ret = true; } @@ -3140,13 +3145,13 @@ namespace PersistentWindows.Common if (hwnd != kid) { if (prevDisplayMetrics.Title != curDisplayMetrics.Title) - Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title} with different title {curDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); + Log.Error($"{hwnd.ToString("X")} Inherit position data from killed window {prevDisplayMetrics.Title} with different title {curDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); else - Log.Error($"Inherit position data from killed window {prevDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); + Log.Error($"{hwnd.ToString("X")} Inherit position data from killed window {prevDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); ResolveWindowHandleCollision(hwnd); } else - Log.Error($"Inherit position data from existing window 0x{kid.ToString("X")} for {curDisplayMetrics.Title}"); + Log.Error($"{hwnd.ToString("X")} Inherit position data from existing window 0x{kid.ToString("X")} for {curDisplayMetrics.Title}"); if (initialized && autoRestoreNewWindowToLastCapture) return true; From 9224d621917971674e605ab6bf41f0becbf26aa0 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 26 Apr 2025 22:55:31 -0700 Subject: [PATCH 12/46] postpone capture to avoid wrong matching to killed window --- .../Common/PersistentWindowProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 7187d05..6c74083 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2683,6 +2683,10 @@ namespace PersistentWindows.Common if (new_window) { + //if (windowProcessName[hWnd] == "mstsc" && curDisplayMetrics.IsMinimized && curDisplayMetrics.IsInvisible && !curDisplayMetrics.IsFullScreen) + if (curDisplayMetrics.IsMinimized && curDisplayMetrics.IsInvisible && !curDisplayMetrics.IsFullScreen) + return false; //postpone capture till window is visible + IntPtr kid = FindMatchingKilledWindow(hWnd); bool restore_last = TryInheritWindow(hWnd, curDisplayMetrics.HWnd, kid, curDisplayMetrics); if (restore_last && prevDisplayMetrics != null && !restoringFromDB && IsResizableWindow(hWnd)) From d36bf0c56c8e0db6085b61283f5b24eabb7acd6c Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sun, 27 Apr 2025 14:56:03 -0700 Subject: [PATCH 13/46] reduce cpu load on non-window object --- .../Common/PersistentWindowProcessor.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 6c74083..11e79eb 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1861,6 +1861,10 @@ namespace PersistentWindows.Common if (hwnd == IntPtr.Zero) return; + if (idObject != 0) + // ignore non-window object (caret etc) + return; + { switch (eventType) { @@ -1882,12 +1886,6 @@ namespace PersistentWindows.Common if (eventType == User32Events.EVENT_OBJECT_DESTROY) { - if (idObject != 0) - { - // ignore non-window object (caret etc) - return; - } - noRestoreWindows.Remove(hwnd); debugWindows.Remove(hwnd); if (fullScreenGamingWindows.Contains(hwnd)) From 1a5d9442d2682c02363ff3d4d1e2929f80ef6329 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sun, 27 Apr 2025 21:14:09 -0700 Subject: [PATCH 14/46] relax class name and position match when inherit killed window --- .../Common/PersistentWindowProcessor.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 11e79eb..acacb9e 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -42,6 +42,8 @@ namespace PersistentWindows.Common private const int MaxHistoryQueueLength = 41; // ideally bigger than MaxSnapshots + 2 private const int PauseRestoreTaskbar = 3500; //cursor idle time before dragging taskbar + private const int MinClassNamePrefix = 8; //allow partial class name matching during inheritance + private const int MaxDiffPos = 1000; //allow matching to window of different position private bool initialized = false; @@ -1437,6 +1439,18 @@ namespace PersistentWindows.Common return r; } + int LenCommonPrefix(string a, string b) + { + int len = Math.Min(a.Length, b.Length); + int r; + for (r = 0; r < len; ++r) + { + if (a[r] != b[r]) + break; + } + return r; + } + private IntPtr FindMatchingKilledWindow(IntPtr hwnd) { if (!deadApps.ContainsKey(curDisplayKey)) @@ -1473,7 +1487,7 @@ namespace PersistentWindows.Common int pos_match_cnt = 0; IntPtr pos_match_hid = IntPtr.Zero; int similar_pos_cnt = 0; - int diff_size = 1000; + int diff_size = int.MaxValue; IntPtr similar_pos_hid = IntPtr.Zero; var deadAppPos = deadApps[curDisplayKey]; @@ -1482,11 +1496,17 @@ namespace PersistentWindows.Common { var appPos = deadAppPos[kid].Last(); - if (!className.Equals(appPos.ClassName)) - continue; if (!procName.Equals(appPos.ProcessName)) continue; + if (!className.Equals(appPos.ClassName)) + { + if (className.Length != appPos.ClassName.Length) + continue; + if (LenCommonPrefix(className, appPos.ClassName) < MinClassNamePrefix) + continue; + } + if (IsMinimized(hwnd) != appPos.IsMinimized) continue; if (User32.IsWindowVisible(hwnd) == appPos.IsInvisible) @@ -1526,7 +1546,7 @@ namespace PersistentWindows.Common if (pos_match_cnt == 1) return pos_match_hid; - if (similar_pos_cnt == 1 || diff_size < 200) + if (similar_pos_cnt == 1 || diff_size < MaxDiffPos) { Log.Event($"found similar match with pos diff of {diff_size}"); return similar_pos_hid; From 7dc0cbee82340d1b6bb43d51c9a9c812d245da7f Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sun, 27 Apr 2025 21:18:07 -0700 Subject: [PATCH 15/46] relax title match when inherit killed window --- .../Common/PersistentWindowProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index acacb9e..f074423 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1540,9 +1540,6 @@ namespace PersistentWindows.Common } } - if (title_match_cnt == 1) - return title_match_hid; - if (pos_match_cnt == 1) return pos_match_hid; @@ -1551,6 +1548,9 @@ namespace PersistentWindows.Common Log.Event($"found similar match with pos diff of {diff_size}"); return similar_pos_hid; } + + if (title_match_cnt == 1) + return title_match_hid; } return IntPtr.Zero; From 40f8f5cefde79c59d8575759531640371bb64ce7 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sun, 27 Apr 2025 22:11:42 -0700 Subject: [PATCH 16/46] delay activating webpage commander window when cursor is beam (user might be typing) --- Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs b/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs index be667c3..02471fe 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs @@ -907,7 +907,10 @@ namespace PersistentWindows.Common else if (hCursor == Cursors.IBeam.Handle) { Visible = false; - StartAliveTimer(11, 1000); + if (Math.Abs(cursorPos.X - lastCursorPos.X) < 3 && Math.Abs(cursorPos.Y - lastCursorPos.Y) < 3) + StartAliveTimer(11, 2000); + else + StartAliveTimer(11, 1000); return; } else if (hCursor == Cursors.Cross.Handle || handCursor) From 052cb05f53b2c4c67c308b81ebac22ae5290ddf2 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sun, 27 Apr 2025 22:51:01 -0700 Subject: [PATCH 17/46] refix delay activating webpage commander window when curosr is beam (user might be typing) --- .../Common/HotKeyWindow.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs b/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs index 02471fe..f4fd28a 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs @@ -37,6 +37,7 @@ namespace PersistentWindows.Common private static POINT lastCursorPos; private POINT lastWheelCursorPos; private bool handCursor = false; + private bool ibeamCursor = false; private int titleHeight; private Color dfltBackColor; private bool promptZkey = true; @@ -869,6 +870,7 @@ namespace PersistentWindows.Common } else if (Math.Abs(cursorPos.X - lastCursorPos.X) > 3 || Math.Abs(cursorPos.Y - lastCursorPos.Y) > 3) { + ibeamCursor = false; //mouse moving, continue monitor totalWaitSecondsForWhiteColor = 0; } @@ -906,15 +908,14 @@ namespace PersistentWindows.Common } else if (hCursor == Cursors.IBeam.Handle) { + ibeamCursor = true; Visible = false; - if (Math.Abs(cursorPos.X - lastCursorPos.X) < 3 && Math.Abs(cursorPos.Y - lastCursorPos.Y) < 3) - StartAliveTimer(11, 2000); - else - StartAliveTimer(11, 1000); + StartAliveTimer(11, 1000); return; } else if (hCursor == Cursors.Cross.Handle || handCursor) { + ibeamCursor = false; StartAliveTimer(7); return; } @@ -949,6 +950,14 @@ namespace PersistentWindows.Common regain_focus = false; } + if (ibeamCursor && hCursor == Cursors.Default.Handle) + //&& Math.Abs(cursorPos.X - lastCursorPos.X) < 3 && Math.Abs(cursorPos.Y - lastCursorPos.Y) < 3) + { + ibeamCursor = false; + StartAliveTimer(11, 1000); + return; + } + // let tiny hotkey window follow cursor position ResetHotKeyVirtualDesktop(); ResetHotkeyWindowPos(); @@ -971,6 +980,7 @@ namespace PersistentWindows.Common // hand cursor shape if (!handCursor) { + ibeamCursor = false; handCursor = true; Left -= 10; } From 8c5d6475ad0d5390d67d6764bff02e5ce4582764 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 10:58:58 -0700 Subject: [PATCH 18/46] fix broken -auto_restore_new_window_to_last_capture --- .../Common/PersistentWindowProcessor.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index f074423..d186d08 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2706,14 +2706,7 @@ namespace PersistentWindows.Common return false; //postpone capture till window is visible IntPtr kid = FindMatchingKilledWindow(hWnd); - bool restore_last = TryInheritWindow(hWnd, curDisplayMetrics.HWnd, kid, curDisplayMetrics); - if (restore_last && prevDisplayMetrics != null && !restoringFromDB && IsResizableWindow(hWnd)) - { - Log.Trace($"restore {windowTitle[hWnd]} to last captured position"); - restoringFromMem = true; - RestoreApplicationsOnCurrentDisplays(displayKey, hWnd, prevDisplayMetrics.CaptureTime); - restoringFromMem = false; - } + TryInheritWindow(hWnd, curDisplayMetrics.HWnd, kid, curDisplayMetrics); if (kid == IntPtr.Zero) monitorApplications[displayKey].Add(hWnd, new List()); @@ -3176,7 +3169,16 @@ namespace PersistentWindows.Common Log.Error($"{hwnd.ToString("X")} Inherit position data from existing window 0x{kid.ToString("X")} for {curDisplayMetrics.Title}"); if (initialized && autoRestoreNewWindowToLastCapture) - return true; + { + if (!restoringFromDB && IsResizableWindow(hwnd)) + { + Log.Trace($"restore {windowTitle[hwnd]} to last captured position"); + restoringFromMem = true; + RestoreApplicationsOnCurrentDisplays(curDisplayKey, hwnd, prevDisplayMetrics.CaptureTime); + restoringFromMem = false; + } + } + return true; } return false; From cce0f7f52d269d41779c762f1fa6602cf987c3d6 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 13:50:30 -0700 Subject: [PATCH 19/46] refix #369, #388, #392: improve algorithm matching new window to killed one. Add command option -pos_match_threshold, using 40 as default value --- .../Common/PersistentWindowProcessor.cs | 41 ++++++++++++++++--- .../Common/WinApiBridge/WindowsPosition.cs | 3 +- .../SystrayShell/Program.cs | 10 +++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index d186d08..b232f09 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -43,7 +43,7 @@ namespace PersistentWindows.Common private const int PauseRestoreTaskbar = 3500; //cursor idle time before dragging taskbar private const int MinClassNamePrefix = 8; //allow partial class name matching during inheritance - private const int MaxDiffPos = 1000; //allow matching to window of different position + public int MaxDiffPos = 40; //allow matching window of slightly different position private bool initialized = false; @@ -1521,8 +1521,7 @@ namespace PersistentWindows.Common if (title.Equals(appPos.Title)) { - if (title_match_cnt == 0) - title_match_hid = kid; + title_match_hid = kid; ++title_match_cnt; } @@ -1543,14 +1542,46 @@ namespace PersistentWindows.Common if (pos_match_cnt == 1) return pos_match_hid; - if (similar_pos_cnt == 1 || diff_size < MaxDiffPos) + if (diff_size <= MaxDiffPos) { - Log.Event($"found similar match with pos diff of {diff_size}"); + Log.Event($"matching window with position diff of {diff_size}"); return similar_pos_hid; } + /* if (title_match_cnt == 1) + { + Log.Event($"matching window with same title"); return title_match_hid; + } + */ + if (!monitorApplications.ContainsKey(curDisplayKey)) + return IntPtr.Zero; + + int proc_name_match_cnt = 0; + int class_name_match_cnt = 0; + foreach(var h in monitorApplications[curDisplayKey].Keys) + { + foreach (var dm in monitorApplications[curDisplayKey][h]) + { + if (dm.ProcessName == procName) + proc_name_match_cnt++; + if (dm.ClassName == className) + class_name_match_cnt++; + break; + } + } + + //force match if hwnd is the first live window of the app + if (proc_name_match_cnt == 0) + return similar_pos_hid; + + if (proc_name_match_cnt == 1 && class_name_match_cnt == 0) + return similar_pos_hid; + + //force match if hwnd-like window has multiple instantiations but has only one top-level matching candidate + if (similar_pos_cnt == 1 && class_name_match_cnt > 0) + return similar_pos_hid; } return IntPtr.Zero; diff --git a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs index 949d34c..521b285 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs @@ -64,7 +64,8 @@ namespace PersistentWindows.Common.WinApiBridge public int Diff(RECT r) { - return Math.Abs(Left - r.Left) + Math.Abs(Right - r.Right) + Math.Abs(Top - r.Top) + Math.Abs(Bottom - r.Bottom); + int diff = Math.Abs(Left - r.Left) + Math.Abs(Right - r.Right) + Math.Abs(Top - r.Top) + Math.Abs(Bottom - r.Bottom); + return diff / 4; } } } diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs index 25b6755..71a5bd5 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs @@ -74,6 +74,7 @@ if not errorlevel 1 goto wait_to_finish"; bool offscreen_fix = true; bool fix_unminimized_window = true; bool enhanced_offscreen_fix = false; + bool set_pos_match_threshold = false; bool auto_restore_missing_windows = false; bool auto_restore_from_db_at_startup = false; bool auto_restore_last_capture_at_startup = false; @@ -143,6 +144,12 @@ if not errorlevel 1 goto wait_to_finish"; restore_snapshot = SnapshotCharToId(arg[0]); continue; } + else if (set_pos_match_threshold) + { + set_pos_match_threshold = false; + pwp.MaxDiffPos = int.Parse(arg); + continue; + } switch(arg) { @@ -266,6 +273,9 @@ if not errorlevel 1 goto wait_to_finish"; case "-auto_restore_new_window_to_last_capture=1": pwp.autoRestoreNewWindowToLastCapture = true; break; + case "-pos_match_threshold": + set_pos_match_threshold = true; + break; case "-auto_restore_missing_windows": case "-auto_restore_missing_windows=1": auto_restore_missing_windows = true; From f34fffa5747315f382332ee40f7d340658f6010f Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 14:10:41 -0700 Subject: [PATCH 20/46] disable auto restore from db if system is up more than 5 min --- .../Common/PersistentWindowProcessor.cs | 3 +++ .../Common/WinApiBridge/User32.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index b232f09..8bc1107 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1082,6 +1082,9 @@ namespace PersistentWindows.Common } else if (db_exist && autoRestoreLiveWindowsFromDb) { + var ticks = Kernel32.GetTickCount64(); + if (ticks > 600000) //system up 5min + return true; Log.Event("auto restore from db"); restoringFromDB = true; autoInitialRestoreFromDB = true; diff --git a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/User32.cs b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/User32.cs index c9c0555..b2e453d 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/User32.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/User32.cs @@ -624,6 +624,9 @@ namespace PersistentWindows.Common.WinApiBridge [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CloseHandle(IntPtr hHandle); + [DllImport("kernel32")] + public static extern UInt64 GetTickCount64(); + [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess( ProcessAccessFlags processAccess, From 8771c2edd7bb2bbd221e8f31d1ddcc07c5d83461 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 14:14:31 -0700 Subject: [PATCH 21/46] disable auto restore from db/xml at pw start if system is up more than 5 min --- .../Common/PersistentWindowProcessor.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 8bc1107..bfed266 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1070,6 +1070,10 @@ namespace PersistentWindows.Common normalSessions.Add(item); } + var ticks = Kernel32.GetTickCount64(); + if (ticks > 600000) //system up 5min + return true; + if (db_exist && auto_restore_from_db) { restoringFromDB = true; @@ -1082,9 +1086,6 @@ namespace PersistentWindows.Common } else if (db_exist && autoRestoreLiveWindowsFromDb) { - var ticks = Kernel32.GetTickCount64(); - if (ticks > 600000) //system up 5min - return true; Log.Event("auto restore from db"); restoringFromDB = true; autoInitialRestoreFromDB = true; From f7a1f9234388ac4ce6941a2804a7771d33bc6023 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 14:16:45 -0700 Subject: [PATCH 22/46] fix wrong tick value for 5 min --- .../Common/PersistentWindowProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index bfed266..56c6b87 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1071,7 +1071,7 @@ namespace PersistentWindows.Common } var ticks = Kernel32.GetTickCount64(); - if (ticks > 600000) //system up 5min + if (ticks > 300000) //system up 5min return true; if (db_exist && auto_restore_from_db) From 8da7d9b4bc53d8a13b09144ca622bf97cf6add05 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 14:24:18 -0700 Subject: [PATCH 23/46] turn on -auto_restore_new_window_to_last_capture again --- .../Common/PersistentWindowProcessor.cs | 2 +- Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 56c6b87..20b9297 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -122,7 +122,7 @@ namespace PersistentWindows.Common public bool fixUnminimizedWindow = true; public bool autoRestoreMissingWindows = false; public bool autoRestoreLiveWindowsFromDb = true; //for new display session, autorestore live windows using data from db (without resurrecting dead one) - public bool autoRestoreNewWindowToLastCapture = false; + public bool autoRestoreNewWindowToLastCapture = true; public bool launchOncePerProcessId = true; private int restoreTimes = 0; //multiple passes need to fully restore private Object restoreLock = new object(); diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs index 71a5bd5..37705b2 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs @@ -270,8 +270,8 @@ if not errorlevel 1 goto wait_to_finish"; case "-auto_restore_existing_window_to_last_capture=1": auto_restore_last_capture_at_startup = true; break; - case "-auto_restore_new_window_to_last_capture=1": - pwp.autoRestoreNewWindowToLastCapture = true; + case "-auto_restore_new_window_to_last_capture=0": + pwp.autoRestoreNewWindowToLastCapture = false; break; case "-pos_match_threshold": set_pos_match_threshold = true; From 2d695d11bff9155a724dfd17577649b943122b25 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 14:53:26 -0700 Subject: [PATCH 24/46] tag 5.63, update help for command line option change --- Help.md | 3 ++- .../SystrayShell/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Help.md b/Help.md index 0932846..67410e3 100644 --- a/Help.md +++ b/Help.md @@ -25,7 +25,8 @@ | -fix_unminimized_window=0 | Turn off auto restore of unminimized windows. Use this switch to avoid undesirable window shifting during window activation, which comes with Event id 9999 : "restore minimized window ...." in event viewer. |-auto_restore_new_display_session_from_db=0| Disable window restore from DB upon PC startup or switching display for the first time |-auto_restore_existing_window_to_last_capture=1 | Turn on auto restore existing window from last capture upon PW start - |-auto_restore_new_window_to_last_capture=1 | Turn on auto restore new window to last killed position + |-auto_restore_new_window_to_last_capture=0 | Turn off auto restore new window to last killed position + |-pos_match_threshold 80 | Auto correct new window position to last killed position within range of 80 pixels, default is 40 | ‑auto_restore_missing_windows=1 | When restoring from disk, restore missing windows without prompting the user | ‑auto_restore_missing_windows=2 | At startup, automatically restore missing windows from disk. The user will be prompted before restoring each missing window | ‑auto_restore_missing_windows=3 | At startup, automatically restore missing windows from disk without prompting the user diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs index 72165d4..a465d6e 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.62.*")] +[assembly: AssemblyVersion("5.63.*")] From 5f666b20d14c4fe1264ad863747e4f19db631283 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 28 Apr 2025 16:36:47 -0700 Subject: [PATCH 25/46] avoid wrong matching --- .../Common/PersistentWindowProcessor.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 20b9297..3634e4f 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1568,10 +1568,16 @@ namespace PersistentWindows.Common { foreach (var dm in monitorApplications[curDisplayKey][h]) { + if (IsMinimized(h) != dm.IsMinimized) + continue; + if (User32.IsWindowVisible(h) == dm.IsInvisible) + continue; if (dm.ProcessName == procName) + { proc_name_match_cnt++; - if (dm.ClassName == className) - class_name_match_cnt++; + if (dm.ClassName == className) + class_name_match_cnt++; + } break; } } From f66dc3afd8f1a2c024e58ed7620d56dc87be970c Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Wed, 30 Apr 2025 19:47:32 -0700 Subject: [PATCH 26/46] match new window with killed one in LIFO style --- .../Common/PersistentWindowProcessor.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 3634e4f..1eb5531 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1493,6 +1493,8 @@ namespace PersistentWindows.Common int similar_pos_cnt = 0; int diff_size = int.MaxValue; IntPtr similar_pos_hid = IntPtr.Zero; + DateTime last_killed_time = new DateTime(0); + IntPtr last_killed_hid = IntPtr.Zero; var deadAppPos = deadApps[curDisplayKey]; lock(captureLock) @@ -1541,6 +1543,12 @@ namespace PersistentWindows.Common similar_pos_cnt++; similar_pos_hid = kid; } + + if (appPos.CaptureTime > last_killed_time) + { + last_killed_time = appPos.CaptureTime; + last_killed_hid = kid; + } } if (pos_match_cnt == 1) @@ -1564,6 +1572,7 @@ namespace PersistentWindows.Common int proc_name_match_cnt = 0; int class_name_match_cnt = 0; + int class_name_mismatch_cnt = 0; foreach(var h in monitorApplications[curDisplayKey].Keys) { foreach (var dm in monitorApplications[curDisplayKey][h]) @@ -1577,21 +1586,27 @@ namespace PersistentWindows.Common proc_name_match_cnt++; if (dm.ClassName == className) class_name_match_cnt++; + else + class_name_mismatch_cnt++; } break; } } - //force match if hwnd is the first live window of the app + //force match last killed pos if hwnd is the first live window of the app if (proc_name_match_cnt == 0) - return similar_pos_hid; + return last_killed_hid; + //force match most closest pos if hwnd is the first sub window of the app if (proc_name_match_cnt == 1 && class_name_match_cnt == 0) return similar_pos_hid; //force match if hwnd-like window has multiple instantiations but has only one top-level matching candidate if (similar_pos_cnt == 1 && class_name_match_cnt > 0) return similar_pos_hid; + + if (class_name_match_cnt > 0 && class_name_mismatch_cnt == 0) + return last_killed_hid; } return IntPtr.Zero; From f05d569387b0366b8e3a8649a8c4c099e54cfa32 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 09:57:36 -0700 Subject: [PATCH 27/46] #400, fix wrong mismatch of dialog of different style --- .../Common/Models/ApplicationDisplayMetrics.cs | 2 ++ .../Common/PersistentWindowProcessor.cs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/Models/ApplicationDisplayMetrics.cs b/Ninjacrab.PersistentWindows.Solution/Common/Models/ApplicationDisplayMetrics.cs index a18acd2..1c4acc4 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/Models/ApplicationDisplayMetrics.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/Models/ApplicationDisplayMetrics.cs @@ -26,6 +26,8 @@ namespace PersistentWindows.Common.Models public bool IsFullScreen { get; set; } public bool IsMinimized { get; set; } public bool IsInvisible { get; set; } + public long Style { get; set; } + public long ExtStyle { get; set; } // for restore window position to display session end time public DateTime CaptureTime { get; set; } diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 1eb5531..afaa950 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1496,6 +1496,9 @@ namespace PersistentWindows.Common DateTime last_killed_time = new DateTime(0); IntPtr last_killed_hid = IntPtr.Zero; + long style = User32.GetWindowLong(hwnd, User32.GWL_STYLE); + long ext_style = User32.GetWindowLong(hwnd, User32.GWL_EXSTYLE); + var deadAppPos = deadApps[curDisplayKey]; lock(captureLock) foreach (var kid in deadAppPos.Keys) @@ -1505,6 +1508,11 @@ namespace PersistentWindows.Common if (!procName.Equals(appPos.ProcessName)) continue; + if (appPos.Style != 0 && style != appPos.Style) + continue; + if (appPos.ExtStyle != 0 && ext_style != appPos.ExtStyle) + continue; + if (!className.Equals(appPos.ClassName)) { if (className.Length != appPos.ClassName.Length) @@ -1513,6 +1521,7 @@ namespace PersistentWindows.Common continue; } + if (IsMinimized(hwnd) != appPos.IsMinimized) continue; if (User32.IsWindowVisible(hwnd) == appPos.IsInvisible) @@ -3319,6 +3328,9 @@ namespace PersistentWindows.Common NeedUpdateWindowPlacement = false, ScreenPosition = screenPosition, + Style = User32.GetWindowLong(hwnd, User32.GWL_STYLE), + ExtStyle = User32.GetWindowLong(hwnd, User32.GWL_EXSTYLE), + IsTopMost = IsWindowTopMost(hwnd), NeedClearTopMost = false, From 5d26d86e2831b18d72531e0c05f8bfe02836eccb Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 11:16:42 -0700 Subject: [PATCH 28/46] speedup auto-restore-new-window, reduce killed window entry by wild matching non-resizable window which will not be restored --- .../Common/PersistentWindowProcessor.cs | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index afaa950..4c79016 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1486,8 +1486,6 @@ namespace PersistentWindows.Common if (!string.IsNullOrEmpty(className)) { - int title_match_cnt = 0; - IntPtr title_match_hid = IntPtr.Zero; int pos_match_cnt = 0; IntPtr pos_match_hid = IntPtr.Zero; int similar_pos_cnt = 0; @@ -1521,7 +1519,6 @@ namespace PersistentWindows.Common continue; } - if (IsMinimized(hwnd) != appPos.IsMinimized) continue; if (User32.IsWindowVisible(hwnd) == appPos.IsInvisible) @@ -1534,12 +1531,6 @@ namespace PersistentWindows.Common if (rect.Equals(r) && title.Equals(appPos.Title)) return kid; - if (title.Equals(appPos.Title)) - { - title_match_hid = kid; - ++title_match_cnt; - } - if (rect.Equals(r)) { pos_match_cnt++; @@ -1569,13 +1560,9 @@ namespace PersistentWindows.Common return similar_pos_hid; } - /* - if (title_match_cnt == 1) - { - Log.Event($"matching window with same title"); - return title_match_hid; - } - */ + if (!IsResizableWindow(hwnd)) + return similar_pos_hid; + if (!monitorApplications.ContainsKey(curDisplayKey)) return IntPtr.Zero; @@ -1586,9 +1573,9 @@ namespace PersistentWindows.Common { foreach (var dm in monitorApplications[curDisplayKey][h]) { - if (IsMinimized(h) != dm.IsMinimized) + if (style != dm.Style && dm.Style != 0) continue; - if (User32.IsWindowVisible(h) == dm.IsInvisible) + if (ext_style != dm.ExtStyle && dm.ExtStyle != 0) continue; if (dm.ProcessName == procName) { @@ -2141,24 +2128,14 @@ namespace PersistentWindows.Common { case User32Events.EVENT_OBJECT_CREATE: { - if (idObject != 0) - // ignore non-window object (caret etc) - return; - if (restoringFromDB) return; if (freezeCapture || !monitorApplications.ContainsKey(curDisplayKey)) return; - /* - //try to inherit from killed window database - if (FindMatchingKilledWindow(hwnd) != IntPtr.Zero) - { - } - */ userMove = true; - StartCaptureTimer(UserMoveLatency * 4); + StartCaptureTimer(UserMoveLatency / 2); } break; @@ -3241,6 +3218,8 @@ namespace PersistentWindows.Common restoringFromMem = true; RestoreApplicationsOnCurrentDisplays(curDisplayKey, hwnd, prevDisplayMetrics.CaptureTime); restoringFromMem = false; + userMove = true; + StartCaptureTimer(UserMoveLatency / 2); } } return true; From 9661f8bd798495c23611043ee1193a1c7b3ea103 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 11:30:27 -0700 Subject: [PATCH 29/46] undo wild match for non-resizable window --- .../Common/PersistentWindowProcessor.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 4c79016..2f15a7b 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1560,9 +1560,6 @@ namespace PersistentWindows.Common return similar_pos_hid; } - if (!IsResizableWindow(hwnd)) - return similar_pos_hid; - if (!monitorApplications.ContainsKey(curDisplayKey)) return IntPtr.Zero; From 304c654e80efa2eb35a6b17c35b43b10a86a8017 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 14:55:44 -0700 Subject: [PATCH 30/46] inherit window in stack order (last kill, first inherit) --- .../Common/PersistentWindowProcessor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 2f15a7b..ccf7e96 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1984,7 +1984,9 @@ namespace PersistentWindows.Common } // for matching new window with killed one - monitorApplications[display_config][hwnd].Last().ProcessName = windowProcessName[hwnd]; + var dm = monitorApplications[display_config][hwnd].Last(); + if (dm.SnapShotFlags == 0) + dm.CaptureTime = DateTime.Now; //for inheritence in LIFO stile deadApps[display_config][hwnd] = monitorApplications[display_config][hwnd]; From e7b141e095ee4e2f7cd39130701055a3f73f4c4a Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 15:01:17 -0700 Subject: [PATCH 31/46] further speedup auto-restore-new-window --- .../Common/PersistentWindowProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index ccf7e96..196105f 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2134,7 +2134,7 @@ namespace PersistentWindows.Common return; userMove = true; - StartCaptureTimer(UserMoveLatency / 2); + StartCaptureTimer(UserMoveLatency / 4); } break; From 1fd82f35c8abd3f624d468bda7dcaee97a7999a5 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 15:42:07 -0700 Subject: [PATCH 32/46] avoid trigger batch restore in restore single window mode --- .../Common/PersistentWindowProcessor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 196105f..baf2f32 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -106,6 +106,7 @@ namespace PersistentWindows.Common private Timer restoreTimer; private Timer restoreFinishedTimer; public bool restoringFromMem = false; // automatic restore from memory or snapshot + private bool restoreSingleWindow = false; public bool restoringFromDB = false; // manual restore from DB public bool autoInitialRestoreFromDB = false; public bool restoringSnapshot = false; // implies restoringFromMem @@ -2113,7 +2114,7 @@ namespace PersistentWindows.Common if (eventType == User32Events.EVENT_OBJECT_LOCATIONCHANGE) { - if ((remoteSession || restoreTimes >= MinRestoreTimes) && !restoringSnapshot) + if (((remoteSession && !restoreSingleWindow) || restoreTimes >= MinRestoreTimes) && !restoringSnapshot) { // restore is not finished as long as window location keeps changing CancelRestoreFinishedTimer(); @@ -3214,8 +3215,10 @@ namespace PersistentWindows.Common if (!restoringFromDB && IsResizableWindow(hwnd)) { Log.Trace($"restore {windowTitle[hwnd]} to last captured position"); + restoreSingleWindow = true; restoringFromMem = true; RestoreApplicationsOnCurrentDisplays(curDisplayKey, hwnd, prevDisplayMetrics.CaptureTime); + restoreSingleWindow = false; restoringFromMem = false; userMove = true; StartCaptureTimer(UserMoveLatency / 2); From 4998a104ea1e37b2aec448b54237afbaf8aded5e Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 15:46:41 -0700 Subject: [PATCH 33/46] tag 5.64 --- .../SystrayShell/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs index a465d6e..7b0bef2 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.63.*")] +[assembly: AssemblyVersion("5.64.*")] From 9e85ee58109e1d0320a86883a85cc70b324bf9c4 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 17:10:45 -0700 Subject: [PATCH 34/46] #369, fix crash on windows 11 when launching webpage --- .../SystrayShell/SystrayForm.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/SystrayForm.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/SystrayForm.cs index e7bd829..28c2a48 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/SystrayForm.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/SystrayForm.cs @@ -252,7 +252,16 @@ namespace PersistentWindows.SystrayShell if (!upgradeDownloaded.ContainsKey(latestVersion)) { - Process.Start(Program.ProjectUrl + "/releases"); + string url = Program.ProjectUrl + "/releases"; + var os_version = Environment.OSVersion; + if (os_version.Version.Major < 10) + Process.Start(url); + else if (os_version.Version.Build < 22000) + Process.Start(url); + /* windows 11 + else + Process.Start(new ProcessStartInfo(url)); + */ var src_file = $"{Program.ProjectUrl}/releases/download/{latestVersion}/{System.Windows.Forms.Application.ProductName}{latestVersion}.zip"; var dst_file = $"{Program.AppdataFolder}/upgrade.zip"; From 2c06fe1684950d30a3ca3a6bbe4d9a82dc311f84 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 1 May 2025 23:43:38 -0700 Subject: [PATCH 35/46] =?UTF-8?q?#388=EF=BC=8C=20fix=20mismatch=20killed?= =?UTF-8?q?=20window=20for=20jEdit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/PersistentWindowProcessor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index baf2f32..08a63b5 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1571,10 +1571,11 @@ namespace PersistentWindows.Common { foreach (var dm in monitorApplications[curDisplayKey][h]) { - if (style != dm.Style && dm.Style != 0) + if (IsMinimized(hwnd) != dm.IsMinimized) continue; - if (ext_style != dm.ExtStyle && dm.ExtStyle != 0) + if (User32.IsWindowVisible(hwnd) == dm.IsInvisible) continue; + if (dm.ProcessName == procName) { proc_name_match_cnt++; From e8f06c1368519e328952866daa0c485918566a11 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 3 May 2025 16:15:24 -0700 Subject: [PATCH 36/46] no need to write xml when restore finished, as it is done in session end --- .../Common/PersistentWindowProcessor.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 08a63b5..805b477 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -769,8 +769,6 @@ namespace PersistentWindows.Common Log.Event("Restore finished in pass {0} with {1} windows recovered for display setting {2}", restorePass, numWindowRestored, curDisplayKey); sessionActive = true; - WriteDataDump(); - if (!wasRestoringSnapshot && !wasRestoringFromDB) { if (!snapshotTakenTime.ContainsKey(curDisplayKey)) @@ -865,6 +863,7 @@ namespace PersistentWindows.Common (s, e) => { process.PriorityClass = ProcessPriorityClass.High; + EndDisplaySession(); WriteDataDump(); Log.Event("Session ending"); }; @@ -873,6 +872,9 @@ namespace PersistentWindows.Common this.displaySettingsChangingHandler = (s, e) => { + if (fastRestore) + process.PriorityClass = ProcessPriorityClass.High; + if (!freezeCapture) { lastDisplayChangeTime = DateTime.Now; @@ -895,9 +897,6 @@ namespace PersistentWindows.Common this.displaySettingsChangedHandler = (s, e) => { - if (fastRestore) - process.PriorityClass = ProcessPriorityClass.High; - string displayKey = GetDisplayKey(); Log.Event("Display settings changed {0}", displayKey); From 4df563f1bb45ae3418862e8a365865fdd8bd090c Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 3 May 2025 16:33:47 -0700 Subject: [PATCH 37/46] speedup PW startup restore by setting set process priority to high during the first 10 seconds of PW starting --- .../SystrayShell/Program.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs index 37705b2..909c6d8 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs @@ -48,6 +48,15 @@ if not errorlevel 1 goto wait_to_finish"; [STAThread] static void Main(string[] args) { + var process = Process.GetCurrentProcess(); + var process_priority = process.PriorityClass; + process.PriorityClass = ProcessPriorityClass.High; + var timer = new System.Threading.Timer(state => + { + process.PriorityClass = process_priority; + }); + timer.Change(10000, System.Threading.Timeout.Infinite); + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); From cad1ec393fe5475d3d4f8c02a1e409cbf3ec7d95 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 3 May 2025 17:24:44 -0700 Subject: [PATCH 38/46] restore process priority 10 seconds after startup --- .../Common/PersistentWindowProcessor.cs | 4 +--- .../SystrayShell/Program.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 805b477..8760539 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -161,7 +161,7 @@ namespace PersistentWindows.Common private static Dictionary windowProcessName = new Dictionary(); private Process process; - private ProcessPriorityClass processPriority; + public ProcessPriorityClass processPriority; private string appDataFolder; public bool redirectAppDataFolder = false; @@ -594,8 +594,6 @@ namespace PersistentWindows.Common public bool Start(bool auto_restore_from_db, bool auto_restore_last_capture_at_startup) { process = Process.GetCurrentProcess(); - processPriority = process.PriorityClass; - string productName = System.Windows.Forms.Application.ProductName; appDataFolder = redirectAppDataFolder ? "." : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), productName); diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs index 909c6d8..73168a9 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs @@ -48,15 +48,6 @@ if not errorlevel 1 goto wait_to_finish"; [STAThread] static void Main(string[] args) { - var process = Process.GetCurrentProcess(); - var process_priority = process.PriorityClass; - process.PriorityClass = ProcessPriorityClass.High; - var timer = new System.Threading.Timer(state => - { - process.PriorityClass = process_priority; - }); - timer.Change(10000, System.Threading.Timeout.Infinite); - Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); @@ -65,6 +56,15 @@ if not errorlevel 1 goto wait_to_finish"; pwp = new PersistentWindowProcessor(); + var process = Process.GetCurrentProcess(); + pwp.processPriority = process.PriorityClass; + process.PriorityClass = ProcessPriorityClass.High; + var timer = new System.Threading.Timer(state => + { + process.PriorityClass = pwp.processPriority; + }); + timer.Change(10000, System.Threading.Timeout.Infinite); + bool splash = true; int delay_restart = 0; int relaunch_delay = 0; From f7eb0abe432a744bb83bd269b6f9110a393feb5b Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 3 May 2025 22:24:20 -0700 Subject: [PATCH 39/46] speedup inheritance in case window create event is delayed --- .../Common/PersistentWindowProcessor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 8760539..c53cb6b 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -34,7 +34,7 @@ namespace PersistentWindows.Common public int UserForcedRestoreLatency = 0; private const int CaptureLatency = 3000; // delay in milliseconds from window OS move to capture private const int UserMoveLatency = 1000; // delay in milliseconds from user move/minimize/unminimize/maximize to capture, must < CaptureLatency - private const int ForegroundTimerLatency = UserMoveLatency / 5; + private const int ForegroundTimerLatency = UserMoveLatency / 4; private const int MaxUserMoves = 4; // max user window moves per capture cycle private const int MinWindowOsMoveEvents = 12; // threshold of window move events initiated by OS per capture cycle private const int MaxSnapshots = 38; // 0-9, a-z, ` (for redo last auto restore) and final one for undo last snapshot restore @@ -483,6 +483,10 @@ namespace PersistentWindows.Common } else if (fullScreenGamingWindow == IntPtr.Zero) { + //create window event may be delayed + if (hwnd != IntPtr.Zero) + CaptureWindow(hwnd, 0, DateTime.Now, curDisplayKey); + StartCaptureTimer(); //speed up initial capture From 11d0c30ddbb6b0d1b2e7008b723a71e2d93208bb Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 3 May 2025 23:12:28 -0700 Subject: [PATCH 40/46] disable double restore full screen window due to invisible window issue --- .../Common/PersistentWindowProcessor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index c53cb6b..216855a 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -3698,6 +3698,7 @@ namespace PersistentWindows.Common 0, 0, 0, UIntPtr.Zero); Log.Error("restore full screen window {0}", GetWindowTitle(hwnd)); + /* Thread.Sleep(3 * double_clck_interval); style = User32.GetWindowLong(hwnd, User32.GWL_STYLE); @@ -3722,6 +3723,7 @@ namespace PersistentWindows.Common } Log.Error("fail to restore full screen window {0}", GetWindowTitle(hwnd)); + */ } private void RestoreSnapWindow(IntPtr hwnd, RECT target_pos) From 3884c4446fd5d330369262e5fef9347375df3012 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 3 May 2025 23:23:51 -0700 Subject: [PATCH 41/46] no fast capture if window is marked no restore --- .../Common/PersistentWindowProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 216855a..cfed54d 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -484,7 +484,7 @@ namespace PersistentWindows.Common else if (fullScreenGamingWindow == IntPtr.Zero) { //create window event may be delayed - if (hwnd != IntPtr.Zero) + if (hwnd != IntPtr.Zero && !noRestoreWindows.Contains(hwnd)) CaptureWindow(hwnd, 0, DateTime.Now, curDisplayKey); StartCaptureTimer(); From 3d2ed7395c47cf367b0aa37aaa0b8c25a87f649e Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sat, 3 May 2025 23:26:00 -0700 Subject: [PATCH 42/46] tag 5.65 --- .../SystrayShell/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs index 7b0bef2..03b8722 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.64.*")] +[assembly: AssemblyVersion("5.65.*")] From 3a473bdc109e0d1f1acfbf8a3ed63bc70e896b25 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 5 May 2025 17:49:23 -0700 Subject: [PATCH 43/46] [dual pos switch] use first diff pos as background match --- .../Common/PersistentWindowProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index cfed54d..7ea75c3 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -2648,6 +2648,14 @@ namespace PersistentWindows.Common continue; } + if (!toForeground) + { + RECT screenPosition = new RECT(); + User32.GetWindowRect(hwnd, ref screenPosition); + if (screenPosition.Equals(metrics.ScreenPosition)) + continue; + } + IntPtr prevZwnd = metrics.PrevZorderWindow; if (prevZwnd != front_hwnd && (prevZwnd == IntPtr.Zero || prevZwnd != firstBackgroundWindow)) { From cd29b292820b588ce3fb64b120c272089bc70c36 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 5 May 2025 18:09:34 -0700 Subject: [PATCH 44/46] simplify DPS code --- .../Common/PersistentWindowProcessor.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 7ea75c3..7ebe6cf 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -522,12 +522,12 @@ namespace PersistentWindows.Common if (!ctrl_key_pressed && !alt_key_pressed) { //restore window to previous background position - SwitchForeBackground(hwnd, secondBackGround:shift_key_pressed); + SwitchForeBackground(hwnd); } else if (ctrl_key_pressed && !alt_key_pressed) { //restore to previous background zorder with current size/pos - SwitchForeBackground(hwnd, strict_dps_check: false, updateBackgroundPos: true, secondBackGround:shift_key_pressed); + SwitchForeBackground(hwnd, strict_dps_check: false, updateBackgroundPos: true); } else if (!ctrl_key_pressed && alt_key_pressed && !shift_key_pressed) { @@ -2613,7 +2613,7 @@ namespace PersistentWindows.Common Log.Event("Bring foreground window {0} to bottom", GetWindowTitle(hwnd)); } - public void SwitchForeBackground(IntPtr hwnd, bool strict_dps_check = true, bool toForeground=false, bool updateBackgroundPos=false, bool secondBackGround = false) + public void SwitchForeBackground(IntPtr hwnd, bool strict_dps_check = true, bool toForeground=false, bool updateBackgroundPos=false) { if (hwnd == IntPtr.Zero || IsTaskBar(hwnd)) return; @@ -2638,8 +2638,6 @@ namespace PersistentWindows.Common if (toForeground && IsTaskBar(front_hwnd)) return; //already foreground - IntPtr firstBackgroundWindow = IntPtr.Zero; - for (; prevIndex >= 0; --prevIndex) { var metrics = monitorApplications[curDisplayKey][hwnd][prevIndex]; @@ -2657,7 +2655,7 @@ namespace PersistentWindows.Common } IntPtr prevZwnd = metrics.PrevZorderWindow; - if (prevZwnd != front_hwnd && (prevZwnd == IntPtr.Zero || prevZwnd != firstBackgroundWindow)) + if (prevZwnd != front_hwnd) { if (toForeground) { @@ -2666,12 +2664,6 @@ namespace PersistentWindows.Common } else { - if (secondBackGround) - { - firstBackgroundWindow = prevZwnd; - secondBackGround = false; - continue; - } if (IsTaskBar(front_hwnd) && IsTaskBar(prevZwnd)) return; //#266, ignore taskbar (as prev-zwindow) change due to window maximize From 6745982d60f6e0ade9fa1552f988f264e098bd57 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Mon, 5 May 2025 18:12:11 -0700 Subject: [PATCH 45/46] simplify help on DPS, remove Shift + click desktop window --- Help.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Help.md b/Help.md index 67410e3..e7d93ad 100644 --- a/Help.md +++ b/Help.md @@ -60,7 +60,6 @@ * Dual Position Switching functionality: * Click the desktop window to bring the foreground window to its previous background position and Z-order. - * Shift + Click the desktop window to bring the foreground window to its *second* last background position. This is useful if the previous background position is mis-captured due to invoking start menu or other popups. * Ctrl + Click the desktop window to bring the foreground window to its previous Z-order while keeping the current location and size. * To cancel Dual Position Switching: From ae8da1683433ad5ec6a08e892ee02006044bfbd4 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Thu, 8 May 2025 13:43:36 -0700 Subject: [PATCH 46/46] #403, fix crash due to empty history record --- .../Common/PersistentWindowProcessor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs index 7ebe6cf..fbafe35 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/PersistentWindowProcessor.cs @@ -1503,7 +1503,9 @@ namespace PersistentWindows.Common lock(captureLock) foreach (var kid in deadAppPos.Keys) { - var appPos = deadAppPos[kid].Last(); + var appPos = deadAppPos[kid].LastOrDefault(); + if (appPos == null) + continue; if (!procName.Equals(appPos.ProcessName)) continue;