SHELL32: Invoke shortcuts through IContextMenu, rather than trying to access them directly.

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

SHELL32: Invoke shortcuts through IContextMenu, rather than trying to access them directly.

Mike McCormack

This patch changes the way shortcuts are invoked by the shell.  The
original Wine ShellExecute code "knew" too much about .lnk files,
treating the in a special way.  This caused a number of problems,
because .lnk files didn't work the same way as other files.

I wrote a description of the way it should work in the Wine Wiki:

http://wiki.winehq.org/ShellLink

Mike


ChangeLog:
Invoke shortcuts through IContextMenu, rather than trying to access them
directly.

42820026e7f476cb5b7916987dc411ea58350c7a
diff --git a/dlls/shell32/shlexec.c b/dlls/shell32/shlexec.c
--- a/dlls/shell32/shlexec.c
+++ b/dlls/shell32/shlexec.c
@@ -267,70 +267,6 @@ HRESULT SHELL_GetPathFromIDListForExecut
 }
 
 /*************************************************************************
- * SHELL_ResolveShortCutW [Internal]
- * read shortcut file at 'wcmd'
- */
-static HRESULT SHELL_ResolveShortCutW(LPWSTR wcmd, LPWSTR wargs, LPWSTR wdir, HWND hwnd, LPCWSTR lpVerb, int* pshowcmd, LPITEMIDLIST* ppidl)
-{
-    IShellFolder* psf;
-
-    HRESULT hr = SHGetDesktopFolder(&psf);
-
-    *ppidl = NULL;
-
-    if (SUCCEEDED(hr)) {
- LPITEMIDLIST pidl;
- ULONG l;
-
-     hr = IShellFolder_ParseDisplayName(psf, 0, 0, wcmd, &l, &pidl, 0);
-
- if (SUCCEEDED(hr)) {
-    IShellLinkW* psl;
-
-    hr = IShellFolder_GetUIObjectOf(psf, NULL, 1, (LPCITEMIDLIST*)&pidl, &IID_IShellLinkW, NULL, (LPVOID*)&psl);
-
-    if (SUCCEEDED(hr)) {
- hr = IShellLinkW_Resolve(psl, hwnd, 0);
-
- if (SUCCEEDED(hr)) {
-    hr = IShellLinkW_GetPath(psl, wcmd, MAX_PATH, NULL, SLGP_UNCPRIORITY);
-
-    if (SUCCEEDED(hr)) {
- if (!*wcmd) {
-    /* We could not translate the PIDL in the shell link into a valid file system path - so return the PIDL instead. */
-    hr = IShellLinkW_GetIDList(psl, ppidl);
-
-    if (SUCCEEDED(hr) && *ppidl) {
- /* We got a PIDL instead of a file system path - try to translate it. */
- if (SHGetPathFromIDListW(*ppidl, wcmd)) {
-    SHFree(*ppidl);
-    *ppidl = NULL;
- }
-    }
- }
-
- if (SUCCEEDED(hr)) {
-    /* get command line arguments, working directory and display mode if available */
-    IShellLinkW_GetWorkingDirectory(psl, wdir, MAX_PATH);
-    IShellLinkW_GetArguments(psl, wargs, MAX_PATH);
-    IShellLinkW_GetShowCmd(psl, pshowcmd);
- }
-    }
- }
-
- IShellLinkW_Release(psl);
-    }
-
-    SHFree(pidl);
- }
-
- IShellFolder_Release(psf);
-    }
-
-    return hr;
-}
-
-/*************************************************************************
  * SHELL_ExecuteW [Internal]
  *
  */
@@ -965,6 +901,262 @@ HINSTANCE WINAPI FindExecutableW(LPCWSTR
     return (HINSTANCE)retval;
 }
 
