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);
        }
Example #2
0
        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);
            }
        }