[EXPLORER][RUNDLL32] Restore minimized non-task windows (#5228)
authorKatayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
Tue, 2 May 2023 22:39:05 +0000 (07:39 +0900)
committerGitHub <noreply@github.com>
Tue, 2 May 2023 22:39:05 +0000 (07:39 +0900)
The minimized non-task windows were not usable due to the bugs. In some situations, the system will restore the minimized non-task windows.
- Add IsTaskWnd helper function.
- Add SendPulseToTray function to send a pulse to the tray window.
- At some shell hook handlings, send a pulse to the tray window.
- Add IgnorePulse flag to control the timing of restoring.
- Add a timer to reset IgnorePulse flag.
- If the pulse has come and IgnorePulse flag is false, then restore the minimized non-task windows.
- Modify the rundll32 window procedure.
- Use WINDOWPLACEMENT to restore the minimized windows.
CORE-13895, CORE-18350

base/shell/explorer/precomp.h
base/shell/explorer/taskswnd.cpp
base/shell/explorer/traywnd.cpp
base/system/rundll32/rundll32.c

index efce3c2..881c2d0 100644 (file)
@@ -130,6 +130,7 @@ HRESULT WINAPI _CBandSite_CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void
 #define TWM_GETTASKSWITCH (WM_USER + 236)
 #define TWM_OPENSTARTMENU (WM_USER + 260)
 #define TWM_SETTINGSCHANGED (WM_USER + 300)
+#define TWM_PULSE (WM_USER + 400)
 
 extern const GUID IID_IShellDesktopTray;
 
@@ -149,6 +150,7 @@ DECLARE_INTERFACE_(ITrayWindow, IUnknown)
     STDMETHOD_(HWND, DisplayProperties) (THIS) PURE;
     STDMETHOD_(BOOL, ExecContextMenuCmd) (THIS_ UINT uiCmd) PURE;
     STDMETHOD_(BOOL, Lock) (THIS_ BOOL bLock) PURE;
+    STDMETHOD_(BOOL, IsTaskWnd) (THIS_ HWND hWnd) PURE;
 };
 #undef INTERFACE
 
@@ -166,6 +168,7 @@ DECLARE_INTERFACE_(ITrayWindow, IUnknown)
 #define ITrayWindow_DisplayProperties(p)    (p)->lpVtbl->DisplayProperties(p)
 #define ITrayWindow_ExecContextMenuCmd(p,a) (p)->lpVtbl->ExecContextMenuCmd(p,a)
 #define ITrayWindow_Lock(p,a)               (p)->lpVtbl->Lock(p,a)
+#define ITrayWindow_IsTaskWnd(p,a)          (p)->lpVtbl->IsTaskWnd(p,a)
 #endif
 
 HRESULT CreateTrayWindow(ITrayWindow ** ppTray);
index da260db..5437f47 100644 (file)
@@ -82,6 +82,7 @@ typedef struct _TASK_ITEM
     PTASK_GROUP Group;
     INT Index;
     INT IconIndex;
+    WINDOWPLACEMENT wndpl;
 
     union
     {
@@ -1112,6 +1113,8 @@ public:
                 TaskItem->hWnd = hWnd;
                 TaskItem->Index = -1;
                 TaskItem->Group = AddToTaskGroup(hWnd);
+                TaskItem->wndpl.length = sizeof(TaskItem->wndpl);
+                ::GetWindowPlacement(hWnd, &TaskItem->wndpl);
 
                 if (!m_IsDestroying)
                 {
@@ -1131,7 +1134,6 @@ public:
         }
 
         CheckActivateTaskItem(TaskItem);
-
         return FALSE;
     }
 
@@ -1384,22 +1386,11 @@ public:
 
     BOOL CALLBACK EnumWindowsProc(IN HWND hWnd)
     {
-        /* Only show windows that still exist and are visible and none of explorer's
-        special windows (such as the desktop or the tray window) */
-        if (::IsWindow(hWnd) && ::IsWindowVisible(hWnd) &&
-            !m_Tray->IsSpecialHWND(hWnd))
+        if (m_Tray->IsTaskWnd(hWnd))
         {
-            DWORD exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
-            /* Don't list popup windows and also no tool windows */
-            if ((::GetWindow(hWnd, GW_OWNER) == NULL || exStyle & WS_EX_APPWINDOW) &&
-                !(exStyle & WS_EX_TOOLWINDOW))
-            {
-                TRACE("Adding task for %p...\n", hWnd);
-                AddTask(hWnd);
-            }
-
+            TRACE("Adding task for %p...\n", hWnd);
+            AddTask(hWnd);
         }
-
         return TRUE;
     }
 
@@ -1475,6 +1466,12 @@ public:
         return TRUE;
     }
 
