Explorar o código

Windows shell extension: port to the new protocol

Olivier Goffart %!s(int64=8) %!d(string=hai) anos
pai
achega
883080b557

+ 11 - 27
shell_integration/windows/OCContextMenu/OCClientInterface.cpp

@@ -34,7 +34,7 @@ using namespace std;
 #define PIPE_TIMEOUT  5*1000 //ms
 #define PIPE_TIMEOUT  5*1000 //ms
 #define SOCK_BUFFER 4096
 #define SOCK_BUFFER 4096
 
 
-OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
+OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo(const std::wstring &files)
 {
 {
     auto pipename = CommunicationSocket::DefaultPipePath();
     auto pipename = CommunicationSocket::DefaultPipePath();
 
 
@@ -45,7 +45,8 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
     if (!socket.Connect(pipename)) {
     if (!socket.Connect(pipename)) {
         return {};
         return {};
     }
     }
-    socket.SendMsg(L"GET_STRINGS\n");
+    socket.SendMsg(L"GET_STRINGS:CONTEXT_MENU_TITLE\n");
+    socket.SendMsg((L"GET_MENU_ITEMS:" + files + L"\n").data());
 
 
     ContextMenuInfo info;
     ContextMenuInfo info;
     std::wstring response;
     std::wstring response;
@@ -60,16 +61,14 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
                 wstring stringName, stringValue;
                 wstring stringName, stringValue;
                 if (!StringUtil::extractChunks(response, stringName, stringValue))
                 if (!StringUtil::extractChunks(response, stringName, stringValue))
                     continue;
                     continue;
-                if (stringName == L"SHARE_MENU_TITLE")
-                    info.shareMenuTitle = move(stringValue);
-                else if (stringName == L"CONTEXT_MENU_TITLE")
+                if (stringName == L"CONTEXT_MENU_TITLE")
                     info.contextMenuTitle = move(stringValue);
                     info.contextMenuTitle = move(stringValue);
-                else if (stringName == L"COPY_PRIVATE_LINK_MENU_TITLE")
-                    info.copyLinkMenuTitle = move(stringValue);
-                else if (stringName == L"EMAIL_PRIVATE_LINK_MENU_TITLE")
-                    info.emailLinkMenuTitle = move(stringValue);
-            }
-            else if (StringUtil::begins_with(response, wstring(L"GET_STRINGS:END"))) {
+            } else if (StringUtil::begins_with(response, wstring(L"MENU_ITEM:"))) {
+                wstring commandName, flags, title;
+                if (!StringUtil::extractChunks(response, commandName, flags, title))
+                    continue;
+                info.menuItems.push_back({ commandName, flags, title });
+            } else if (StringUtil::begins_with(response, wstring(L"GET_MENU_ITEMS:END"))) {
                 break; // Stop once we completely received the last sent request
                 break; // Stop once we completely received the last sent request
             }
             }
         }
         }
@@ -81,22 +80,7 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
     return info;
     return info;
 }
 }
 
 
