We now support [Set|Get]CurrentProcessExplicitAppUserModelID. This is a basic building block for supporting Win7-like matching of shortcuts to open windows. --- dlls/shell32/shell32.spec | 1 + dlls/shell32/shell32_main.c | 109 +++++++++++++++++++++++++++++++++- dlls/shell32/tests/Makefile.in | 1 + dlls/shell32/tests/appusermodel.c | 121 ++++++++++++++++++++++++++++++++++++++ include/shobjidl.idl | 6 ++ include/winbase.h | 2 + 6 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 dlls/shell32/tests/appusermodel.c
diff --git a/dlls/shell32/shell32.spec b/dlls/shell32/shell32.spec index ed34919..1aa2c6b 100644 --- a/dlls/shell32/shell32.spec +++ b/dlls/shell32/shell32.spec @@ -326,6 +326,7 @@ @ stub RealShellExecuteW @ stdcall RegenerateUserEnvironment(ptr long) @ stdcall SetCurrentProcessExplicitAppUserModelID(wstr) +@ stdcall GetCurrentProcessExplicitAppUserModelID(ptr) @ stdcall SHAddToRecentDocs (long ptr) @ stdcall SHAppBarMessage(long ptr) @ stdcall SHAssocEnumHandlers(wstr long ptr) diff --git a/dlls/shell32/shell32_main.c b/dlls/shell32/shell32_main.c index cabf5e4..886cdfb 100644 --- a/dlls/shell32/shell32_main.c +++ b/dlls/shell32/shell32_main.c @@ -40,6 +40,7 @@ #include "rpcproxy.h" #include "shlwapi.h" #include "propsys.h" +#include "winternl.h"
#include "undocshell.h" #include "pidl.h" @@ -1370,11 +1371,115 @@ HRESULT WINAPI SHGetLocalizedName(LPCWSTR path, LPWSTR module, UINT size, INT *r
/*********************************************************************** * SetCurrentProcessExplicitAppUserModelID (SHELL32.@) + * + * Sets the AppUserModelID for the current process. + * + * An AppUserModelID is a unique identifier like + * Company.ProductName.SubProduct.Version + * where the SubProduct and Version parts are optional, and no spaces + * are allowed (wine does not currently enforce this). + * The maximum length is 128 characters (including the null-terminator). + * + * AppUserModelIDs can be used to reliably match application windows + * to shortcuts, similar to the X11 WM_CLASS property. + * + * Each process can have an associated AppUserModelID which has to be + * set using this API call before any windows are created, and each + * window can have a custom AppUserModelID which overrides the process- + * wide one (see: SHGetPropertyStoreForWindow). */ HRESULT WINAPI SetCurrentProcessExplicitAppUserModelID(PCWSTR appid) { - FIXME("%s: stub\n", debugstr_w(appid)); - return E_NOTIMPL; + int len; + RTL_USER_PROCESS_PARAMETERS *params; + static WCHAR *id_buffer = NULL; + + if (!appid) + { + return E_INVALIDARG; + } + + len = lstrlenW(appid); + + if (len > 127) + { + return E_INVALIDARG; + } + + RtlAcquirePebLock(); + + params = NtCurrentTeb()->Peb->ProcessParameters; + + /* FIXME: we leak the old buffer, and the new one + * if shell32.dll is unloaded and then loaded again */ + if (!id_buffer) + { + id_buffer = HeapAlloc(GetProcessHeap(), 0, 128 * sizeof(WCHAR)); + + if (id_buffer == NULL) + { + RtlReleasePebLock(); + + return E_OUTOFMEMORY; + } + + params->WindowTitle.Buffer = id_buffer; + params->WindowTitle.MaximumLength = 128 * sizeof(WCHAR); + } + + memcpy(params->WindowTitle.Buffer, appid, (len + 1) * sizeof(WCHAR)); + + params->WindowTitle.Length = len * sizeof(WCHAR); + params->dwFlags &= ~STARTF_TITLEISLINKNAME; + params->dwFlags |= STARTF_TITLEISAPPID; + + RtlReleasePebLock(); + + return S_OK; +} + +/*********************************************************************** + * GetCurrentProcessExplicitAppUserModelID (SHELL32.@) + * + * Get the AppUserModelID for the current process if it was already set. + */ +HRESULT WINAPI GetCurrentProcessExplicitAppUserModelID(PWSTR *appid) +{ + HRESULT ret = S_OK; + RTL_USER_PROCESS_PARAMETERS *params; + + if (!appid) + { + return E_INVALIDARG; + } + + *appid = NULL; + + RtlAcquirePebLock(); + + params = NtCurrentTeb()->Peb->ProcessParameters; + + if ((params->dwFlags & STARTF_TITLEISAPPID) > 0) + { + *appid = CoTaskMemAlloc(params->WindowTitle.Length + sizeof(WCHAR)); + if (*appid) + { + memcpy(*appid, params->WindowTitle.Buffer, params->WindowTitle.Length); + (*appid)[params->WindowTitle.Length / sizeof(WCHAR)] = 0; + } + else + { + ret = E_OUTOFMEMORY; + } + } + else + { + ret = E_FAIL; + } + + RtlReleasePebLock(); + + return ret; }
/*********************************************************************** diff --git a/dlls/shell32/tests/Makefile.in b/dlls/shell32/tests/Makefile.in index cfe0c50..9a37e2e 100644 --- a/dlls/shell32/tests/Makefile.in +++ b/dlls/shell32/tests/Makefile.in @@ -3,6 +3,7 @@ IMPORTS = shell32 ole32 oleaut32 user32 advapi32
C_SRCS = \ appbar.c \ + appusermodel.c \ assoc.c \ autocomplete.c \ brsfolder.c \ diff --git a/dlls/shell32/tests/appusermodel.c b/dlls/shell32/tests/appusermodel.c new file mode 100644 index 0000000..dd83ef8 --- /dev/null +++ b/dlls/shell32/tests/appusermodel.c @@ -0,0 +1,121 @@ +/* Unit test suite for the AppUserModelID support + * + * Copyright 2015 Jonas Kümmerlin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS + +#include "windows.h" +#include "shlguid.h" +#include "shobjidl.h" +#include "shlobj.h" +#include "shellapi.h" +#include "wine/test.h" + +#include "shell32_test.h" + +static HRESULT (WINAPI *pSetCurrentProcessExplicitAppUserModelID)(const WCHAR *id); +static HRESULT (WINAPI *pGetCurrentProcessExplicitAppUserModelID)(WCHAR **id); + +static void test_process_aum_id(void) +{ + HRESULT hr; + WCHAR *received = NULL; + int i; + + /* MSDN claims the maximum length to be 128 chars, but in reality, + it is 127 chars + terminating NUL byte */ + WCHAR test_id[129] = { 'W','i','n','e','.','T','e','s','t','.','A' }; + + /* fill the remainder with 'a' to test the maximum length */ + for (i = 11; i < 128; ++i) + test_id[i] = 'a'; + test_id[128] = 0; + + if (!pGetCurrentProcessExplicitAppUserModelID + || !pSetCurrentProcessExplicitAppUserModelID) + { + win_skip("SetCurrentProcessExplicitAppUserModelID is not available"); + return; + } + + /* Receiving it without setting will fail with E_FAIL */ + hr = pGetCurrentProcessExplicitAppUserModelID(&received); + ok(hr == E_FAIL, "receiving the AppUserModelID unexpected hr=%08x\n", hr); + ok(received == NULL, "AppUserModelID '%s' has been returned even though none was set\n", + wine_dbgstr_w(received)); + + if (received) + { + CoTaskMemFree(received); + received = NULL; + } + + /* Passing NULL pointers segfaults on Win7 */ + if (0) + { + hr = pSetCurrentProcessExplicitAppUserModelID(NULL); + ok(hr == E_POINTER, "unexpected hr=%08x\n", hr); + + hr = pGetCurrentProcessExplicitAppUserModelID(NULL); + ok(hr == E_POINTER, "unexpected hr=%08x\n", hr); + } + + /* Test with 128 chars + null byte */ + hr = pSetCurrentProcessExplicitAppUserModelID(test_id); + ok(hr == E_INVALIDARG, "unexpexted hr=0x%08x\n", hr); + + /* Test again, this time with 127 chars + null byte */ + test_id[127] = 0; + hr = pSetCurrentProcessExplicitAppUserModelID(test_id); + ok(hr == S_OK, "unexpexted hr=0x%08x\n", hr); + + /* Set a different value. */ + test_id[10] = 'B'; + hr = pSetCurrentProcessExplicitAppUserModelID(test_id); + ok(hr == S_OK, "unexpexted hr=0x%08x\n", hr); + + /* Retrieve it again and compare it to the latest value set. */ + hr = pGetCurrentProcessExplicitAppUserModelID(&received); + ok(hr == S_OK, "GetCurrentProcessExplicitAppUserModelID failed (0x%08x)\n", hr); + ok(received != NULL, "GetCurrentProcessExplicitAppUserModelID returned a NULL ID\n"); + + if (received) + { + ok(!lstrcmpW(received, test_id), + "Expected '%s', but got '%s'\n", + wine_dbgstr_w(test_id), wine_dbgstr_w(received)); + + CoTaskMemFree(received); + } +} + +/* TODO: Test the window property store (SHGetPropertyStoreForWindow) */ + +#define RESOLVE(hDll, proc) p##proc = (void*)GetProcAddress(hDll, #proc) + +START_TEST(appusermodel) +{ + HMODULE hShell32; + + hShell32 = GetModuleHandleA("shell32.dll"); + + RESOLVE(hShell32, SetCurrentProcessExplicitAppUserModelID); + RESOLVE(hShell32, GetCurrentProcessExplicitAppUserModelID); + + test_process_aum_id(); +} diff --git a/include/shobjidl.idl b/include/shobjidl.idl index 455045d..d1fee7c 100644 --- a/include/shobjidl.idl +++ b/include/shobjidl.idl @@ -3638,6 +3638,12 @@ typedef enum ASSOC_FILTER cpp_quote("HRESULT WINAPI SHAssocEnumHandlers(PCWSTR extra, ASSOC_FILTER filter, IEnumAssocHandlers **handlersenum);")
/***************************************************************************** + * AppUserModelID support + */ +cpp_quote("HRESULT WINAPI SetCurrentProcessExplicitAppUserModelID(PCWSTR AppID);") +cpp_quote("HRESULT WINAPI GetCurrentProcessExplicitAppUserModelID(PWSTR *AppID);") + +/***************************************************************************** * ShellObjects typelibrary */ [ diff --git a/include/winbase.h b/include/winbase.h index a1859b0..edbe04f 100644 --- a/include/winbase.h +++ b/include/winbase.h @@ -579,6 +579,8 @@ typedef VOID (CALLBACK *LPOVERLAPPED_COMPLETION_ROUTINE)(DWORD,DWORD,LPOVERLAPPE #define STARTF_FORCEOFFFEEDBACK 0x00000080 #define STARTF_USESTDHANDLES 0x00000100 #define STARTF_USEHOTKEY 0x00000200 +#define STARTF_TITLEISLINKNAME 0x00000800 +#define STARTF_TITLEISAPPID 0x00001000
typedef struct _STARTUPINFOA{ DWORD cb; /* 00: size of struct */