https://git.reactos.org/?p=reactos.git;a=commitdiff;h=bc52d5f1f4591dc5b15698cccb59b379539aaadf
commit bc52d5f1f4591dc5b15698cccb59b379539aaadf Author: Whindmar Saksit <whinds...@proton.me> AuthorDate: Thu Feb 13 13:13:02 2025 +0100 Commit: GitHub <nore...@github.com> CommitDate: Thu Feb 13 13:13:02 2025 +0100 [SHIMGVW] Display shell context menu for the image on right-click (#7711) CORE-13340 --- dll/win32/shimgvw/CMakeLists.txt | 1 + dll/win32/shimgvw/loader.cpp | 18 ++--- dll/win32/shimgvw/shimgvw.c | 23 ++++-- dll/win32/shimgvw/shimgvw.h | 5 ++ dll/win32/shimgvw/util.c | 158 +++++++++++++++++++++++++++++++++++++++ sdk/include/reactos/shellutils.h | 12 +-- 6 files changed, 198 insertions(+), 19 deletions(-) diff --git a/dll/win32/shimgvw/CMakeLists.txt b/dll/win32/shimgvw/CMakeLists.txt index 15983aff150..b9b55c42233 100644 --- a/dll/win32/shimgvw/CMakeLists.txt +++ b/dll/win32/shimgvw/CMakeLists.txt @@ -7,6 +7,7 @@ list(APPEND SOURCE shimgvw.c comsup.c shimgvw.rc + util.c ${CMAKE_CURRENT_BINARY_DIR}/shimgvw_stubs.c ${CMAKE_CURRENT_BINARY_DIR}/shimgvw.def) diff --git a/dll/win32/shimgvw/loader.cpp b/dll/win32/shimgvw/loader.cpp index 50112f6c486..bde8b12ac13 100644 --- a/dll/win32/shimgvw/loader.cpp +++ b/dll/win32/shimgvw/loader.cpp @@ -21,7 +21,7 @@ static HRESULT Read(HANDLE hFile, void* Buffer, DWORD Size) return Size == Transferred ? S_OK : HResultFromWin32(ERROR_HANDLE_EOF); } -struct IMAGEINFO +struct IMAGESTATS { UINT w, h; BYTE bpp; @@ -91,7 +91,7 @@ static BYTE GetPngBppFromIHDRData(const void* buffer) return (depth <= 16 && type <= 6) ? channels[type] * depth : 0; } -static bool GetInfoFromPng(const void* file, SIZE_T size, IMAGEINFO& info) +static bool GetInfoFromPng(const void* file, SIZE_T size, IMAGESTATS& info) { C_ASSERT(sizeof(PNGSIGNATURE) == 8); C_ASSERT(sizeof(PNGSIGANDIHDR) == 8 + (4 + 4 + (4 + 4 + 5) + 4)); @@ -111,7 +111,7 @@ static bool GetInfoFromPng(const void* file, SIZE_T size, IMAGEINFO& info) return false; } -static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGEINFO& info) +static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGESTATS& info) { BitmapInfoHeader bih(pBitmapInfo); info.w = bih.biWidth; @@ -121,11 +121,11 @@ static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGEINFO& info) return info.w && bpp == info.bpp; } -static bool GetInfoFromIcoBmp(const void* pBitmapInfo, IMAGEINFO& stat) +static bool GetInfoFromIcoBmp(const void* pBitmapInfo, IMAGESTATS& info) { - bool ret = GetInfoFromBmp(pBitmapInfo, stat); - stat.h /= 2; // Don't include mask - return ret && stat.h; + bool ret = GetInfoFromBmp(pBitmapInfo, info); + info.h /= 2; // Don't include mask + return ret && info.h; } EXTERN_C PCWSTR GetExtraExtensionsGdipList(VOID) @@ -158,7 +158,7 @@ static void OverrideFileContent(HGLOBAL& hMem, DWORD& Size) for (UINT i = 0; i < count; ++i) { BOOL valid = FALSE; - IMAGEINFO info; + IMAGESTATS info; const BYTE* data = buffer + entries[i].offset; if (IsPngSignature(data, entries[i].size)) valid = GetInfoFromPng(data, entries[i].size, info); @@ -189,7 +189,7 @@ static void OverrideFileContent(HGLOBAL& hMem, DWORD& Size) const BYTE* data = buffer + entries[i].offset; if (IsPngSignature(data, entries[i].size)) { - IMAGEINFO info; + IMAGESTATS info; if (!GetInfoFromPng(data, entries[i].size, info)) continue; bih.biPlanes = 1; diff --git a/dll/win32/shimgvw/shimgvw.c b/dll/win32/shimgvw/shimgvw.c index 7284266b94a..ba8dd000e81 100644 --- a/dll/win32/shimgvw/shimgvw.c +++ b/dll/win32/shimgvw/shimgvw.c @@ -1190,7 +1190,7 @@ ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_RBUTTONUP: { ZoomWnd_OnButtonUp(hwnd, uMsg, wParam, lParam); - break; + goto doDefault; } case WM_LBUTTONDBLCLK: { @@ -1209,6 +1209,10 @@ ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam)); break; } + case WM_CONTEXTMENU: + if (Preview_IsMainWnd(pData->m_hwnd)) + DoShellContextMenuOnFile(hwnd, pData->m_szFile, lParam); + break; case WM_HSCROLL: case WM_VSCROLL: ZoomWnd_OnHVScroll(pData, hwnd, wParam, uMsg == WM_VSCROLL); @@ -1230,7 +1234,7 @@ ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } break; } - default: + default: doDefault: { return DefWindowProcW(hwnd, uMsg, wParam, lParam); } @@ -1429,9 +1433,7 @@ Preview_ToggleSlideShowEx(PPREVIEW_DATA pData, BOOL StartTimer) if (IsWindowVisible(g_hwndFullscreen)) { - KillTimer(g_hwndFullscreen, SLIDESHOW_TIMER_ID); - ShowWindow(g_hMainWnd, SW_SHOW); - ShowWindow(g_hwndFullscreen, SW_HIDE); + Preview_EndSlideShow(g_hwndFullscreen); } else { @@ -1577,6 +1579,10 @@ Preview_OnCommand(HWND hwnd, UINT nCommandID) Preview_Edit(hwnd); break; + case IDC_HELP_TOC: + DisplayHelp(hwnd); + break; + default: break; } @@ -1693,6 +1699,13 @@ PreviewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) Preview_OnDestroy(hwnd); break; } + case WM_CONTEXTMENU: + { + PPREVIEW_DATA pData = Preview_GetData(hwnd); + if ((int)lParam == -1) + return ZoomWndProc(pData->m_hwndZoom, uMsg, wParam, lParam); + break; + } case WM_TIMER: { if (wParam == SLIDESHOW_TIMER_ID) diff --git a/dll/win32/shimgvw/shimgvw.h b/dll/win32/shimgvw/shimgvw.h index c17a15bb8f2..279d08cd244 100644 --- a/dll/win32/shimgvw/shimgvw.h +++ b/dll/win32/shimgvw/shimgvw.h @@ -12,6 +12,7 @@ #define _INC_WINDOWS #define COM_NO_WINDOWS_H #define INITGUID +#define COBJMACROS #include <windef.h> #include <winbase.h> @@ -23,6 +24,7 @@ #include <gdiplus.h> #include <shlwapi.h> #include <strsafe.h> +#include <shobjidl.h> #include <debug.h> @@ -69,6 +71,9 @@ void Anime_Start(PANIME pAnime, DWORD dwDelay); void Anime_Pause(PANIME pAnime); BOOL Anime_OnTimer(PANIME pAnime, WPARAM wParam); +void DoShellContextMenuOnFile(HWND hwnd, PCWSTR File, LPARAM lParam); +void DisplayHelp(HWND hwnd); + static inline LPVOID QuickAlloc(SIZE_T cbSize, BOOL bZero) { return HeapAlloc(GetProcessHeap(), (bZero ? HEAP_ZERO_MEMORY : 0), cbSize); diff --git a/dll/win32/shimgvw/util.c b/dll/win32/shimgvw/util.c new file mode 100644 index 00000000000..290ad083fad --- /dev/null +++ b/dll/win32/shimgvw/util.c @@ -0,0 +1,158 @@ +/* + * PROJECT: ReactOS Picture and Fax Viewer + * LICENSE: GPL-2.0 (https://spdx.org/licenses/GPL-2.0) + * PURPOSE: Utility routines + * COPYRIGHT: Copyright 2025 Whindmar Saksit <whinds...@proton.me> + */ + +#include "shimgvw.h" +#include <windowsx.h> +#include <shlobj.h> +#include <shellapi.h> +#include <shellutils.h> +#include <shlwapi_undoc.h> + +IContextMenu *g_pContextMenu = NULL; + +static int +GetMenuItemIdByPos(HMENU hMenu, UINT Pos) +{ + MENUITEMINFOW mii; + mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem); /* USER32 version agnostic */ + mii.fMask = MIIM_ID; + mii.cch = 0; + return GetMenuItemInfoW(hMenu, Pos, TRUE, &mii) ? mii.wID : -1; +} + +static BOOL +IsMenuSeparator(HMENU hMenu, UINT Pos) +{ + MENUITEMINFOW mii; + mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem); /* USER32 version agnostic */ + mii.fMask = MIIM_FTYPE; + mii.cch = 0; + return GetMenuItemInfoW(hMenu, Pos, TRUE, &mii) && (mii.fType & MFT_SEPARATOR); +} + +static BOOL +IsSelfShellVerb(PCWSTR Assoc, PCWSTR Verb) +{ + WCHAR buf[MAX_PATH * 3]; + DWORD cch = _countof(buf); + HRESULT hr = AssocQueryStringW(ASSOCF_NOTRUNCATE, ASSOCSTR_COMMAND, Assoc, Verb, buf, &cch); + return hr == S_OK && *Assoc == L'.' && StrStrW(buf, L",ImageView_Fullscreen"); +} + +static void +ModifyShellContextMenu(IContextMenu *pCM, HMENU hMenu, UINT CmdIdFirst, PCWSTR Assoc) +{ + HRESULT hr; + UINT id, i; + for (i = 0; i < GetMenuItemCount(hMenu); ++i) + { + WCHAR buf[200]; + id = GetMenuItemIdByPos(hMenu, i); + if (id == (UINT)-1) + continue; + + *buf = UNICODE_NULL; + /* Note: We just ask for the wide string because all the items we care about come from shell32 and it handles both */ + hr = IContextMenu_GetCommandString(pCM, id - CmdIdFirst, GCS_VERBW, NULL, (char*)buf, _countof(buf)); + if (SUCCEEDED(hr)) + { + UINT remove = FALSE; + if (IsSelfShellVerb(Assoc, buf)) + ++remove; + else if (!lstrcmpiW(L"cut", buf) || !lstrcmpiW(L"copy", buf) || !lstrcmpiW(L"link", buf)) + ++remove; + + if (remove && DeleteMenu(hMenu, i, MF_BYPOSITION)) + { + if (i-- > 0) + { + if (IsMenuSeparator(hMenu, i) && IsMenuSeparator(hMenu, i + 1)) + DeleteMenu(hMenu, i, MF_BYPOSITION); + } + } + } + } + + while (IsMenuSeparator(hMenu, 0) && DeleteMenu(hMenu, 0, MF_BYPOSITION)) {} +} + +static LRESULT CALLBACK +ShellContextMenuWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + LRESULT lRes = 0; + if (FAILED(SHForwardContextMenuMsg((IUnknown*)g_pContextMenu, uMsg, wParam, lParam, &lRes, TRUE))) + lRes = DefWindowProc(hwnd, uMsg, wParam, lParam); + return lRes; +} + +static void +DoShellContextMenu(HWND hwnd, IContextMenu *pCM, PCWSTR File, LPARAM lParam) +{ + enum { first = 1, last = 0x7fff }; + HRESULT hr; + HMENU hMenu = CreatePopupMenu(); + UINT cmf = GetKeyState(VK_SHIFT) < 0 ? CMF_EXTENDEDVERBS : 0; + + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + if ((int)lParam == -1) + { + RECT rect; + GetWindowRect(hwnd, &rect); + pt.x = (rect.left + rect.right) / 2; + pt.y = rect.top; + } + + g_pContextMenu = pCM; + hwnd = SHCreateWorkerWindowW(ShellContextMenuWindowProc, hwnd, 0, WS_VISIBLE | WS_CHILD, NULL, 0); + if (!hwnd) + goto die; + hr = IContextMenu_QueryContextMenu(pCM, hMenu, 0, first, last, cmf | CMF_NODEFAULT); + if (SUCCEEDED(hr)) + { + UINT id; + ModifyShellContextMenu(pCM, hMenu, first, PathFindExtensionW(File)); + id = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, pt.x, pt.y, hwnd, NULL); + if (id) + { + UINT flags = (GetKeyState(VK_SHIFT) < 0 ? CMIC_MASK_SHIFT_DOWN : 0) | + (GetKeyState(VK_CONTROL) < 0 ? CMIC_MASK_CONTROL_DOWN : 0); + CMINVOKECOMMANDINFO ici = { sizeof(ici), flags, hwnd, MAKEINTRESOURCEA(id - first) }; + ici.nShow = SW_SHOW; + hr = IContextMenu_InvokeCommand(pCM, &ici); + } + } + DestroyWindow(hwnd); +die: + DestroyMenu(hMenu); + g_pContextMenu = NULL; +} + +void +DoShellContextMenuOnFile(HWND hwnd, PCWSTR File, LPARAM lParam) +{ + HRESULT hr; + IShellFolder *pSF; + PCUITEMID_CHILD pidlItem; + PIDLIST_ABSOLUTE pidl = ILCreateFromPath(File); + if (pidl && SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &pidlItem))) + { + IContextMenu *pCM; + hr = IShellFolder_GetUIObjectOf(pSF, hwnd, 1, &pidlItem, &IID_IContextMenu, NULL, (void**)&pCM); + if (SUCCEEDED(hr)) + { + DoShellContextMenu(hwnd, pCM, File, lParam); + IContextMenu_Release(pCM); + } + IShellFolder_Release(pSF); + } + SHFree(pidl); +} + +void DisplayHelp(HWND hwnd) +{ + SHELL_ErrorBox(hwnd, ERROR_NOT_SUPPORTED); +} diff --git a/sdk/include/reactos/shellutils.h b/sdk/include/reactos/shellutils.h index 756c57c9d73..c45b6ad8770 100644 --- a/sdk/include/reactos/shellutils.h +++ b/sdk/include/reactos/shellutils.h @@ -23,7 +23,7 @@ extern "C" { #endif /* defined(__cplusplus) */ -inline ULONG +static inline ULONG Win32DbgPrint(const char *filename, int line, const char *lpFormat, ...) { char szMsg[512]; @@ -63,11 +63,11 @@ Win32DbgPrint(const char *filename, int line, const char *lpFormat, ...) # define IID_PPV_ARG(Itype, ppType) IID_##Itype, reinterpret_cast<void**>((static_cast<Itype**>(ppType))) # define IID_NULL_PPV_ARG(Itype, ppType) IID_##Itype, NULL, reinterpret_cast<void**>((static_cast<Itype**>(ppType))) #else -# define IID_PPV_ARG(Itype, ppType) IID_##Itype, (void**)(ppType) -# define IID_NULL_PPV_ARG(Itype, ppType) IID_##Itype, NULL, (void**)(ppType) +# define IID_PPV_ARG(Itype, ppType) &IID_##Itype, (void**)(ppType) +# define IID_NULL_PPV_ARG(Itype, ppType) &IID_##Itype, NULL, (void**)(ppType) #endif -inline HRESULT HResultFromWin32(DWORD hr) +static inline HRESULT HResultFromWin32(DWORD hr) { // HRESULT_FROM_WIN32 will evaluate its parameter twice, this function will not. return HRESULT_FROM_WIN32(hr); @@ -75,7 +75,7 @@ inline HRESULT HResultFromWin32(DWORD hr) #if 1 -inline BOOL _ROS_FAILED_HELPER(HRESULT hr, const char* expr, const char* filename, int line) +static inline BOOL _ROS_FAILED_HELPER(HRESULT hr, const char* expr, const char* filename, int line) { if (FAILED(hr)) { @@ -122,6 +122,8 @@ SHELL_ErrorBox(H hwndOwner, UINT Error = GetLastError()) { return SHELL_ErrorBoxHelper(const_cast<HWND>(hwndOwner), Error); } +#else +#define SHELL_ErrorBox SHELL_ErrorBoxHelper #endif #ifdef __cplusplus