-void OCClientInterface::RequestShare(const std::wstring &path)
-{
-    SendRequest(L"SHARE", path);
-}
-
-void OCClientInterface::RequestCopyLink(const std::wstring &path)
-{
-    SendRequest(L"COPY_PRIVATE_LINK", path);
-}
-
-void OCClientInterface::RequestEmailLink(const std::wstring &path)
-{
-    SendRequest(L"EMAIL_PRIVATE_LINK", path);
-}
-
-void OCClientInterface::SendRequest(wchar_t *verb, const std::wstring &path)
+void OCClientInterface::SendRequest(const wchar_t *verb, const std::wstring &path)
 {
 {
     auto pipename = CommunicationSocket::DefaultPipePath();
     auto pipename = CommunicationSocket::DefaultPipePath();
 
 

+ 7 - 11
shell_integration/windows/OCContextMenu/OCClientInterface.h

@@ -46,18 +46,14 @@ public:
     struct ContextMenuInfo {
     struct ContextMenuInfo {
         std::vector<std::wstring> watchedDirectories;
         std::vector<std::wstring> watchedDirectories;
         std::wstring contextMenuTitle;
         std::wstring contextMenuTitle;
-        std::wstring shareMenuTitle;
-        std::wstring copyLinkMenuTitle;
-        std::wstring emailLinkMenuTitle;
+        struct MenuItem
+        {
+            std::wstring command, flags, title;
+        };
+        std::vector<MenuItem> menuItems;
     };
     };
-    static ContextMenuInfo FetchInfo();
-
-    static void RequestShare(const std::wstring &path);
-    static void RequestCopyLink(const std::wstring &path);
-    static void RequestEmailLink(const std::wstring &path);
-
-private:
-    static void SendRequest(wchar_t *verb, const std::wstring &path);
+    static ContextMenuInfo FetchInfo(const std::wstring &files);
+    static void SendRequest(const wchar_t *verb, const std::wstring &path);
 };
 };
 
 
 #endif //ABSTRACTSOCKETHANDLER_H
 #endif //ABSTRACTSOCKETHANDLER_H

+ 40 - 140
shell_integration/windows/OCContextMenu/OCContextMenu.cpp

@@ -22,13 +22,8 @@
 #include <shellapi.h>
 #include <shellapi.h>
 #include <StringUtil.h>
 #include <StringUtil.h>
 
 
-extern HINSTANCE g_hInst;
 extern long g_cDllRef;
 extern long g_cDllRef;
 
 
-#define IDM_SHARE 0
-#define IDM_COPYLINK 1
-#define IDM_EMAILLINK 2
-
 OCContextMenu::OCContextMenu(void) 
 OCContextMenu::OCContextMenu(void) 
     : m_cRef(1)
     : m_cRef(1)
 {
 {
@@ -40,23 +35,6 @@ OCContextMenu::~OCContextMenu(void)
     InterlockedDecrement(&g_cDllRef);
     InterlockedDecrement(&g_cDllRef);
 }
 }
 
 
-
-void OCContextMenu::OnVerbShare(HWND hWnd)
-{
-    OCClientInterface::RequestShare(std::wstring(m_szSelectedFile));
-}
-
-void OCContextMenu::OnVerbCopyLink(HWND hWnd)
-{
-    OCClientInterface::RequestCopyLink(std::wstring(m_szSelectedFile));
-}
-
-void OCContextMenu::OnVerbEmailLink(HWND hWnd)
-{
-    OCClientInterface::RequestEmailLink(std::wstring(m_szSelectedFile));
-}
-
-
 #pragma region IUnknown
 #pragma region IUnknown
 
 
 // Query to the interface the component supported.
 // Query to the interface the component supported.