+    VOID SendPulseToTray(BOOL bDelete, HWND hwndActive)
+    {
+        HWND hwndTray = m_Tray->GetHWND();
+        ::SendMessage(hwndTray, TWM_PULSE, bDelete, (LPARAM)hwndActive);
+    }
+
     BOOL HandleAppCommand(IN WPARAM wParam, IN LPARAM lParam)
     {
         BOOL Ret = FALSE;
@@ -1516,17 +1513,20 @@ public:
             break;
 
         case HSHELL_WINDOWCREATED:
+            SendPulseToTray(FALSE, (HWND)lParam);
             AddTask((HWND) lParam);
             break;
 
         case HSHELL_WINDOWDESTROYED:
             /* The window still exists! Delay destroying it a bit */
-            DeleteTask((HWND) lParam);
+            SendPulseToTray(TRUE, (HWND)lParam);
+            DeleteTask((HWND)lParam);
             break;
 
         case HSHELL_RUDEAPPACTIVATED:
         case HSHELL_WINDOWACTIVATED:
-            ActivateTask((HWND) lParam);
+            SendPulseToTray(FALSE, (HWND)lParam);
+            ActivateTask((HWND)lParam);
             break;
 
         case HSHELL_FLASH:
@@ -1597,12 +1597,17 @@ public:
 
             if (!bIsMinimized && bIsActive)
             {
+                TaskItem->wndpl.length = sizeof(TaskItem->wndpl);
+                ::GetWindowPlacement(TaskItem->hWnd, &TaskItem->wndpl);
+
                 ::ShowWindowAsync(TaskItem->hWnd, SW_MINIMIZE);
                 TRACE("Valid button clicked. App window Minimized.\n");
             }
             else
             {
                 ::SwitchToThisWindow(TaskItem->hWnd, TRUE);
+                ::SetWindowPlacement(TaskItem->hWnd, &TaskItem->wndpl);
+
                 TRACE("Valid button clicked. App window Restored.\n");
             }
         }
@@ -1631,6 +1636,7 @@ public:
         TaskItem = FindTaskItemByIndex((INT) wIndex);
         if (TaskItem != NULL)
         {
+            SendPulseToTray(FALSE, TaskItem->hWnd);
             HandleTaskItemClick(TaskItem);
             return TRUE;
         }
index ca930e5..05d130b 100644 (file)
@@ -169,7 +169,13 @@ IsThereAnyEffectiveWindow(BOOL bMustBeInMonitor)
     return ei.hwndFound != NULL;
 }
 