+/* FIXME: is this already implemented somewhere else? */
+static HKEY ShellExecute_GetClassKey( LPSHELLEXECUTEINFOW sei )
+{
+    LPCWSTR ext = NULL, lpClass = NULL;
+    LPWSTR cls = NULL;
+    DWORD type = 0, sz = 0;
+    HKEY hkey = 0;
+    LONG r;
+
+    if (sei->fMask & SEE_MASK_CLASSALL)
+        return sei->hkeyClass;
+
+    if (sei->fMask & SEE_MASK_CLASSNAME)
+        lpClass = sei->lpClass;
+    else
+    {
+        ext = PathFindExtensionW( sei->lpFile );
+        TRACE("ext = %s\n", debugstr_w( ext ) );
+        if (!ext)
+            return hkey;
+
+        r = RegOpenKeyW( HKEY_CLASSES_ROOT, ext, &hkey );
+        if (r != ERROR_SUCCESS )
+            return hkey;
+
+        r = RegQueryValueExW( hkey, NULL, 0, &type, NULL, &sz );
+        if ( r == ERROR_SUCCESS && type == REG_SZ )
+        {
+            sz += sizeof (WCHAR);
+            cls = HeapAlloc( GetProcessHeap(), 0, sz );
+            cls[0] = 0;
+            RegQueryValueExW( hkey, NULL, 0, &type, (LPBYTE) cls, &sz );
+        }
+
+        RegCloseKey( hkey );
+        lpClass = cls;
+    }
+
+    TRACE("class = %s\n", debugstr_w(lpClass) );
+
+    hkey = 0;
+    if ( lpClass )
+        RegOpenKeyW( HKEY_CLASSES_ROOT, lpClass, &hkey );
+
+    HeapFree( GetProcessHeap(), 0, cls );
+
+    return hkey;
+}
+
+static IDataObject *shellex_get_dataobj( LPSHELLEXECUTEINFOW sei )
+{
+    LPCITEMIDLIST pidllast = NULL;
+    IDataObject *dataobj = NULL;
+    IShellFolder *shf = NULL;
+    LPITEMIDLIST pidl = NULL;
+    HRESULT r;
+
+    if (sei->fMask & SEE_MASK_CLASSALL)
+        pidl = sei->lpIDList;
+    else
+    {
+        WCHAR fullpath[MAX_PATH];
+
+        fullpath[0] = 0;
+        r = GetFullPathNameW( sei->lpFile, MAX_PATH, fullpath, NULL );
+        if (!r)
+            goto end;
+
+        pidl = ILCreateFromPathW( fullpath );
+    }
+
+    r = SHBindToParent( pidl, &IID_IShellFolder, (LPVOID*)&shf, &pidllast );
+    if ( FAILED( r ) )
+        goto end;
+
+    IShellFolder_GetUIObjectOf( shf, NULL, 1, &pidllast,
+                                &IID_IDataObject, NULL, (LPVOID*) &dataobj );
+
+end:
+    if ( pidl != sei->lpIDList )
+        ILFree( pidl );
+    if ( shf )
+        IShellFolder_Release( shf );
+    return dataobj;
+}
+
+static HRESULT shellex_run_context_menu_default( IShellExtInit *obj,
+                                                 LPSHELLEXECUTEINFOW sei )
+{
+    IContextMenu *cm = NULL;
+    CMINVOKECOMMANDINFOEX ici;
+    MENUITEMINFOW info;
+    WCHAR string[0x80];
+    INT i, n, def = -1;
+    HMENU hmenu = 0;
+    HRESULT r;
+
+    TRACE("%p %p\n", obj, sei );
+
+    r = IShellExtInit_QueryInterface( obj, &IID_IContextMenu, (LPVOID*) &cm );
+    if ( FAILED( r ) )
+        return r;
+
+    hmenu = CreateMenu();
+    if ( !hmenu )
+        goto end;
+
+    /* the number of the last menu added is returned in r */
+    r = IContextMenu_QueryContextMenu( cm, hmenu, 0, 0x20, 0x7fff, CMF_DEFAULTONLY );
+    if ( FAILED( r ) )
+        goto end;
+
+    n = GetMenuItemCount( hmenu );
+    for ( i = 0; i < n; i++ )
+    {
+        memset( &info, 0, sizeof info );
+        info.cbSize = sizeof info;
+        info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_STATE | MIIM_DATA | MIIM_ID;
+        info.dwTypeData = string;
+        info.cch = sizeof string;
+        string[0] = 0;
+        GetMenuItemInfoW( hmenu, i, TRUE, &info );
+
+        TRACE("menu %d %s %08x %08lx %08x %08x\n", i, debugstr_w(string),
+            info.fState, info.dwItemData, info.fType, info.wID );
+        if ( ( !sei->lpVerb && (info.fState & MFS_DEFAULT) ) ||
+             ( sei->lpVerb && !lstrcmpiW( sei->lpVerb, string ) ) )
+        {
+            def = i;
+            break;
+        }
+    }
+
+    r = E_FAIL;
+    if ( def == -1 )
+        goto end;
+
+    memset( &ici, 0, sizeof ici );
+    ici.cbSize = sizeof ici;
+    ici.fMask = CMIC_MASK_UNICODE;
+    ici.nShow = sei->nShow;
+    ici.lpVerb = MAKEINTRESOURCEA( def );
+    ici.hwnd = sei->hwnd;
+    ici.lpParametersW = sei->lpParameters;
+    
+    r = IContextMenu_InvokeCommand( cm, (LPCMINVOKECOMMANDINFO) &ici );
+
+    TRACE("invoke command returned %08lx\n", r );
+
+end:
+    if ( hmenu )
+        DestroyMenu( hmenu );
+    if ( cm )
+        IContextMenu_Release( cm );
+    return r;
+}
+
+static HRESULT shellex_load_object_and_run( HKEY hkey, LPCGUID guid, LPSHELLEXECUTEINFOW sei )
+{
+    IDataObject *dataobj = NULL;
+    IObjectWithSite *ows = NULL;
+    IShellExtInit *obj = NULL;
+    HRESULT r;
+
+    TRACE("%p %s %p\n", hkey, debugstr_guid( guid ), sei );
+
+    r = CoInitialize( NULL );
+    if ( FAILED( r ) )
+        goto end;
+
+    r = CoCreateInstance( guid, NULL, CLSCTX_INPROC_SERVER,
+                           &IID_IShellExtInit, (LPVOID*)&obj );
+    if ( FAILED( r ) )
+    {
+        ERR("failed %08lx\n", r );
+        goto end;
+    }
+
+    dataobj = shellex_get_dataobj( sei );
+    if ( !dataobj )
+    {
+        ERR("failed to get data object\n");
+        goto end;
+    }
+
+    r = IShellExtInit_Initialize( obj, NULL, dataobj, hkey );
+    if ( FAILED( r ) )
+        goto end;
+
+    r = IShellExtInit_QueryInterface( obj, &IID_IObjectWithSite, (LPVOID*) &ows );
+    if ( FAILED( r ) )
+        goto end;
+
+    IObjectWithSite_SetSite( ows, NULL );
+
+    r = shellex_run_context_menu_default( obj, sei );
+
+end:
+    if ( ows )
+        IObjectWithSite_Release( ows );
+    if ( dataobj )
+        IDataObject_Release( dataobj );
+    if ( obj )
+        IShellExtInit_Release( obj );
+    CoUninitialize();
+    return r;
+}
+
+
+/*************************************************************************
+ * ShellExecute_FromContextMenu [Internal]
+ */
+static LONG ShellExecute_FromContextMenu( LPSHELLEXECUTEINFOW sei )
+{
+    static const WCHAR szcm[] = { 's','h','e','l','l','e','x','\\',
+        'C','o','n','t','e','x','t','M','e','n','u','H','a','n','d','l','e','r','s',0 };
+    HKEY hkey, hkeycm = 0;
+    WCHAR szguid[39];
+    HRESULT hr;
+    GUID guid;
+    DWORD i;
+    LONG r;
+
+    TRACE("%s\n", debugstr_w(sei->lpFile) );
+
+    hkey = ShellExecute_GetClassKey( sei );
+    if ( !hkey )
+        return ERROR_FUNCTION_FAILED;
+
+    r = RegOpenKeyW( hkey, szcm, &hkeycm );
+    if ( r == ERROR_SUCCESS )
+    {
+        i = 0;
+        while ( 1 )
+        {
+            r = RegEnumKeyW( hkeycm, i++, szguid, 39 );
+            if ( r != ERROR_SUCCESS )
+                break;
+            r = ERROR_FUNCTION_FAILED;
+            hr = CLSIDFromString( szguid, &guid );
+            if ( FAILED( hr ) )
+                break;
+            r = ERROR_SUCCESS;
+            /* stop at the first one that succeeds in running */
+            hr = shellex_load_object_and_run( hkey, &guid, sei );
+            if ( SUCCEEDED( hr ) )
+                break;
+        }
+        RegCloseKey( hkeycm );
+    }
+
+    if ( hkey != sei->hkeyClass )
+        RegCloseKey( hkey );
+    return r;
+}
+
 /*************************************************************************
  * ShellExecuteExW32 [Internal]
  */