@@ -97,12 +75,12 @@ IFACEMETHODIMP_(ULONG) OCContextMenu::Release()
 IFACEMETHODIMP OCContextMenu::Initialize(
 IFACEMETHODIMP OCContextMenu::Initialize(
     LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
     LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
 {
 {
+    m_selectedFiles.clear();
+
     if (!pDataObj) {
     if (!pDataObj) {
         return E_INVALIDARG;
         return E_INVALIDARG;
     }
     }
 
 
-    HRESULT hr = E_FAIL;
-
     FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
     FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
     STGMEDIUM stm;
     STGMEDIUM stm;
 
 
@@ -110,14 +88,19 @@ IFACEMETHODIMP OCContextMenu::Initialize(
         // Get an HDROP handle.
         // Get an HDROP handle.
         HDROP hDrop = static_cast<HDROP>(GlobalLock(stm.hGlobal));
         HDROP hDrop = static_cast<HDROP>(GlobalLock(stm.hGlobal));
         if (hDrop) {
         if (hDrop) {
-            // Ignore multi-selections
             UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
             UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
-            if (nFiles == 1) {
+            for (int i = 0; i < nFiles; ++i) {
                 // Get the path of the file.
                 // Get the path of the file.
-                if (0 != DragQueryFile(hDrop, 0, m_szSelectedFile,  ARRAYSIZE(m_szSelectedFile)))
-                {
-                    hr = S_OK;
+                wchar_t buffer[MAX_PATH];
+
+                if (!DragQueryFile(hDrop, i, buffer, ARRAYSIZE(buffer))) {
+                    m_selectedFiles.clear();
+                    break;
                 }
                 }
+
+                if (i)
+                    m_selectedFiles += L'\x1e';
+                m_selectedFiles += buffer;
             }
             }
 
 
             GlobalUnlock(stm.hGlobal);
             GlobalUnlock(stm.hGlobal);
@@ -128,7 +111,7 @@ IFACEMETHODIMP OCContextMenu::Initialize(
 
 
     // If any value other than S_OK is returned from the method, the context 
     // If any value other than S_OK is returned from the method, the context 
     // menu item is not displayed.
     // menu item is not displayed.
-    return hr;
+    return m_selectedFiles.empty() ? E_FAIL : S_OK;
 }
 }
 
 
 #pragma endregion
 #pragma endregion
@@ -153,17 +136,8 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT
         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
     }
     }
 
 
-    OCClientInterface::ContextMenuInfo info = OCClientInterface::FetchInfo();
-    bool skip = true;
-    size_t selectedFileLength = wcslen(m_szSelectedFile);
-    for (const std::wstring path : info.watchedDirectories) {
-        if (StringUtil::isDescendantOf(m_szSelectedFile, selectedFileLength, path)) {
-            skip = false;
-            break;
-        }
-    }
-
-    if (skip) {
+    m_info = OCClientInterface::FetchInfo(m_selectedFiles);
+    if (m_info.menuItems.empty()) {
         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
     }
     }
 
 
@@ -175,7 +149,7 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT
         mii.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING;
         mii.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING;
         mii.hSubMenu = hSubmenu;
         mii.hSubMenu = hSubmenu;
         mii.fType = MFT_STRING;
         mii.fType = MFT_STRING;
-        mii.dwTypeData = &info.contextMenuTitle[0];
+        mii.dwTypeData = &m_info.contextMenuTitle[0];
 
 
         if (!InsertMenuItem(hMenu, indexMenu++, TRUE, &mii))
         if (!InsertMenuItem(hMenu, indexMenu++, TRUE, &mii))
             return HRESULT_FROM_WIN32(GetLastError());
             return HRESULT_FROM_WIN32(GetLastError());
@@ -183,133 +157,59 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT
     InsertSeperator(hMenu, indexMenu++);
     InsertSeperator(hMenu, indexMenu++);
 
 
     UINT indexSubMenu = 0;
     UINT indexSubMenu = 0;
-    {
-        assert(!info.shareMenuTitle.empty());
-        MENUITEMINFO mii = { sizeof(mii) };
-        mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
-        mii.wID = idCmdFirst + IDM_SHARE;
-        mii.fType = MFT_STRING;
-        mii.dwTypeData = &info.shareMenuTitle[0];
-
-        if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
-            return HRESULT_FROM_WIN32(GetLastError());
-    }
-    {
-        assert(!info.copyLinkMenuTitle.empty());
-        MENUITEMINFO mii = { sizeof(mii) };
-        mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
-        mii.wID = idCmdFirst + IDM_COPYLINK;
-        mii.fType = MFT_STRING;
-        mii.dwTypeData = &info.copyLinkMenuTitle[0];
+    for (auto &item : m_info.menuItems) {
+        bool disabled = item.flags.find(L'd') != std::string::npos;
 
 
-        if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
-            return HRESULT_FROM_WIN32(GetLastError());
-    }
-    {
-        assert(!info.emailLinkMenuTitle.empty());
         MENUITEMINFO mii = { sizeof(mii) };
         MENUITEMINFO mii = { sizeof(mii) };
-        mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
-        mii.wID = idCmdFirst + IDM_EMAILLINK;
+        mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING | MIIM_STATE;
+        mii.wID = indexSubMenu;
         mii.fType = MFT_STRING;
         mii.fType = MFT_STRING;
-        mii.dwTypeData = &info.emailLinkMenuTitle[0];
+        mii.dwTypeData = &item.title[0];
+        mii.fState = disabled ? MFS_DISABLED : MFS_ENABLED;
 
 
-        if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
+        if (!InsertMenuItem(hSubmenu, indexSubMenu, true, &mii))
             return HRESULT_FROM_WIN32(GetLastError());
             return HRESULT_FROM_WIN32(GetLastError());
+        indexSubMenu++;
     }
     }
 
 
-
     // Return an HRESULT value with the severity set to SEVERITY_SUCCESS. 
     // Return an HRESULT value with the severity set to SEVERITY_SUCCESS. 
     // Set the code value to the offset of the largest command identifier 
     // Set the code value to the offset of the largest command identifier 
     // that was assigned, plus one (1).
     // that was assigned, plus one (1).
-    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_EMAILLINK + 1));
+    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(indexSubMenu));
 }
 }
 
 
 IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
 IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
 {
 {
+    std::wstring command;
 
 
     // For the Unicode case, if the high-order word is not zero, the 
     // For the Unicode case, if the high-order word is not zero, the 
     // command's verb string is in lpcmi->lpVerbW. 
     // command's verb string is in lpcmi->lpVerbW. 
     if (HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW))
     if (HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW))
     {
     {
-        // Is the verb supported by this context menu extension?
-        if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocshare") == 0) {
-            OnVerbShare(pici->hwnd);
-        }
-        else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"occopylink") == 0) {
-            OnVerbCopyLink(pici->hwnd);
-        }
-        else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocemaillink") == 0) {
-            OnVerbEmailLink(pici->hwnd);
-        }
-        else {
-            // If the verb is not recognized by the context menu handler, it 
-            // must return E_FAIL to allow it to be passed on to the other 
-            // context menu handlers that might implement that verb.
-            return E_FAIL;
-        }
-    }
+        command = ((CMINVOKECOMMANDINFOEX *)pici)->lpVerbW;
+    } else {
+        // If the command cannot be identified through the verb string, then
+        // check the identifier offset.
 
 
-    // If the command cannot be identified through the verb string, then 
-    // check the identifier offset.
-    else
-    {
-        // Is the command identifier offset supported by this context menu 
-        // extension?
-        if (LOWORD(pici->lpVerb) == IDM_SHARE) {
-            OnVerbShare(pici->hwnd);
-        }
-        else if (LOWORD(pici->lpVerb) == IDM_COPYLINK) {
-            OnVerbCopyLink(pici->hwnd);
-        }
-        else if (LOWORD(pici->lpVerb) == IDM_EMAILLINK) {
-            OnVerbEmailLink(pici->hwnd);
-        }
-        else {
-            // If the verb is not recognized by the context menu handler, it 
-            // must return E_FAIL to allow it to be passed on to the other 
-            // context menu handlers that might implement that verb.
+        auto offset = LOWORD(pici->lpVerb);
+        if (offset < m_info.menuItems.size())
             return E_FAIL;
             return E_FAIL;
-        }
+
+        command = m_info.menuItems[offset].command;
     }
     }
 
 