-CSimpleArray<HWND>  g_MinimizedAll;
+/* Minimized window position info */
+struct MINWNDPOS
+{
+    HWND hwnd;
+    WINDOWPLACEMENT wndpl;
+};
+CSimpleArray<MINWNDPOS>  g_MinimizedAll;
 
 /*
  * ITrayWindow
@@ -559,6 +565,7 @@ public:
             DWORD InSizeMove : 1;
             DWORD IsDragging : 1;
             DWORD NewPosSize : 1;
+            DWORD IgnorePulse : 1;
         };
     };
 
@@ -587,6 +594,7 @@ public:
         ZeroMemory(&m_TraySize, sizeof(m_TraySize));
         ZeroMemory(&m_AutoHideOffset, sizeof(m_AutoHideOffset));
         ZeroMemory(&m_MouseTrackingInfo, sizeof(m_MouseTrackingInfo));
+        IgnorePulse = TRUE;
     }
 
     virtual ~CTrayWindow()
@@ -2460,6 +2468,22 @@ ChangePos:
         return bPrevLock;
     }
 
+    /* The task window is visible and non-WS_EX_TOOLWINDOW and
+       { has WS_EX_APPWINDOW style or has no owner } and is none of explorer's
+       special windows (such as the desktop or the tray window) */
+    BOOL STDMETHODCALLTYPE IsTaskWnd(HWND hWnd)
+    {
+        if (::IsWindow(hWnd) && ::IsWindowVisible(hWnd) && !IsSpecialHWND(hWnd))
+        {
+            DWORD exStyle = (DWORD)::GetWindowLongPtr(hWnd, GWL_EXSTYLE);
+            if (((exStyle & WS_EX_APPWINDOW) || ::GetWindow(hWnd, GW_OWNER) == NULL) &&
+                !(exStyle & WS_EX_TOOLWINDOW))
+            {
+                return TRUE;
+            }
+        }
+        return FALSE;
+    }
 
     /*
      *  IContextMenu
@@ -2596,6 +2620,15 @@ ChangePos:
         return TRUE;
     }
 
+#define TIMER_ID_IGNOREPULSERESET 888
+#define TIMER_IGNOREPULSERESET_TIMEOUT 200
+
+    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+    {
+        KillTimer(TIMER_ID_IGNOREPULSERESET);
+        return 0;
+    }
+
     LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
     {
         if (m_Theme)
@@ -3160,6 +3193,39 @@ HandleTrayContextMenu:
         return (LRESULT)m_TaskSwitch;
     }
 
+    void RestoreMinimizedNonTaskWnds(BOOL bDestroyed, HWND hwndActive)
+    {
+        for (INT i = g_MinimizedAll.GetSize() - 1; i >= 0; --i)
+        {
+            HWND hwnd = g_MinimizedAll[i].hwnd;
+            if (!hwnd || hwndActive == hwnd)
+                continue;
+
+            if (::IsWindowVisible(hwnd) && ::IsIconic(hwnd) &&
+                (!IsTaskWnd(hwnd) || !::IsWindowEnabled(hwnd)))
+            {
+                ::SetWindowPlacement(hwnd, &g_MinimizedAll[i].wndpl); // Restore
+            }
+        }
+
+        g_MinimizedAll.RemoveAll();
+
+        if (!bDestroyed)
+            ::SetForegroundWindow(hwndActive);
+    }
+
+    LRESULT OnPulse(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+    {
+        if (IgnorePulse)
+            return 0;
+
+        KillTimer(TIMER_ID_IGNOREPULSERESET);
+        IgnorePulse = TRUE;
+        RestoreMinimizedNonTaskWnds((BOOL)wParam, (HWND)lParam);
+        SetTimer(TIMER_ID_IGNOREPULSERESET, TIMER_IGNOREPULSERESET_TIMEOUT, NULL);
+        return 0;
+    }
+
     LRESULT OnHotkey(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
     {
         return HandleHotKey(wParam);
@@ -3170,8 +3236,7 @@ HandleTrayContextMenu:
         HWND hwndDesktop;
         HWND hTrayWnd;
         HWND hwndProgman;
-        BOOL bRet;
-        CSimpleArray<HWND> *pMinimizedAll;
+        CSimpleArray<MINWNDPOS> *pMinimizedAll;
         BOOL bShowDesktop;
     };
 
@@ -3185,11 +3250,9 @@ HandleTrayContextMenu:
     static BOOL CALLBACK MinimizeWindowsProc(HWND hwnd, LPARAM lParam)
     {
         MINIMIZE_INFO *info = (MINIMIZE_INFO *)lParam;
-        if (hwnd == info->hwndDesktop || hwnd == info->hTrayWnd ||
-            hwnd == info->hwndProgman)
-        {
-            return TRUE;
-        }
+        if (hwnd == info->hwndDesktop || hwnd == info->hTrayWnd || hwnd == info->hwndProgman)
+            return TRUE; // Ignore special windows
+
         if (!info->bShowDesktop)
         {
             if (!::IsWindowEnabled(hwnd) || IsDialog(hwnd))
@@ -3198,35 +3261,38 @@ HandleTrayContextMenu:
             if (hwndOwner && !::IsWindowEnabled(hwndOwner))
                 return TRUE;
         }
+
         if (::IsWindowVisible(hwnd) && !::IsIconic(hwnd))
         {
+            MINWNDPOS mwp;
+            mwp.hwnd = hwnd;
+            mwp.wndpl.length = sizeof(mwp.wndpl);
+            ::GetWindowPlacement(hwnd, &mwp.wndpl); // Save the position and status
+
+            info->pMinimizedAll->Add(mwp);
+
             ::ShowWindowAsync(hwnd, SW_MINIMIZE);
-            info->bRet = TRUE;
-            info->pMinimizedAll->Add(hwnd);
         }
+
         return TRUE;
     }
 
     VOID MinimizeAll(BOOL bShowDesktop = FALSE)
     {
+        IgnorePulse = TRUE;
+        KillTimer(TIMER_ID_IGNOREPULSERESET);
+
         MINIMIZE_INFO info;
         info.hwndDesktop = GetDesktopWindow();;
         info.hTrayWnd = FindWindowW(L"Shell_TrayWnd", NULL);
         info.hwndProgman = FindWindowW(L"Progman", NULL);
-        info.bRet = FALSE;
         info.pMinimizedAll = &g_MinimizedAll;
         info.bShowDesktop = bShowDesktop;
         EnumWindows(MinimizeWindowsProc, (LPARAM)&info);
 
-        // invalid handles should be cleared to avoid mismatch of handles
-        for (INT i = 0; i < g_MinimizedAll.GetSize(); ++i)
-        {
-            if (!::IsWindow(g_MinimizedAll[i]))
-                g_MinimizedAll[i] = NULL;
-        }
-
         ::SetForegroundWindow(m_DesktopWnd);
         ::SetFocus(m_DesktopWnd);
+        SetTimer(TIMER_ID_IGNOREPULSERESET, TIMER_IGNOREPULSERESET_TIMEOUT, NULL);
     }
 
     VOID ShowDesktop()
@@ -3236,15 +3302,20 @@ HandleTrayContextMenu:
 
     VOID RestoreAll()
     {
+        IgnorePulse = TRUE;
+        KillTimer(TIMER_ID_IGNOREPULSERESET);
+
         for (INT i = g_MinimizedAll.GetSize() - 1; i >= 0; --i)
         {
-            HWND hwnd = g_MinimizedAll[i];
+            HWND hwnd = g_MinimizedAll[i].hwnd;
             if (::IsWindowVisible(hwnd) && ::IsIconic(hwnd))
             {
-                ::ShowWindowAsync(hwnd, SW_RESTORE);
+                ::SetWindowPlacement(hwnd, &g_MinimizedAll[i].wndpl);
             }
         }
+
         g_MinimizedAll.RemoveAll();
+        SetTimer(TIMER_ID_IGNOREPULSERESET, TIMER_IGNOREPULSERESET_TIMEOUT, NULL);
     }
 
     LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
@@ -3288,9 +3359,12 @@ HandleTrayContextMenu:
         {
             ProcessAutoHide();
         }
-
-        bHandled = FALSE;
-        return TRUE;
+        else if (wParam == TIMER_ID_IGNOREPULSERESET)
+        {
+            KillTimer(TIMER_ID_IGNOREPULSERESET);
+            IgnorePulse = FALSE;
+        }
+        return 0;
     }
 
     LRESULT OnNcActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
@@ -3479,7 +3553,7 @@ HandleTrayContextMenu:
         MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
         MESSAGE_HANDLER(WM_SIZE, OnSize)
         MESSAGE_HANDLER(WM_CREATE, OnCreate)
-        /*MESSAGE_HANDLER(WM_DESTROY, OnDestroy)*/
+        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
         MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
         MESSAGE_HANDLER(WM_COMMAND, OnCommand)
         MESSAGE_HANDLER(WM_SYSCOMMAND, OnSysCommand)
