From 43781dcb5eebc166c15f7dd9d0c5d20977998216 Mon Sep 17 00:00:00 2001 From: Kang Yu Date: Sun, 1 Mar 2020 15:59:51 -0800 Subject: [PATCH] identify OS move by using counter; Capture user snap window using timer --- .../Models/ApplicationDisplayMetrics.cs | 1 + .../PersistentWindowProcessor.cs | 105 +++++++++++------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/Models/ApplicationDisplayMetrics.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/Models/ApplicationDisplayMetrics.cs index 915a9cc..1a8a7e1 100644 --- a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/Models/ApplicationDisplayMetrics.cs +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/Models/ApplicationDisplayMetrics.cs @@ -8,6 +8,7 @@ namespace Ninjacrab.PersistentWindows.Common.Models { public IntPtr HWnd { get; set; } public uint ProcessId { get; set; } + public string ClassName { get; set; } public string ApplicationName { get; set; } public RECT ScreenPosition { get; set; } public WindowPlacement WindowPlacement { get; set; } diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/PersistentWindowProcessor.cs index c73313f..b9c1476 100644 --- a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/PersistentWindowProcessor.cs +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Common/PersistentWindowProcessor.cs @@ -21,20 +21,24 @@ namespace Ninjacrab.PersistentWindows.Common private const int MaxRestoreTimesLocal = 4; // Max restores activated by further window event for local console session private const int MaxRestoreTimesRemote = 6; // for remote session - private const int CaptureLatency = 3000; // milliseconds to wait for window position capture, should be bigger than RestoreLatency + private const int CaptureLatency = 3000; // milliseconds to wait for window position capture + private const int MinOsMoveWindows = 5; // minimum number of moving windows to measure in order to recognize OS initiated move // window position database private Dictionary> monitorApplications = null; // control shared by capture and restore - private Timer captureTimer; - private Timer restoreTimer; - private Timer restoreFinishedTimer; private Object databaseLock; // lock access to window position database private Object controlLock = new Object(); + + // capture control + private Timer captureTimer; private string validDisplayKeyForCapture = null; + private HashSet pendingCaptureWindows = new HashSet(); // restore control + private Timer restoreTimer; + private Timer restoreFinishedTimer; private bool restoringWindowPos = false; // about to restore private int restoreTimes = 0; private int restoreNestLevel = 0; // nested call level @@ -63,7 +67,7 @@ namespace Ninjacrab.PersistentWindows.Common }); validDisplayKeyForCapture = GetDisplayKey(); - StartCaptureTimer(); //initial capture + BeginCaptureApplicationsOnCurrentDisplays(); restoreTimer = new Timer(state => { @@ -196,12 +200,12 @@ namespace Ninjacrab.PersistentWindows.Common private void WinEventProc(IntPtr hWinEventHook, User32Events eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { - var window = new SystemWindow(hwnd); if (!User32.IsTopLevelWindow(hwnd)) { return; } + var window = new SystemWindow(hwnd); if (window.Parent.HWnd.ToInt64() != 0 || !window.Visible || string.IsNullOrEmpty(window.Title)) { // only track top level visible windows @@ -233,21 +237,23 @@ namespace Ninjacrab.PersistentWindows.Common Log.Trace(log); #endif + DateTime now = DateTime.Now; + if (restoringWindowPos) { switch (eventType) { - case User32Events.EVENT_SYSTEM_FOREGROUND: - // if user opened new window, don't abort restore, as new window is not affected by restore anyway - return; case User32Events.EVENT_OBJECT_LOCATIONCHANGE: // let it trigger next restore break; + + case User32Events.EVENT_SYSTEM_MOVESIZESTART: + return; + case User32Events.EVENT_SYSTEM_MINIMIZESTART: case User32Events.EVENT_SYSTEM_MINIMIZEEND: - case User32Events.EVENT_SYSTEM_MOVESIZESTART: case User32Events.EVENT_SYSTEM_MOVESIZEEND: - Log.Trace("User aborted restore by actively maneuver window"); + case User32Events.EVENT_SYSTEM_FOREGROUND: var thread = new Thread(() => { try @@ -255,7 +261,7 @@ namespace Ninjacrab.PersistentWindows.Common lock (databaseLock) { string displayKey = GetDisplayKey(); - CaptureWindow(window, eventType, DateTime.Now, displayKey); + CaptureWindow(window, eventType, now, displayKey); } } catch (Exception ex) @@ -264,7 +270,7 @@ namespace Ninjacrab.PersistentWindows.Common } }); thread.Start(); - return; + break; } CancelCaptureTimer(); @@ -282,19 +288,48 @@ namespace Ninjacrab.PersistentWindows.Common switch (eventType) { case User32Events.EVENT_OBJECT_LOCATIONCHANGE: + lock (controlLock) + { + // can not tell if this event is caused by user snap operation or OS initiated closing session + pendingCaptureWindows.Add(hwnd); + // wait for other user move events until capture timer expires + if (pendingCaptureWindows.Count < MinOsMoveWindows) + { + StartCaptureTimer(); + } + } break; case User32Events.EVENT_SYSTEM_FOREGROUND: case User32Events.EVENT_SYSTEM_MINIMIZESTART: case User32Events.EVENT_SYSTEM_MINIMIZEEND: case User32Events.EVENT_SYSTEM_MOVESIZEEND: + lock (controlLock) + { + // reset capture statistics for next capture + CancelCaptureTimer(); + if (pendingCaptureWindows.Count >= MinOsMoveWindows) + { + // too many windows changed location, this must be caused by OS move due to closing session + // stop capture any window move until restored + break; + } + pendingCaptureWindows.Clear(); + } + + string displayKey = GetDisplayKey(); + if (displayKey != validDisplayKeyForCapture) + { + Log.Trace("Discard capture for {0}, when expecting {1}", displayKey, validDisplayKeyForCapture); + break; + } + var thread = new Thread(() => { try { lock (databaseLock) { - string displayKey = GetDisplayKey(); - CaptureWindow(window, eventType, DateTime.Now, displayKey); + CaptureWindow(window, eventType, now, displayKey); } } catch (Exception ex) @@ -305,18 +340,6 @@ namespace Ninjacrab.PersistentWindows.Common thread.Start(); break; } - - /* - if (eventType != User32Events.EVENT_OBJECT_LOCATIONCHANGE) - { - userMoves++; - } - - if (userMoves > 0) - { - StartCaptureTimer(); - } - */ } } catch (Exception ex) @@ -427,7 +450,7 @@ namespace Ninjacrab.PersistentWindows.Common lock (databaseLock) { - StartCaptureApplicationsOnCurrentDisplays(); + CaptureApplicationsOnCurrentDisplays(displayKey); } } catch (Exception ex) @@ -452,25 +475,14 @@ namespace Ninjacrab.PersistentWindows.Common // reset capture statistics for next capture period CancelCaptureTimer(); + pendingCaptureWindows.Clear(); } } - private void StartCaptureApplicationsOnCurrentDisplays() - { - restoringWindowPos = false; - ResetState(); - CaptureApplicationsOnCurrentDisplays(); - } - - private void CaptureApplicationsOnCurrentDisplays(string displayKey = null) + private void CaptureApplicationsOnCurrentDisplays(string displayKey) { var appWindows = CaptureWindowsOfInterest(); int cnt = 0; - if (displayKey == null) - { - displayKey = GetDisplayKey(); - } - Log.Trace("Capturing windows for display setting {0}", displayKey); foreach (var window in appWindows) { @@ -539,11 +551,12 @@ namespace Ninjacrab.PersistentWindows.Common { HWnd = hwnd, #if DEBUG - // these function calls are very cpu-intensive + // these function calls are very CPU-intensive ApplicationName = window.Process.ProcessName, #else ApplicationName = "", #endif + ClassName = window.ClassName, ProcessId = processId, WindowPlacement = windowPlacement, @@ -559,12 +572,18 @@ namespace Ninjacrab.PersistentWindows.Common else { ApplicationDisplayMetrics prevDisplayMetrics = monitorApplications[displayKey][curDisplayMetrics.Key]; - if (prevDisplayMetrics.ProcessId != curDisplayMetrics.ProcessId) + if (prevDisplayMetrics.ProcessId != curDisplayMetrics.ProcessId + || prevDisplayMetrics.ClassName != curDisplayMetrics.ClassName) { // key collision between dead window and new window with the same hwnd monitorApplications[displayKey].Remove(curDisplayMetrics.Key); moved = true; } + else if (eventType == User32Events.EVENT_SYSTEM_FOREGROUND) + { + // when close/reopen session, OS/user may activate existing window (possibly with different position) + // just ignore it + } else if (!prevDisplayMetrics.EqualPlacement(curDisplayMetrics)) { /*