+    OCClientInterface::SendRequest(command.data(), m_selectedFiles);
     return S_OK;
     return S_OK;
 }
 }
 
 
 IFACEMETHODIMP OCContextMenu::GetCommandString(UINT_PTR idCommand,
 IFACEMETHODIMP OCContextMenu::GetCommandString(UINT_PTR idCommand,
     UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax)
     UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax)
 {
 {
-    HRESULT hr = E_INVALIDARG;
-
-    switch (idCommand) {
-    case IDM_SHARE:
-        if (uFlags == GCS_VERBW) {
-            // GCS_VERBW is an optional feature that enables a caller to
-            // discover the canonical name for the verb passed in through
-            // idCommand.
-            hr = StringCchCopy(reinterpret_cast<PWSTR>(pszName), cchMax,
-                L"OCShareViaOC");
-        }
-        break;
-    case IDM_COPYLINK:
-        if (uFlags == GCS_VERBW) {
-            hr = StringCchCopy(reinterpret_cast<PWSTR>(pszName), cchMax,
-                L"OCCopyLink");
-        }
-        break;
-    case IDM_EMAILLINK:
-        if (uFlags == GCS_VERBW) {
-            hr = StringCchCopy(reinterpret_cast<PWSTR>(pszName), cchMax,
-                L"OCEmailLink");
-        }
-        break;
-    default:
-        break;
+    if (idCommand < m_info.menuItems.size() && uFlags == GCS_VERBW) {
+        return StringCchCopyW(reinterpret_cast<PWSTR>(pszName), cchMax,
+            m_info.menuItems[idCommand].command.data());
     }
     }
-
-    // If the idCommand or uFlags is not supported by this context menu
-    // extension handler, return E_INVALIDARG.
-
-    return hr;
+    return E_INVALIDARG;
 }
 }
 
 
