[SHELL32] Make Control Panel applets start in single instance (#4405) (#4405)
authorRaymond Czerny <102076097+RaymondCzerny@users.noreply.github.com>
Thu, 7 Apr 2022 12:58:13 +0000 (14:58 +0200)
committerGitHub <noreply@github.com>
Thu, 7 Apr 2022 12:58:13 +0000 (15:58 +0300)
In MS Windows all control panel applets are started in single instance.
This prevents conflicts of concurrent accesses to the configuration data.

Before starting applets, look up the dialog window of the existing instance
by checking several atoms, such as the applet path, CPLName, and CPLFlags.
If the dialog is found, it's brought to the foreground, and a new applet
instance not started.

CORE-7921 CORE-17025

Signed-off-by: Raymond Czerny <chip@raymisoft.de>
Reviewed-by: Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Reviewed-by: Joachim Henze <joachim.henze@reactos.org>
Reviewed-by: Mark Jansen <mark.jansen@reactos.org>
dll/win32/shell32/wine/control.c

index 272747d..385f985 100644 (file)
@@ -2,6 +2,7 @@
  *
  * Copyright 2001 Eric Pouech
  * Copyright 2008 Owen Rudge
+ * Copyright 2022 Raymond Czerny
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -736,6 +737,75 @@ static     void    Control_DoWindow(CPanel* panel, HWND hWnd, HINSTANCE hInst)
 }
 #endif
 
+#ifdef __REACTOS__
+
+/** Structure for in and out data when
+ *  search for the cpl dialog of first instance
+ */
+typedef struct tagAppDlgFindData
+{
+    PCWSTR    szAppFile;  /**< Full path to applet library as search parameter */
+    UINT_PTR  sAppletNo;  /**< Number of applet in a system control library as search parameter */
+    ATOM      aCPLName;   /**< to read window property 'CPLName' */
+    ATOM      aCPLFlags;  /**< to read window property 'CPLFlags'*/
+    HWND      hRunDLL;    /**< to skip self instance */
+    HWND      hDlgResult; /**< Returned dialog handle or NULL if not found */
+} AppDlgFindData;
+
+/**
+ * Callback function to search applet dialog
+ * @param hwnd A handle to a top-level window.
+ * @param lParam Pointer of AppDlgFindData
+ * @return TRUE: continue enumeration, FALSE: stop enumeration
+ */
+static BOOL CALLBACK
+Control_EnumWinProc(
+    _In_ HWND   hwnd,
+    _In_ LPARAM lParam)
+{
+    AppDlgFindData* pData = (AppDlgFindData*)lParam;
+    WCHAR szClassName[256] = L"";
+
+    if (pData->hRunDLL == hwnd)
+    {
+        // Skip self instance
+        return TRUE;
+    }
+
+    if (GetClassNameW(hwnd, szClassName, _countof(szClassName)))
+    {
+        // Note: A comparison on identical is not possible, the class names are different.
+        // ReactOS: 'rundll32_window'
+        // WinXP: 'RunDLL'
+        // other OS: not checked
+        if (StrStrIW(szClassName, L"rundll32") != NULL)
+        {
+            UINT_PTR sAppletNo;
+
+            sAppletNo = (UINT_PTR)GetPropW(hwnd, (LPTSTR)MAKEINTATOM(pData->aCPLFlags));
+            if (sAppletNo == pData->sAppletNo)
+            {
+                HANDLE hRes;
+                WCHAR szAppFile[MAX_PATH];
+
+                hRes = GetPropW(hwnd, (LPTSTR)MAKEINTATOM(pData->aCPLName));
+                GlobalGetAtomNameW((ATOM)HandleToUlong(hRes), szAppFile, _countof(szAppFile));
+                if (wcscmp(szAppFile, pData->szAppFile) == 0)
+                {
+                    HWND hDialog = GetLastActivePopup(hwnd);
+                    if (IsWindow(hDialog))
+                    {
+                        pData->hDlgResult = hDialog;
+                        return FALSE; // stop enumeration
+                    }
+                }
+            }
+        }
+    }
+    return TRUE; // continue enumeration
+}
+#endif /* __REACTOS__ */
+
 static void    Control_DoLaunch(CPanel* panel, HWND hWnd, LPCWSTR wszCmd)
    /* forms to parse:
     *  foo.cpl,@sp,str
@@ -828,6 +898,10 @@ static     void    Control_DoLaunch(CPanel* panel, HWND hWnd, LPCWSTR wszCmd)
 #ifdef __REACTOS__
     ULONG_PTR cookie;
     BOOL bActivated;
+    ATOM aCPLName;
+    ATOM aCPLFlags;
+    ATOM aCPLPath;
+    AppDlgFindData findData;
 #endif
         /* we've been given a textual parameter (or none at all) */
         if (sp == -1) {
@@ -846,10 +920,51 @@ static    void    Control_DoLaunch(CPanel* panel, HWND hWnd, LPCWSTR wszCmd)
 
 #ifdef __REACTOS__
         bActivated = (applet->hActCtx != INVALID_HANDLE_VALUE ? ActivateActCtx(applet->hActCtx, &cookie) : FALSE);
+
+        aCPLPath = GlobalFindAtomW(applet->cmd);
+        if (!aCPLPath)
+        {
+            aCPLPath = GlobalAddAtomW(applet->cmd);
+        }
+
+        aCPLName = GlobalFindAtomW(L"CPLName");
+        if (!aCPLName)
+        {
+            aCPLName = GlobalAddAtomW(L"CPLName");
+        }
+
+        aCPLFlags = GlobalFindAtomW(L"CPLFlags");
+        if (!aCPLFlags)
+        {
+            aCPLFlags = GlobalAddAtomW(L"CPLFlags");
+        }
+
+        findData.szAppFile = applet->cmd;
+        findData.sAppletNo = (UINT_PTR)(sp + 1);
+        findData.aCPLName = aCPLName;
+        findData.aCPLFlags = aCPLFlags;
+        findData.hRunDLL = applet->hWnd;
+        findData.hDlgResult = NULL;
+        // Find the dialog of this applet in the first instance.
+        // Note: The simpler functions "FindWindow" or "FindWindowEx" does not find this type of dialogs.
+        EnumWindows(Control_EnumWinProc, (LPARAM)&findData);
+        if (findData.hDlgResult)
+        {
+            BringWindowToTop(findData.hDlgResult);
+        }
+        else
+        {
+            SetPropW(applet->hWnd, (LPTSTR)MAKEINTATOM(aCPLName), (HANDLE)MAKEINTATOM(aCPLPath));
+            SetPropW(applet->hWnd, (LPTSTR)MAKEINTATOM(aCPLFlags), UlongToHandle(sp + 1));
 #endif
 
         if (!applet->proc(applet->hWnd, CPL_STARTWPARMSW, sp, (LPARAM)extraPmts))
             applet->proc(applet->hWnd, CPL_DBLCLK, sp, applet->info[sp].data);
+#ifdef __REACTOS__
+            RemovePropW(applet->hWnd, applet->cmd);
+            GlobalDeleteAtom(aCPLPath);
+        }
+#endif
 
         Control_UnloadApplet(applet);