Windows shell extension: port to the new protocol

This commit is contained in:
Olivier Goffart 2018-01-19 19:44:10 +01:00 коммит произвёл Olivier Goffart
Родитель ebfac84c69
Коммит 883080b557
5 изменённых файлов: 89 добавлений и 195 удалений

Просмотреть файл

@ -34,7 +34,7 @@ using namespace std;
#define PIPE_TIMEOUT 5*1000 //ms
#define SOCK_BUFFER 4096
OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo(const std::wstring &files)
{
auto pipename = CommunicationSocket::DefaultPipePath();
@ -45,7 +45,8 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
if (!socket.Connect(pipename)) {
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;
std::wstring response;
@ -60,16 +61,14 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
wstring stringName, stringValue;
if (!StringUtil::extractChunks(response, stringName, stringValue))
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);
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
}
}
@ -81,22 +80,7 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
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();

Просмотреть файл

@ -46,18 +46,14 @@ public:
struct ContextMenuInfo {
std::vector<std::wstring> watchedDirectories;
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

Просмотреть файл

@ -22,13 +22,8 @@
#include <shellapi.h>
#include <StringUtil.h>
extern HINSTANCE g_hInst;
extern long g_cDllRef;
#define IDM_SHARE 0
#define IDM_COPYLINK 1
#define IDM_EMAILLINK 2
OCContextMenu::OCContextMenu(void)
: m_cRef(1)
{
@ -40,23 +35,6 @@ OCContextMenu::~OCContextMenu(void)
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
// Query to the interface the component supported.
@ -97,12 +75,12 @@ IFACEMETHODIMP_(ULONG) OCContextMenu::Release()
IFACEMETHODIMP OCContextMenu::Initialize(
LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
{
m_selectedFiles.clear();
if (!pDataObj) {
return E_INVALIDARG;
}
HRESULT hr = E_FAIL;
FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stm;
@ -110,14 +88,19 @@ IFACEMETHODIMP OCContextMenu::Initialize(
// Get an HDROP handle.
HDROP hDrop = static_cast<HDROP>(GlobalLock(stm.hGlobal));
if (hDrop) {
// Ignore multi-selections
UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
if (nFiles == 1) {
for (int i = 0; i < nFiles; ++i) {
// 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);
@ -128,7 +111,7 @@ IFACEMETHODIMP OCContextMenu::Initialize(
// If any value other than S_OK is returned from the method, the context
// menu item is not displayed.
return hr;
return m_selectedFiles.empty() ? E_FAIL : S_OK;
}
#pragma endregion
@ -153,17 +136,8 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT
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));
}
@ -175,7 +149,7 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT
mii.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING;
mii.hSubMenu = hSubmenu;
mii.fType = MFT_STRING;
mii.dwTypeData = &info.contextMenuTitle[0];
mii.dwTypeData = &m_info.contextMenuTitle[0];
if (!InsertMenuItem(hMenu, indexMenu++, TRUE, &mii))
return HRESULT_FROM_WIN32(GetLastError());
@ -183,133 +157,59 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT
InsertSeperator(hMenu, indexMenu++);
UINT indexSubMenu = 0;
{
assert(!info.shareMenuTitle.empty());
for (auto &item : m_info.menuItems) {
bool disabled = item.flags.find(L'd') != std::string::npos;
MENUITEMINFO mii = { sizeof(mii) };
mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
mii.wID = idCmdFirst + IDM_SHARE;
mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING | MIIM_STATE;
mii.wID = indexSubMenu;
mii.fType = MFT_STRING;
mii.dwTypeData = &info.shareMenuTitle[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());
indexSubMenu++;
}
{
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];
if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
return HRESULT_FROM_WIN32(GetLastError());
}
{
assert(!info.emailLinkMenuTitle.empty());
MENUITEMINFO mii = { sizeof(mii) };
mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
mii.wID = idCmdFirst + IDM_EMAILLINK;
mii.fType = MFT_STRING;
mii.dwTypeData = &info.emailLinkMenuTitle[0];
if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
return HRESULT_FROM_WIN32(GetLastError());
}
// Return an HRESULT value with the severity set to SEVERITY_SUCCESS.
// Set the code value to the offset of the largest command identifier
// 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)
{
std::wstring command;
// For the Unicode case, if the high-order word is not zero, the
// command's verb string is in lpcmi->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.
command = ((CMINVOKECOMMANDINFOEX *)pici)->lpVerbW;
} else {
// If the command cannot be identified through the verb string, then
// check the identifier offset.
auto offset = LOWORD(pici->lpVerb);
if (offset < m_info.menuItems.size())
return E_FAIL;
}
}
// 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.
return E_FAIL;
}
command = m_info.menuItems[offset].command;
}
OCClientInterface::SendRequest(command.data(), m_selectedFiles);
return S_OK;
}
IFACEMETHODIMP OCContextMenu::GetCommandString(UINT_PTR idCommand,
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

Просмотреть файл

@ -17,6 +17,8 @@
#pragma once
#include <shlobj.h> // For IShellExtInit and IContextMenu
#include <string>
#include "OCClientInterface.h"
class OCContextMenu : public IShellExtInit, public IContextMenu
{
@ -43,21 +45,9 @@ private:
// Reference count of component.
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

Просмотреть файл

@ -61,6 +61,30 @@ public:
thirdChunk = source.substr(statusEnd + 1);
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