@@ -975,7 +1167,6 @@ BOOL WINAPI ShellExecuteExW32 (LPSHELLEX
     static const WCHAR wWww[] = {'w','w','w',0};
     static const WCHAR wFile[] = {'f','i','l','e',0};
     static const WCHAR wHttp[] = {'h','t','t','p',':','/','/',0};
-    static const WCHAR wExtLnk[] = {'.','l','n','k',0};
     static const WCHAR wExplorer[] = {'e','x','p','l','o','r','e','r','.','e','x','e',0};
     static const DWORD unsupportedFlags =
         SEE_MASK_INVOKEIDLIST  | SEE_MASK_ICON         | SEE_MASK_HOTKEY |
@@ -992,7 +1183,6 @@ BOOL WINAPI ShellExecuteExW32 (LPSHELLEX
     UINT_PTR retval = 31;
     WCHAR wcmd[1024];
     WCHAR buffer[MAX_PATH];
-    const WCHAR* ext;
     BOOL done;
 
     /* make a local copy of the LPSHELLEXECUTEINFO structure and work with this from now on */
@@ -1065,6 +1255,9 @@ BOOL WINAPI ShellExecuteExW32 (LPSHELLEX
         TRACE("-- idlist=%p (%s)\n", sei_tmp.lpIDList, debugstr_w(wszApplicationName));
     }
 
+    if ( ERROR_SUCCESS == ShellExecute_FromContextMenu( &sei_tmp ) )
+        return TRUE;
+
     if (sei_tmp.fMask & SEE_MASK_CLASSALL)
     {
  /* launch a document by fileclass like 'WordPad.Document.1' */
@@ -1093,78 +1286,6 @@ BOOL WINAPI ShellExecuteExW32 (LPSHELLEX
             return FALSE;
     }
 
-
-    /* resolve shell shortcuts */
-    ext = PathFindExtensionW(sei_tmp.lpFile);
-
-    if (ext && !strncmpiW(ext, wExtLnk, sizeof(wExtLnk) / sizeof(WCHAR) - 1) &&
-        (ext[sizeof(wExtLnk) / sizeof(WCHAR) - 1] == '\0' ||
-         (sei_tmp.lpFile[0] == '"' && ext[sizeof(wExtLnk) / sizeof(WCHAR) - 1] == '"'))) /* or check for: shell_attribs & SFGAO_LINK */
-    {
- HRESULT hr;
- BOOL Quoted;
-
- if (wszApplicationName[0] == '"')
- {
-    if (wszApplicationName[strlenW(wszApplicationName) - 1] == '"')
-    {
- wszApplicationName[strlenW(wszApplicationName) - 1] = '\0';
- Quoted = TRUE;
-    }
-    else
-    {
- Quoted = FALSE;
-    }
- }
- else
- {
-    Quoted = FALSE;
- }
- /* expand paths before reading shell link */
- if (ExpandEnvironmentStringsW(Quoted ? sei_tmp.lpFile + 1 : sei_tmp.lpFile, buffer, MAX_PATH))
-    lstrcpyW(Quoted ? wszApplicationName + 1 : wszApplicationName/*sei_tmp.lpFile*/, buffer);
-
- if (*sei_tmp.lpParameters)
-    if (ExpandEnvironmentStringsW(sei_tmp.lpParameters, buffer, MAX_PATH))
- lstrcpyW(wszParameters/*sei_tmp.lpParameters*/, buffer);
-
- hr = SHELL_ResolveShortCutW((LPWSTR)(Quoted ? sei_tmp.lpFile + 1 : sei_tmp.lpFile),
-                            (LPWSTR)sei_tmp.lpParameters, (LPWSTR)sei_tmp.lpDirectory,
-    sei_tmp.hwnd, sei_tmp.lpVerb?sei_tmp.lpVerb:wszEmpty, &sei_tmp.nShow, (LPITEMIDLIST*)&sei_tmp.lpIDList);
- if (Quoted)
- {
-    wszApplicationName[strlenW(wszApplicationName) + 1] = '\0';
-    wszApplicationName[strlenW(wszApplicationName)] = '"';
- }
-
- if (sei->lpIDList)
-    sei->fMask |= SEE_MASK_IDLIST;
-
- if (SUCCEEDED(hr))
- {
-    /* repeat IDList processing if needed */
-    if (sei_tmp.fMask & SEE_MASK_IDLIST)
-    {
- IShellExecuteHookW* pSEH;
-
- HRESULT hr = SHBindToParent(sei_tmp.lpIDList, &IID_IShellExecuteHookW, (LPVOID*)&pSEH, NULL);
-
- if (SUCCEEDED(hr))
- {
-    hr = IShellExecuteHookW_Execute(pSEH, &sei_tmp);
-
-    IShellExecuteHookW_Release(pSEH);
-
-    if (hr == S_OK)
- return TRUE;
- }
-
- TRACE("-- idlist=%p (%s)\n", debugstr_w(sei_tmp.lpIDList), debugstr_w(sei_tmp.lpFile));
-    }
- }
-    }
-
-
     /* Has the IDList not yet been translated? */
     if (sei_tmp.fMask & SEE_MASK_IDLIST)
     {