private bool AddOrUpdateWindow(string displayKey, SystemWindow window, out ApplicationDisplayMetrics applicationDisplayMetric) { WindowPlacement windowPlacement = new WindowPlacement(); User32.GetWindowPlacement(window.HWnd, ref windowPlacement); if (windowPlacement.ShowCmd == ShowWindowCommands.Normal) { User32.GetWindowRect(window.HWnd, ref windowPlacement.NormalPosition); } applicationDisplayMetric = new ApplicationDisplayMetrics { HWnd = window.HWnd, ApplicationName = window.Process.ProcessName, ProcessId = window.Process.Id, WindowPlacement = windowPlacement }; bool updated = false; if (!monitorApplications[displayKey].ContainsKey(applicationDisplayMetric.Key)) { updated = true; } else if (!monitorApplications[displayKey][applicationDisplayMetric.Key].EqualPlacement(applicationDisplayMetric)) { updated = true; } return(updated); }
private bool AddOrUpdateWindow(string displayKey, AppWindow window, out ApplicationDisplayMetrics applicationDisplayMetric) { WindowPlacement windowPlacement = new WindowPlacement(); User32.GetWindowPlacement(window.HWnd, ref windowPlacement); applicationDisplayMetric = new ApplicationDisplayMetrics { HWnd = window.HWnd, ApplicationName = window.Process.ProcessName, ProcessId = window.Process.Id, WindowPlacement = windowPlacement, Style = (uint)window.Style, ExtendedStyle = (uint)window.ExtendedStyle }; bool updated = false; if (!monitorApplications[displayKey].ContainsKey(applicationDisplayMetric.Key)) { updated = true; } else if (!monitorApplications[displayKey][applicationDisplayMetric.Key].EqualPlacement(applicationDisplayMetric)) { updated = true; } return(updated); }
private bool AddOrUpdateWindow(string displayKey, SystemWindow window, out ApplicationDisplayMetrics applicationDisplayMetric) { WindowPlacement windowPlacement = new WindowPlacement(); User32.GetWindowPlacement(window.HWnd, ref windowPlacement); if (windowPlacement.ShowCmd == ShowWindowCommands.Normal) { User32.GetWindowRect(window.HWnd, ref windowPlacement.NormalPosition); // Undo windows scale factor in window size or we end up inflating the size of windows double dpi = (double)User32.GetDpiForWindow(window.HWnd); Rectangle pos = windowPlacement.NormalPosition.ToRectangle(); pos.Width = (int)((double)pos.Width / dpi * NATIVE_DPI); pos.Height = (int)((double)pos.Height / dpi * NATIVE_DPI); windowPlacement.NormalPosition = (RECT)pos; } applicationDisplayMetric = new ApplicationDisplayMetrics { HWnd = window.HWnd, // Fetching these is super CPU intensive so do it on debug builds only #if DEBUG ApplicationName = window.Process.ProcessName, ProcessId = window.Process.Id, #else ApplicationName = "...", ProcessId = 0, #endif WindowPlacement = windowPlacement }; bool updated = false; if (!monitorApplications[displayKey].ContainsKey(applicationDisplayMetric.Key)) { updated = true; } else if (!monitorApplications[displayKey][applicationDisplayMetric.Key].EqualPlacement(applicationDisplayMetric)) { updated = true; } return(updated); }
private void CaptureApplicationsOnCurrentDisplays(string displayKey = null, bool initialCapture = false) { lock (displayChangeLock) { DesktopDisplayMetrics metrics = DesktopDisplayMetrics.AcquireMetrics(); if (displayKey == null) { displayKey = metrics.Key; } if (!metrics.Equals(lastMetrics)) { // since the resolution doesn't match, lets wait till it's restored Log.Info("Detected changes in display metrics, will capture once windows are restored"); return; } if (!monitorApplications.ContainsKey(displayKey)) { monitorApplications.Add(displayKey, new SortedDictionary <string, ApplicationDisplayMetrics>()); } var appWindows = CaptureWindowsOfInterest(); List <string> changeLog = new List <string>(); List <ApplicationDisplayMetrics> apps = new List <ApplicationDisplayMetrics>(); foreach (var window in appWindows) { ApplicationDisplayMetrics applicationDisplayMetric = null; bool addToChangeLog = AddOrUpdateWindow(displayKey, window, out applicationDisplayMetric); if (addToChangeLog) { apps.Add(applicationDisplayMetric); changeLog.Add(string.Format("CAOCD - Capturing {0,-45} at [{1,4}x{2,4}] size [{3,4}x{4,4}] V:{5} {6} ", applicationDisplayMetric, applicationDisplayMetric.WindowPlacement.NormalPosition.Left, applicationDisplayMetric.WindowPlacement.NormalPosition.Top, applicationDisplayMetric.WindowPlacement.NormalPosition.Width, applicationDisplayMetric.WindowPlacement.NormalPosition.Height, window.Visible, window.Title )); } } // only save the updated if it didn't seem like something moved everything if ((apps.Count > 0 && apps.Count < AppsMovedThreshold) || initialCapture) { foreach (var app in apps) { if (!monitorApplications[displayKey].ContainsKey(app.Key)) { monitorApplications[displayKey].Add(app.Key, app); } else if (!monitorApplications[displayKey][app.Key].EqualPlacement(app)) { monitorApplications[displayKey][app.Key].WindowPlacement = app.WindowPlacement; } } changeLog.Sort(); Log.Info("{0}Capturing applications for {1}", initialCapture ? "Initial " : "", displayKey); Log.Trace("{0} windows recorded{1}{2}", apps.Count, Environment.NewLine, string.Join(Environment.NewLine, changeLog)); } } }
/// <summary> /// OMG this method is awful!!! but yagni /// </summary> /// <param name="callbackParam"></param> private void WindowPositionChangedHandler(CallWindowProcedureParam callbackParam) { ApplicationDisplayMetrics appMetrics = null; if (monitorApplications == null || !monitorApplications.ContainsKey(lastMetrics.Key)) { Log.Error("No definitions found for this resolution: {0}", lastMetrics.Key); return; } appMetrics = monitorApplications[lastMetrics.Key] .FirstOrDefault(row => row.Value.HWnd == callbackParam.hwnd) .Value; if (appMetrics == null) { var newAppWindow = SystemWindow.AllToplevelWindows .FirstOrDefault(row => row.Parent.HWnd.ToInt64() == 0 && !string.IsNullOrEmpty(row.Title) && !row.Title.Equals("Program Manager") && row.Visible && row.HWnd == callbackParam.hwnd); if (newAppWindow == null) { Log.Error("Can't find hwnd {0}", callbackParam.hwnd.ToInt64()); return; } ApplicationDisplayMetrics applicationDisplayMetric = null; AddOrUpdateWindow(lastMetrics.Key, newAppWindow, out applicationDisplayMetric); return; } WindowPlacement windowPlacement = appMetrics.WindowPlacement; WindowsPosition newPosition = (WindowsPosition)Marshal.PtrToStructure(callbackParam.lparam, typeof(WindowsPosition)); windowPlacement.NormalPosition.Left = newPosition.Left; windowPlacement.NormalPosition.Top = newPosition.Top; windowPlacement.NormalPosition.Right = newPosition.Left + newPosition.Width; windowPlacement.NormalPosition.Bottom = newPosition.Top + newPosition.Height; var key = appMetrics.Key; if (monitorApplications[lastMetrics.Key].ContainsKey(key)) { monitorApplications[lastMetrics.Key][appMetrics.Key].WindowPlacement = windowPlacement; } else { Log.Error("Hwnd {0} is not in list, we should capture", callbackParam.hwnd.ToInt64()); return; } Log.Info("WPCH - Capturing {0} at [{1}x{2}] size [{3}x{4}]", appMetrics, appMetrics.WindowPlacement.NormalPosition.Left, appMetrics.WindowPlacement.NormalPosition.Top, appMetrics.WindowPlacement.NormalPosition.Width, appMetrics.WindowPlacement.NormalPosition.Height ); }
private void restoreApplicationsOnCurrentDisplays() { lock (_displayChangeLock) { var desktopKey = _desktopService.GetDesktopKey(); _logger?.LogInformation($"Restore applications for desktop '{desktopKey}' started."); if (!_desktopApplications.TryGetValue(desktopKey, out var applications)) { // the display setting has not been captured yet _logger?.LogWarning($"Restore applications for desktop '{desktopKey}' completed with warning (no capture data)."); return; } else { if (applications.Count == 0) { _logger?.LogWarning($"Restore applications for desktop '{desktopKey}' completed with warning (capture data empty)."); return; } } try { var windowsOfInterest = _windowService.CaptureWindowsOfInterest(); _logger?.LogInformation($"Found {windowsOfInterest.Length} windows in WindowMagic interest."); foreach (var window in windowsOfInterest) { var procName = window.Process.ProcessName; if (procName.Contains("CodeSetup")) { continue; // prevent hang in SetWindowPlacement() (SFA: What's this about??? seems almost too specific!) } var applicationKey = ApplicationDisplayMetrics.GetKey(window.HWnd, procName); if (applications.TryGetValue(applicationKey, out var prevDisplayMetrics)) { try { _logger?.LogInformation($"Restore position for '{applicationKey}' started."); if (hasWindowChanged(applications, window, out var _)) { var windowPlacement = prevDisplayMetrics.WindowPlacement; bool success; // SetWindowPlacement will "place" the window on the correct screen based on its normal position. // If the state isn't "normal/restored" the window will appear not to actually move. To solve this // either a quick switch from 'restore' to whatever the target state is, or using another API // to position the window. if (windowPlacement.ShowCmd != ShowWindowCommands.Normal) { var prevCmd = windowPlacement.ShowCmd; windowPlacement.ShowCmd = ShowWindowCommands.Normal; success = checkWin32Error(User32.SetWindowPlacement(window.HWnd, ref windowPlacement)); windowPlacement.ShowCmd = prevCmd; _logger?.LogTrace("Toggling to normal window state for: ({0}/{6} [{1}x{2}]-[{3}x{4}]) - {5}", window.Process.ProcessName, windowPlacement.NormalPosition.Left, windowPlacement.NormalPosition.Top, windowPlacement.NormalPosition.Width, windowPlacement.NormalPosition.Height, success, windowPlacement.ShowCmd.ToString()); } // Set final window placement data - sets "normal" position for all windows (used for de-snapping and screen ID'ing) success = checkWin32Error(User32.SetWindowPlacement(window.HWnd, ref windowPlacement)); _logger?.LogTrace("SetWindowPlacement({0}/{6} [{1}x{2}]-[{3}x{4}]) - {5}", window.Process.ProcessName, windowPlacement.NormalPosition.Left, windowPlacement.NormalPosition.Top, windowPlacement.NormalPosition.Width, windowPlacement.NormalPosition.Height, success, windowPlacement.ShowCmd.ToString()); // For any windows not maximized or minimized, they might be snapped. This will place them back in their current snapped positions. // (Remember: NormalPosition is used when the user wants to *restore* from the snapped position when dragging) if (windowPlacement.ShowCmd != ShowWindowCommands.ShowMinimized && windowPlacement.ShowCmd != ShowWindowCommands.ShowMaximized) { var rect = prevDisplayMetrics.ScreenPosition; success = User32.SetWindowPos( window.HWnd, IntPtr.Zero, rect.Left, rect.Top, rect.Width, rect.Height, (uint)(SetWindowPosFlags.IgnoreZOrder | SetWindowPosFlags.AsynchronousWindowPosition)); _logger?.LogTrace("Restoring position of non maximized/minimized window: SetWindowPos({0}/{6} [{1}x{2}]-[{3}x{4}]) - {5}", window.Process.ProcessName, rect.Left, rect.Top, rect.Width, rect.Height, success, windowPlacement.ShowCmd.ToString()); checkWin32Error(success); } _logger?.LogInformation($"Restore position for '{applicationKey}' completed (position changed)."); } else { _logger?.LogInformation($"Restore position for '{applicationKey}' completed (position change not needed)."); } } catch (Exception ex) { _logger?.LogWarning(ex, $"Restore position for '{applicationKey}' failed."); } } else { _logger?.LogInformation($"Restore position for '{applicationKey}' ignored, previous location not found."); } } _logger?.LogInformation($"Restore applications for desktop '{desktopKey}' completed."); } catch (Exception ex) { _logger?.LogError(ex, $"Restore applications for desktop '{desktopKey}' failed."); throw; } } }
private bool hasWindowChanged(SortedDictionary <string, ApplicationDisplayMetrics> prevDisplayMetricsCollection, SystemWindow window, out ApplicationDisplayMetrics curDisplayMetrics) { var windowPlacement = new WindowPlacement(); User32.GetWindowPlacement(window.HWnd, ref windowPlacement); // Need to get the "real" screen position that takes into account the snapped or maximized state. "NormalPosition" is used when a restore occurs // or when the user drags the window out of the snapped sate to 'restore' it back to what it was before. (It's a feature!) var screenPosition = new RECT(); User32.GetWindowRect(window.HWnd, ref screenPosition); User32.GetWindowThreadProcessId(window.HWnd, out uint processId); curDisplayMetrics = new ApplicationDisplayMetrics { HWnd = window.HWnd, ProcessId = processId, ProcessName = window.Process.ProcessName, WindowPlacement = windowPlacement, ScreenPosition = screenPosition }; bool needUpdate; if (prevDisplayMetricsCollection.TryGetValue(curDisplayMetrics.Key, out var prevDisplayMetrics)) { if (prevDisplayMetrics.ProcessId != curDisplayMetrics.ProcessId) { // key collision between dead window and new window with the same hwnd _logger?.LogWarning($"Window ProcessId has changed from {prevDisplayMetrics.ProcessId} to {curDisplayMetrics.ProcessId}. Removing from known positions collection."); prevDisplayMetricsCollection.Remove(curDisplayMetrics.Key); needUpdate = true; } else if (!prevDisplayMetrics.ScreenPosition.Equals(curDisplayMetrics.ScreenPosition)) { needUpdate = true; _logger?.LogTrace("Window position changed for: {0} {1} {2}.", window.Process.ProcessName, processId, window.HWnd.ToString("X8")); } else if (!prevDisplayMetrics.EqualPlacement(curDisplayMetrics)) { needUpdate = true; _logger?.LogTrace("Window placement changed for: {0} {1} {2}.", window.Process.ProcessName, processId, window.HWnd.ToString("X8")); } else { needUpdate = false; _logger?.LogTrace("Window position and placement not changed for: {0} {1} {2}.", window.Process.ProcessName, processId, window.HWnd.ToString("X8")); } } else { _logger?.LogTrace("Window is new for: {0} {1} {2}.", window.Process.ProcessName, processId, window.HWnd.ToString("X8")); needUpdate = true; } return(needUpdate); }
private void RestoreApplicationsOnCurrentDisplays(string displayKey = null) { lock (displayChangeLock) { if (displayKey == null) { DesktopDisplayMetrics metrics = DesktopDisplayMetrics.AcquireMetrics(); displayKey = metrics.Key; } if (!monitorApplications.ContainsKey(displayKey) || monitorApplications[displayKey].Count == 0) { // the display setting has not been captured yet Log.Trace("Unknown display setting {0}", displayKey); return; } Log.Info("Restoring applications for {0}", displayKey); foreach (var window in WindowHelper.CaptureWindowsOfInterest()) { var procName = window.Process.ProcessName; if (procName.Contains("CodeSetup")) // SFA: What's this about??? seems almost too specific! { // prevent hang in SetWindowPlacement() continue; } var applicationKey = ApplicationDisplayMetrics.GetKey(window.HWnd, window.Process.ProcessName); var prevDisplayMetrics = monitorApplications[displayKey][applicationKey]; var windowPlacement = prevDisplayMetrics.WindowPlacement; if (monitorApplications[displayKey].ContainsKey(applicationKey)) { ApplicationDisplayMetrics curDisplayMetrics = null; if (!HasWindowChanged(displayKey, window, out curDisplayMetrics)) { continue; } // SetWindowPlacement will "place" the window on the correct screen based on its normal position. // If the state isn't "normal/restored" the window will appear not to actually move. To solve this // either a quick switch from 'restore' to whatever the target state is, or using another API // to position the window. bool success; if (windowPlacement.ShowCmd != ShowWindowCommands.Normal) { var prevCmd = windowPlacement.ShowCmd; windowPlacement.ShowCmd = ShowWindowCommands.Normal; success = CheckWin32Error(User32.SetWindowPlacement(window.HWnd, ref windowPlacement)); windowPlacement.ShowCmd = prevCmd; Log.Trace("Toggling to normal window state for: ({0} [{1}x{2}]-[{3}x{4}]) - {5}", window.Process.ProcessName, windowPlacement.NormalPosition.Left, windowPlacement.NormalPosition.Top, windowPlacement.NormalPosition.Width, windowPlacement.NormalPosition.Height, success); } // Set final window placement data - sets "normal" position for all windows (used for de-snapping and screen ID'ing) success = CheckWin32Error(User32.SetWindowPlacement(window.HWnd, ref windowPlacement)); Log.Trace("SetWindowPlacement({0} [{1}x{2}]-[{3}x{4}]) - {5}", window.Process.ProcessName, windowPlacement.NormalPosition.Left, windowPlacement.NormalPosition.Top, windowPlacement.NormalPosition.Width, windowPlacement.NormalPosition.Height, success); // For any windows not maximized or minimized, they might be snapped. This will place them back in their current snapped positions. // (Remember: NormalPosition is used when the user wants to *restore* from the snapped position when dragging) if (windowPlacement.ShowCmd != ShowWindowCommands.ShowMinimized && windowPlacement.ShowCmd != ShowWindowCommands.ShowMaximized) { var rect = prevDisplayMetrics.ScreenPosition; success = User32.SetWindowPos( window.HWnd, IntPtr.Zero, rect.Left, rect.Top, rect.Width, rect.Height, (uint)(SetWindowPosFlags.IgnoreZOrder | SetWindowPosFlags.AsynchronousWindowPosition)); Log.Trace("Restoring position of non maximized/minimized window: SetWindowPos({0} [{1}x{2}]-[{3}x{4}]) - {5}", window.Process.ProcessName, rect.Left, rect.Top, rect.Width, rect.Height, success); CheckWin32Error(success); } } } Log.Trace("Restored windows position for display setting {0}", displayKey); } }
private bool HasWindowChanged(string displayKey, SystemWindow window, out ApplicationDisplayMetrics curDisplayMetrics) { var windowPlacement = new WindowPlacement(); User32.GetWindowPlacement(window.HWnd, ref windowPlacement); // Need to get the "real" screen position that takes into account the snapped or maximized state. "NormalPosition" is used when a restore occurs // or when the user drags the window out of the snapped sate to 'restore' it back to what it was before. (It's a feature!) var screenPosition = new RECT(); User32.GetWindowRect(window.HWnd, ref screenPosition); uint processId = 0; uint threadId = User32.GetWindowThreadProcessId(window.HWnd, out processId); curDisplayMetrics = new ApplicationDisplayMetrics { HWnd = window.HWnd, #if DEBUG // these function calls are very cpu-intensive ApplicationName = window.Process.ProcessName, #else ApplicationName = "", #endif ProcessId = processId, WindowPlacement = windowPlacement, RecoverWindowPlacement = true, ScreenPosition = screenPosition }; bool needUpdate = false; if (!monitorApplications[displayKey].ContainsKey(curDisplayMetrics.Key)) { needUpdate = true; } else { ApplicationDisplayMetrics prevDisplayMetrics = monitorApplications[displayKey][curDisplayMetrics.Key]; if (prevDisplayMetrics.ProcessId != curDisplayMetrics.ProcessId) { // key collision between dead window and new window with the same hwnd monitorApplications[displayKey].Remove(curDisplayMetrics.Key); needUpdate = true; } else if (!prevDisplayMetrics.ScreenPosition.Equals(curDisplayMetrics.ScreenPosition)) { needUpdate = true; Log.Trace("Window position changed for: {0} {1} {2}.", window.Process.ProcessName, processId, window.HWnd.ToString("X8")); } else if (!prevDisplayMetrics.EqualPlacement(curDisplayMetrics)) { needUpdate = true; Log.Trace("Window placement changed for: {0} {1} {2}.", window.Process.ProcessName, processId, window.HWnd.ToString("X8")); //string log = string.Format("prev WindowPlacement ({0}, {1}) of size {2} x {3}", // prevDisplayMetrics.WindowPlacement.NormalPosition.Left, // prevDisplayMetrics.WindowPlacement.NormalPosition.Top, // prevDisplayMetrics.WindowPlacement.NormalPosition.Width, // prevDisplayMetrics.WindowPlacement.NormalPosition.Height // ); //string log2 = string.Format("\ncur WindowPlacement ({0}, {1}) of size {2} x {3}", // curDisplayMetrics.WindowPlacement.NormalPosition.Left, // curDisplayMetrics.WindowPlacement.NormalPosition.Top, // curDisplayMetrics.WindowPlacement.NormalPosition.Width, // curDisplayMetrics.WindowPlacement.NormalPosition.Height // ); //Log.Trace("{0}", log + log2); } } return(needUpdate); }
private void CaptureApplicationsOnCurrentDisplays(string displayKey = null, bool initialCapture = false) { lock (displayChangeLock) { if (displayKey == null) { DesktopDisplayMetrics metrics = DesktopDisplayMetrics.AcquireMetrics(); displayKey = metrics.Key; } if (!monitorApplications.ContainsKey(displayKey)) { monitorApplications.Add(displayKey, new SortedDictionary <string, ApplicationDisplayMetrics>()); } List <string> updateLogs = new List <string>(); List <ApplicationDisplayMetrics> updateApps = new List <ApplicationDisplayMetrics>(); var appWindows = WindowHelper.CaptureWindowsOfInterest(); foreach (var window in appWindows) { ApplicationDisplayMetrics curDisplayMetrics = null; if (HasWindowChanged(displayKey, window, out curDisplayMetrics)) { updateApps.Add(curDisplayMetrics); string log = string.Format("Captured {0,-8} at ({1}, {2}) of size {3} x {4} V:{5} {6} ", curDisplayMetrics, curDisplayMetrics.ScreenPosition.Left, curDisplayMetrics.ScreenPosition.Top, curDisplayMetrics.ScreenPosition.Width, curDisplayMetrics.ScreenPosition.Height, window.Visible, window.Title ); string log2 = string.Format("\n WindowPlacement.NormalPosition at ({0}, {1}) of size {2} x {3}", curDisplayMetrics.WindowPlacement.NormalPosition.Left, curDisplayMetrics.WindowPlacement.NormalPosition.Top, curDisplayMetrics.WindowPlacement.NormalPosition.Width, curDisplayMetrics.WindowPlacement.NormalPosition.Height ); updateLogs.Add(log + log2); } } Log.Trace("{0}Capturing windows for display setting {1}", initialCapture ? "Initial " : "", displayKey); List <string> commitUpdateLog = new List <string>(); //for (int i = 0; i < maxUpdateCnt; i++) for (int i = 0; i < updateApps.Count; i++) { ApplicationDisplayMetrics curDisplayMetrics = updateApps[i]; commitUpdateLog.Add(updateLogs[i]); if (!monitorApplications[displayKey].ContainsKey(curDisplayMetrics.Key)) { monitorApplications[displayKey].Add(curDisplayMetrics.Key, curDisplayMetrics); } else { /* * // partially update Normal position part of WindowPlacement * WindowPlacement wp = monitorApplications[displayKey][curDisplayMetrics.Key].WindowPlacement; * wp.NormalPosition = curDisplayMetrics.WindowPlacement.NormalPosition; * monitorApplications[displayKey][curDisplayMetrics.Key].WindowPlacement = wp; */ monitorApplications[displayKey][curDisplayMetrics.Key].WindowPlacement = curDisplayMetrics.WindowPlacement; monitorApplications[displayKey][curDisplayMetrics.Key].ScreenPosition = curDisplayMetrics.ScreenPosition; } } //commitUpdateLog.Sort(); Log.Trace("{0}{1}{2} windows captured", string.Join(Environment.NewLine, commitUpdateLog), Environment.NewLine, commitUpdateLog.Count); } }