@@ -3512,6 +3586,7 @@ HandleTrayContextMenu:
         MESSAGE_HANDLER(TWM_OPENSTARTMENU, OnOpenStartMenu)
         MESSAGE_HANDLER(TWM_DOEXITWINDOWS, OnDoExitWindows)
         MESSAGE_HANDLER(TWM_GETTASKSWITCH, OnGetTaskSwitch)
+        MESSAGE_HANDLER(TWM_PULSE, OnPulse)
     ALT_MSG_MAP(1)
     END_MSG_MAP()
 
index b02517e..44b3e54 100644 (file)
@@ -29,6 +29,7 @@
 #include <winnls.h>
 #include <winuser.h>
 #include <tchar.h>
+#include <undocuser.h> // For WM_POPUPSYSTEMMENU
 
 #include "resource.h"
 
@@ -299,9 +300,57 @@ LPSTR DuplicateToMultiByte(LPCTSTR lptString, size_t nBufferSize)
     return lpString;
 }
 
+typedef struct
+{
+    HWND hwndOwner;
+    HWND hwndTarget;
+} FIND_OWNED, *PFIND_OWNED;
+
+static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
+{
+    PFIND_OWNED pFindOwned = (PFIND_OWNED)lParam;
+    if (pFindOwned->hwndOwner == GetWindow(hwnd, GW_OWNER))
+    {
+        pFindOwned->hwndTarget = hwnd;
+        return FALSE;
+    }
+    return TRUE;
+}
+
 LRESULT CALLBACK EmptyWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
-    return DefWindowProc(hWnd, uMsg, wParam, lParam);
+    switch (uMsg)
+    {
+        case WM_POPUPSYSTEMMENU:
+        case WM_SYSCOMMAND:
+        {
+            /* Find the owned window */
+            FIND_OWNED FindOwned = { hWnd, NULL };
+            EnumWindows(EnumWindowsProc, (LPARAM)&FindOwned);
+            /* Forward message */
+            if (FindOwned.hwndTarget)
+                PostMessageW(FindOwned.hwndTarget, uMsg, wParam, lParam);
+            break;
+        }
+        case WM_ACTIVATE:
+        {
+            /* Find the owned window */
+            FIND_OWNED FindOwned = { hWnd, NULL };
+            EnumWindows(EnumWindowsProc, (LPARAM)&FindOwned);
+            if (FindOwned.hwndTarget)
+            {
+                if (LOWORD(wParam) != WA_INACTIVE) /* To be activated */
+                {
+                    SetActiveWindow(FindOwned.hwndTarget);
+                    return 0;
+                }
+            }
+            /* Fall through */
+        }
+        default:
+            return DefWindowProc(hWnd, uMsg, wParam, lParam);
+    }
+    return 0;
 }
 
 // Registers a minimal window class for passing to the dll function