[SHIMGVW] Display shell context menu for the image on right-click (#7711)

CORE-13340
This commit is contained in:
Whindmar Saksit 2025-02-13 13:13:02 +01:00 committed by GitHub
parent 329a414584
commit bc52d5f1f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 198 additions and 19 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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)

View file

@ -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);

158
dll/win32/shimgvw/util.c Normal file
View file

@ -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@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);
}

View file

@ -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