-#pragma endregion
+#pragma endregion

+ 5 - 15
shell_integration/windows/OCContextMenu/OCContextMenu.h

@@ -17,6 +17,8 @@
 
 
 #pragma once
 #pragma once
 #include <shlobj.h>     // For IShellExtInit and IContextMenu
 #include <shlobj.h>     // For IShellExtInit and IContextMenu
+#include <string>
+#include "OCClientInterface.h"
 
 
 class OCContextMenu : public IShellExtInit, public IContextMenu
 class OCContextMenu : public IShellExtInit, public IContextMenu
 {
 {
@@ -43,21 +45,9 @@ private:
 	// Reference count of component.
 	// Reference count of component.
 	long m_cRef;
 	long m_cRef;
 
 
-	// The name of the selected file.
-	wchar_t m_szSelectedFile[MAX_PATH];
-
-    // The method that handles the "ocshare" verb.
-    void OnVerbShare(HWND hWnd);
-    void OnVerbCopyLink(HWND hWnd);
-    void OnVerbEmailLink(HWND hWnd);
-
-	PWSTR m_pszMenuText;
-	PCSTR m_pszVerb;
-	PCWSTR m_pwszVerb;
-	PCSTR m_pszVerbCanonicalName;
-	PCWSTR m_pwszVerbCanonicalName;
-	PCSTR m_pszVerbHelpText;
-	PCWSTR m_pwszVerbHelpText;
+	// The name of the selected files (separated by '\x1e')
+	std::wstring m_selectedFiles;
+	OCClientInterface::ContextMenuInfo m_info;
 };
 };
 	
 	
 #endif //OCCONTEXTMENU_H
 #endif //OCCONTEXTMENU_H

+ 24 - 0
shell_integration/windows/OCUtil/StringUtil.h

@@ -61,6 +61,30 @@ public:
         thirdChunk = source.substr(statusEnd + 1);
         thirdChunk = source.substr(statusEnd + 1);
         return true;
         return true;
     }
     }
+
+    static bool extractChunks(const std::wstring &source, std::wstring &secondChunk, std::wstring &thirdChunk, std::wstring &forthChunk)
+    {
+        auto statusBegin = source.find(L':', 0);
+        assert(statusBegin != std::wstring::npos);
+
+        auto statusEnd = source.find(L':', statusBegin + 1);
+        if (statusEnd == std::wstring::npos) {
+            // the command do not contains two colon?
+            return false;
+        }
+
+        auto thirdColon = source.find(L':', statusEnd + 1);
+        if (statusEnd == std::wstring::npos) {
+            // the command do not contains three colon?
+            return false;
+        }
+
+        // Assume the caller extracted the chunk before the first colon.
+        secondChunk = source.substr(statusBegin + 1, statusEnd - statusBegin - 1);
+        thirdChunk = source.substr(statusEnd + 1, thirdColon - statusEnd - 1);
+        forthChunk = source.substr(thirdColon + 1);
+        return true;
+    }
 };
 };
 
 
 #endif // STRINGUTIL_H
 #endif // STRINGUTIL_H