https://git.reactos.org/?p=reactos.git;a=commitdiff;h=bc52d5f1f4591dc5b1569…
commit bc52d5f1f4591dc5b15698cccb59b379539aaadf
Author: Whindmar Saksit <whindsaks(a)proton.me>
AuthorDate: Thu Feb 13 13:13:02 2025 +0100
Commit: GitHub <noreply(a)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 <whindsaks(a)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