diff --git a/Help.md b/Help.md index 932aa24..e7d93ad 100644 --- a/Help.md +++ b/Help.md @@ -24,8 +24,9 @@ | -fix_offscreen_window=0 | Turn off auto correction of off-screen windows | -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=0 | Turn off auto restore upon PW start + |-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=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 @@ -59,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: diff --git a/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs b/Ninjacrab.PersistentWindows.Solution/Common/HotKeyWindow.cs index be667c3..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,12 +908,14 @@ namespace PersistentWindows.Common } else if (hCursor == Cursors.IBeam.Handle) { + ibeamCursor = true; Visible = false; StartAliveTimer(11, 1000); return; } else if (hCursor == Cursors.Cross.Handle || handCursor) { + ibeamCursor = false; StartAliveTimer(7); return; } @@ -946,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(); @@ -968,6 +980,7 @@ namespace PersistentWindows.Common // hand cursor shape if (!handCursor) { + ibeamCursor = false; handCursor = true; Left -= 10; } 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 6e5e96f..fbafe35 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 @@ -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 + public int MaxDiffPos = 40; //allow matching window of slightly different position private bool initialized = false; @@ -104,10 +106,11 @@ 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 - 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 @@ -158,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; @@ -480,6 +483,10 @@ namespace PersistentWindows.Common } else if (fullScreenGamingWindow == IntPtr.Zero) { + //create window event may be delayed + if (hwnd != IntPtr.Zero && !noRestoreWindows.Contains(hwnd)) + CaptureWindow(hwnd, 0, DateTime.Now, curDisplayKey); + StartCaptureTimer(); //speed up initial capture @@ -515,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) { @@ -591,8 +598,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); @@ -766,8 +771,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)) @@ -862,6 +865,7 @@ namespace PersistentWindows.Common (s, e) => { process.PriorityClass = ProcessPriorityClass.High; + EndDisplaySession(); WriteDataDump(); Log.Event("Session ending"); }; @@ -870,6 +874,9 @@ namespace PersistentWindows.Common this.displaySettingsChangingHandler = (s, e) => { + if (fastRestore) + process.PriorityClass = ProcessPriorityClass.High; + if (!freezeCapture) { lastDisplayChangeTime = DateTime.Now; @@ -892,9 +899,6 @@ namespace PersistentWindows.Common this.displaySettingsChangedHandler = (s, e) => { - if (fastRestore) - process.PriorityClass = ProcessPriorityClass.High; - string displayKey = GetDisplayKey(); Log.Event("Display settings changed {0}", displayKey); @@ -1068,6 +1072,10 @@ namespace PersistentWindows.Common normalSessions.Add(item); } + var ticks = Kernel32.GetTickCount64(); + if (ticks > 300000) //system up 5min + return true; + if (db_exist && auto_restore_from_db) { restoringFromDB = true; @@ -1396,7 +1404,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 @@ -1427,6 +1445,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)) @@ -1458,22 +1488,41 @@ namespace PersistentWindows.Common if (!string.IsNullOrEmpty(className)) { - IntPtr dflt_kid = IntPtr.Zero; - 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 = int.MaxValue; + IntPtr similar_pos_hid = IntPtr.Zero; + 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) { - var appPos = deadAppPos[kid].Last(); - - if (!className.Equals(appPos.ClassName)) + var appPos = deadAppPos[kid].LastOrDefault(); + if (appPos == null) continue; + 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) + continue; + if (LenCommonPrefix(className, appPos.ClassName) < MinClassNamePrefix) + continue; + } + if (IsMinimized(hwnd) != appPos.IsMinimized) continue; if (User32.IsWindowVisible(hwnd) == appPos.IsInvisible) @@ -1486,26 +1535,76 @@ namespace PersistentWindows.Common if (rect.Equals(r) && title.Equals(appPos.Title)) return kid; - if (title.Equals(appPos.Title)) + if (rect.Equals(r)) { - if (title_match_cnt == 0) - title_match_hid = kid; - ++title_match_cnt; - } - else if (rect.Equals(r)) + pos_match_cnt++; pos_match_hid = kid; - else if (dflt_kid == IntPtr.Zero) - dflt_kid = kid; + } + + if (r.Diff(rect) < diff_size) + { + diff_size = r.Diff(rect); + similar_pos_cnt++; + similar_pos_hid = kid; + } + + if (appPos.CaptureTime > last_killed_time) + { + last_killed_time = appPos.CaptureTime; + last_killed_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 (dflt_kid != IntPtr.Zero) - return dflt_kid; + if (diff_size <= MaxDiffPos) + { + Log.Event($"matching window with position diff of {diff_size}"); + return similar_pos_hid; + } + + if (!monitorApplications.ContainsKey(curDisplayKey)) + return IntPtr.Zero; + + 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]) + { + if (IsMinimized(hwnd) != dm.IsMinimized) + continue; + if (User32.IsWindowVisible(hwnd) == dm.IsInvisible) + continue; + + if (dm.ProcessName == procName) + { + proc_name_match_cnt++; + if (dm.ClassName == className) + class_name_match_cnt++; + else + class_name_mismatch_cnt++; + } + break; + } + } + + //force match last killed pos if hwnd is the first live window of the app + if (proc_name_match_cnt == 0) + 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; @@ -1686,6 +1785,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; } @@ -1832,6 +1932,13 @@ namespace PersistentWindows.Common if (!initialized) return; + if (hwnd == IntPtr.Zero) + return; + + if (idObject != 0) + // ignore non-window object (caret etc) + return; + { switch (eventType) { @@ -1853,12 +1960,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)) @@ -1888,7 +1989,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]; @@ -1944,6 +2047,9 @@ namespace PersistentWindows.Common return; } + if (!CaptureProcessName(hwnd)) + return; + if (ignoreProcess.Count > 0) { string processName = windowProcessName[hwnd]; @@ -1951,9 +2057,6 @@ namespace PersistentWindows.Common return; } - if (!CaptureProcessName(hwnd)) - return; - #if DEBUG if (title.Contains("Microsoft Visual Studio") && (eventType == User32Events.EVENT_OBJECT_LOCATIONCHANGE @@ -2015,7 +2118,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(); @@ -2029,22 +2132,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 / 2); - } + userMove = true; + StartCaptureTimer(UserMoveLatency / 4); } break; @@ -2099,6 +2194,9 @@ namespace PersistentWindows.Common if (fullScreenGamingWindow != IntPtr.Zero) return; + if (User32.IsZoomed(hwnd)) + userMove = true; + if (foreGroundWindow == hwnd) { StartCaptureTimer(UserMoveLatency); @@ -2113,6 +2211,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 { @@ -2126,6 +2230,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 @@ -2154,6 +2264,14 @@ 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) @@ -2187,15 +2305,37 @@ namespace PersistentWindows.Common private void TrimQueue(string displayKey, IntPtr hwnd) { + if (monitorApplications[displayKey][hwnd].Count > MaxHistoryQueueLength) + { + // 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); + } + acc_flags |= snapshot_flags; + } + } + } + while (monitorApplications[displayKey][hwnd].Count > MaxHistoryQueueLength) { - // limit length of capture history + // 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.Trace($"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 } } } @@ -2475,7 +2615,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; @@ -2500,8 +2640,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]; @@ -2510,8 +2648,16 @@ 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)) + if (prevZwnd != front_hwnd) { if (toForeground) { @@ -2520,12 +2666,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 @@ -2601,35 +2741,45 @@ 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; if (new_window) { - monitorApplications[displayKey].Add(hWnd, new List()); + //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); + TryInheritWindow(hWnd, curDisplayMetrics.HWnd, kid, curDisplayMetrics); + + if (kid == IntPtr.Zero) + monitorApplications[displayKey].Add(hWnd, new List()); } else { 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; } @@ -3044,6 +3194,46 @@ 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($"{hwnd.ToString("X")} Inherit position data from killed window {prevDisplayMetrics.Title} with different title {curDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); + else + Log.Error($"{hwnd.ToString("X")} Inherit position data from killed window {prevDisplayMetrics.Title} {prevDisplayMetrics.HWnd.ToString("X")}"); + ResolveWindowHandleCollision(hwnd); + } + else + Log.Error($"{hwnd.ToString("X")} Inherit position data from existing window 0x{kid.ToString("X")} for {curDisplayMetrics.Title}"); + + if (initialized && autoRestoreNewWindowToLastCapture) + { + 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); + } + } + return true; + } + + return false; + + } private bool IsWindowMoved(string displayKey, IntPtr hwnd, User32Events eventType, DateTime time, out ApplicationDisplayMetrics curDisplayMetrics, out ApplicationDisplayMetrics prevDisplayMetrics) { @@ -3123,6 +3313,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, @@ -3139,27 +3332,6 @@ namespace PersistentWindows.Common if (noRestoreWindows.Contains(hwnd)) 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) - { - Log.Error($"Inherit position data from killed window 0x{kid.ToString("X")} for {curDisplayMetrics.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; - } - //newly created window or new display setting curDisplayMetrics.WindowId = (uint)realHwnd; @@ -3181,18 +3353,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)) { @@ -3507,11 +3668,6 @@ namespace PersistentWindows.Common */ } - if (restoringFullScreenWindow) - return; - - restoringFullScreenWindow = true; - bool wrong_screen = false; RECT cur_rect = new RECT(); User32.GetWindowRect(hwnd, ref cur_rect); @@ -3544,10 +3700,9 @@ namespace PersistentWindows.Common 0, 0, 0, UIntPtr.Zero); Log.Error("restore full screen window {0}", GetWindowTitle(hwnd)); + /* Thread.Sleep(3 * double_clck_interval); - restoringFullScreenWindow = false; - style = User32.GetWindowLong(hwnd, User32.GWL_STYLE); if ((style & (long)WindowStyleFlags.CAPTION) == 0L) { @@ -3570,6 +3725,7 @@ namespace PersistentWindows.Common } Log.Error("fail to restore full screen window {0}", GetWindowTitle(hwnd)); + */ } private void RestoreSnapWindow(IntPtr hwnd, RECT target_pos) @@ -4318,6 +4474,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) 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, diff --git a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs index f028dee..521b285 100644 --- a/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs +++ b/Ninjacrab.PersistentWindows.Solution/Common/WinApiBridge/WindowsPosition.cs @@ -61,5 +61,11 @@ namespace PersistentWindows.Common.WinApiBridge { return string.Format("({0}, {1}), {2} x {3}", Left, Top, Width, Height); } + + public int Diff(RECT r) + { + 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 76c9668..73168a9 100644 --- a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs +++ b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Program.cs @@ -56,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; @@ -74,9 +83,10 @@ 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 = true; + bool auto_restore_last_capture_at_startup = false; bool launch_once_per_process_id = true; bool check_upgrade = true; bool auto_upgrade = false; @@ -143,6 +153,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) { @@ -260,12 +276,15 @@ if not errorlevel 1 goto wait_to_finish"; case "-redraw_desktop": redraw_desktop = true; break; - case "-auto_restore_existing_window_to_last_capture=0": - auto_restore_last_capture_at_startup = false; + 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=0": pwp.autoRestoreNewWindowToLastCapture = false; 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; diff --git a/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs b/Ninjacrab.PersistentWindows.Solution/SystrayShell/Properties/AssemblyInfo.cs index e9b2c16..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.61.*")] +[assembly: AssemblyVersion("5.65.*")] 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";