Skip to content

Commit bc52d5f

Browse files
authored
[SHIMGVW] Display shell context menu for the image on right-click (reactos#7711)
CORE-13340
1 parent 329a414 commit bc52d5f

File tree

6 files changed

+198
-19
lines changed

6 files changed

+198
-19
lines changed

dll/win32/shimgvw/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ list(APPEND SOURCE
77
shimgvw.c
88
comsup.c
99
shimgvw.rc
10+
util.c
1011
${CMAKE_CURRENT_BINARY_DIR}/shimgvw_stubs.c
1112
${CMAKE_CURRENT_BINARY_DIR}/shimgvw.def)
1213

dll/win32/shimgvw/loader.cpp

+9-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static HRESULT Read(HANDLE hFile, void* Buffer, DWORD Size)
2121
return Size == Transferred ? S_OK : HResultFromWin32(ERROR_HANDLE_EOF);
2222
}
2323

24-
struct IMAGEINFO
24+
struct IMAGESTATS
2525
{
2626
UINT w, h;
2727
BYTE bpp;
@@ -91,7 +91,7 @@ static BYTE GetPngBppFromIHDRData(const void* buffer)
9191
return (depth <= 16 && type <= 6) ? channels[type] * depth : 0;
9292
}
9393

94-
static bool GetInfoFromPng(const void* file, SIZE_T size, IMAGEINFO& info)
94+
static bool GetInfoFromPng(const void* file, SIZE_T size, IMAGESTATS& info)
9595
{
9696
C_ASSERT(sizeof(PNGSIGNATURE) == 8);
9797
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)
111111
return false;
112112
}
113113

114-
static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGEINFO& info)
114+
static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGESTATS& info)
115115
{
116116
BitmapInfoHeader bih(pBitmapInfo);
117117
info.w = bih.biWidth;
@@ -121,11 +121,11 @@ static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGEINFO& info)
121121
return info.w && bpp == info.bpp;
122122
}
123123

124-
static bool GetInfoFromIcoBmp(const void* pBitmapInfo, IMAGEINFO& stat)
124+
static bool GetInfoFromIcoBmp(const void* pBitmapInfo, IMAGESTATS& info)
125125
{
126-
bool ret = GetInfoFromBmp(pBitmapInfo, stat);
127-
stat.h /= 2; // Don't include mask
128-
return ret && stat.h;
126+
bool ret = GetInfoFromBmp(pBitmapInfo, info);
127+
info.h /= 2; // Don't include mask
128+
return ret && info.h;
129129
}
130130

131131
EXTERN_C PCWSTR GetExtraExtensionsGdipList(VOID)
@@ -158,7 +158,7 @@ static void OverrideFileContent(HGLOBAL& hMem, DWORD& Size)
158158
for (UINT i = 0; i < count; ++i)
159159
{
160160
BOOL valid = FALSE;
161-
IMAGEINFO info;
161+
IMAGESTATS info;
162162
const BYTE* data = buffer + entries[i].offset;
163163
if (IsPngSignature(data, entries[i].size))
164164
valid = GetInfoFromPng(data, entries[i].size, info);
@@ -189,7 +189,7 @@ static void OverrideFileContent(HGLOBAL& hMem, DWORD& Size)
189189
const BYTE* data = buffer + entries[i].offset;
190190
if (IsPngSignature(data, entries[i].size))
191191
{
192-
IMAGEINFO info;
192+
IMAGESTATS info;
193193
if (!GetInfoFromPng(data, entries[i].size, info))
194194
continue;
195195
bih.biPlanes = 1;

dll/win32/shimgvw/shimgvw.c

+18-5
Original file line numberDiff line numberDiff line change
@@ -1190,7 +1190,7 @@ ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11901190
case WM_RBUTTONUP:
11911191
{
11921192
ZoomWnd_OnButtonUp(hwnd, uMsg, wParam, lParam);
1193-
break;
1193+
goto doDefault;
11941194
}
11951195
case WM_LBUTTONDBLCLK:
11961196
{
@@ -1209,6 +1209,10 @@ ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
12091209
(SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
12101210
break;
12111211
}
1212+
case WM_CONTEXTMENU:
1213+
if (Preview_IsMainWnd(pData->m_hwnd))
1214+
DoShellContextMenuOnFile(hwnd, pData->m_szFile, lParam);
1215+
break;
12121216
case WM_HSCROLL:
12131217
case WM_VSCROLL:
12141218
ZoomWnd_OnHVScroll(pData, hwnd, wParam, uMsg == WM_VSCROLL);
@@ -1230,7 +1234,7 @@ ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
12301234
}
12311235
break;
12321236
}
1233-
default:
1237+
default: doDefault:
12341238
{
12351239
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
12361240
}
@@ -1429,9 +1433,7 @@ Preview_ToggleSlideShowEx(PPREVIEW_DATA pData, BOOL StartTimer)
14291433

14301434
if (IsWindowVisible(g_hwndFullscreen))
14311435
{
1432-
KillTimer(g_hwndFullscreen, SLIDESHOW_TIMER_ID);
1433-
ShowWindow(g_hMainWnd, SW_SHOW);
1434-
ShowWindow(g_hwndFullscreen, SW_HIDE);
1436+
Preview_EndSlideShow(g_hwndFullscreen);
14351437
}
14361438
else
14371439
{
@@ -1577,6 +1579,10 @@ Preview_OnCommand(HWND hwnd, UINT nCommandID)
15771579
Preview_Edit(hwnd);
15781580
break;
15791581

1582+
case IDC_HELP_TOC:
1583+
DisplayHelp(hwnd);
1584+
break;
1585+
15801586
default:
15811587
break;
15821588
}
@@ -1693,6 +1699,13 @@ PreviewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
16931699
Preview_OnDestroy(hwnd);
16941700
break;
16951701
}
1702+
case WM_CONTEXTMENU:
1703+
{
1704+
PPREVIEW_DATA pData = Preview_GetData(hwnd);
1705+
if ((int)lParam == -1)
1706+
return ZoomWndProc(pData->m_hwndZoom, uMsg, wParam, lParam);
1707+
break;
1708+
}
16961709
case WM_TIMER:
16971710
{
16981711
if (wParam == SLIDESHOW_TIMER_ID)

dll/win32/shimgvw/shimgvw.h

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#define _INC_WINDOWS
1313
#define COM_NO_WINDOWS_H
1414
#define INITGUID
15+
#define COBJMACROS
1516

1617
#include <windef.h>
1718
#include <winbase.h>
@@ -23,6 +24,7 @@
2324
#include <gdiplus.h>
2425
#include <shlwapi.h>
2526
#include <strsafe.h>
27+
#include <shobjidl.h>
2628

2729
#include <debug.h>
2830

@@ -69,6 +71,9 @@ void Anime_Start(PANIME pAnime, DWORD dwDelay);
6971
void Anime_Pause(PANIME pAnime);
7072
BOOL Anime_OnTimer(PANIME pAnime, WPARAM wParam);
7173

74+
void DoShellContextMenuOnFile(HWND hwnd, PCWSTR File, LPARAM lParam);
75+
void DisplayHelp(HWND hwnd);
76+
7277
static inline LPVOID QuickAlloc(SIZE_T cbSize, BOOL bZero)
7378
{
7479
return HeapAlloc(GetProcessHeap(), (bZero ? HEAP_ZERO_MEMORY : 0), cbSize);

dll/win32/shimgvw/util.c

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* PROJECT: ReactOS Picture and Fax Viewer
3+
* LICENSE: GPL-2.0 (https://spdx.org/licenses/GPL-2.0)
4+
* PURPOSE: Utility routines
5+
* COPYRIGHT: Copyright 2025 Whindmar Saksit <[email protected]>
6+
*/
7+
8+
#include "shimgvw.h"
9+
#include <windowsx.h>
10+
#include <shlobj.h>
11+
#include <shellapi.h>
12+
#include <shellutils.h>
13+
#include <shlwapi_undoc.h>
14+
15+
IContextMenu *g_pContextMenu = NULL;
16+
17+
static int
18+
GetMenuItemIdByPos(HMENU hMenu, UINT Pos)
19+
{
20+
MENUITEMINFOW mii;
21+
mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem); /* USER32 version agnostic */
22+
mii.fMask = MIIM_ID;
23+
mii.cch = 0;
24+
return GetMenuItemInfoW(hMenu, Pos, TRUE, &mii) ? mii.wID : -1;
25+
}
26+
27+
static BOOL
28+
IsMenuSeparator(HMENU hMenu, UINT Pos)
29+
{
30+
MENUITEMINFOW mii;
31+
mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem); /* USER32 version agnostic */
32+
mii.fMask = MIIM_FTYPE;
33+
mii.cch = 0;
34+
return GetMenuItemInfoW(hMenu, Pos, TRUE, &mii) && (mii.fType & MFT_SEPARATOR);
35+
}
36+
37+
static BOOL
38+
IsSelfShellVerb(PCWSTR Assoc, PCWSTR Verb)
39+
{
40+
WCHAR buf[MAX_PATH * 3];
41+
DWORD cch = _countof(buf);
42+
HRESULT hr = AssocQueryStringW(ASSOCF_NOTRUNCATE, ASSOCSTR_COMMAND, Assoc, Verb, buf, &cch);
43+
return hr == S_OK && *Assoc == L'.' && StrStrW(buf, L",ImageView_Fullscreen");
44+
}
45+
46+
static void
47+
ModifyShellContextMenu(IContextMenu *pCM, HMENU hMenu, UINT CmdIdFirst, PCWSTR Assoc)
48+
{
49+
HRESULT hr;
50+
UINT id, i;
51+
for (i = 0; i < GetMenuItemCount(hMenu); ++i)
52+
{
53+
WCHAR buf[200];
54+
id = GetMenuItemIdByPos(hMenu, i);
55+
if (id == (UINT)-1)
56+
continue;
57+
58+
*buf = UNICODE_NULL;
59+
/* Note: We just ask for the wide string because all the items we care about come from shell32 and it handles both */
60+
hr = IContextMenu_GetCommandString(pCM, id - CmdIdFirst, GCS_VERBW, NULL, (char*)buf, _countof(buf));
61+
if (SUCCEEDED(hr))
62+
{
63+
UINT remove = FALSE;
64+
if (IsSelfShellVerb(Assoc, buf))
65+
++remove;
66+
else if (!lstrcmpiW(L"cut", buf) || !lstrcmpiW(L"copy", buf) || !lstrcmpiW(L"link", buf))
67+
++remove;
68+
69+
if (remove && DeleteMenu(hMenu, i, MF_BYPOSITION))
70+
{
71+
if (i-- > 0)
72+
{
73+
if (IsMenuSeparator(hMenu, i) && IsMenuSeparator(hMenu, i + 1))
74+
DeleteMenu(hMenu, i, MF_BYPOSITION);
75+
}
76+
}
77+
}
78+
}
79+
80+
while (IsMenuSeparator(hMenu, 0) && DeleteMenu(hMenu, 0, MF_BYPOSITION)) {}
81+
}
82+
83+
static LRESULT CALLBACK
84+
ShellContextMenuWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
85+
{
86+
LRESULT lRes = 0;
87+
if (FAILED(SHForwardContextMenuMsg((IUnknown*)g_pContextMenu, uMsg, wParam, lParam, &lRes, TRUE)))
88+
lRes = DefWindowProc(hwnd, uMsg, wParam, lParam);
89+
return lRes;
90+
}
91+
92+
static void
93+
DoShellContextMenu(HWND hwnd, IContextMenu *pCM, PCWSTR File, LPARAM lParam)
94+
{
95+
enum { first = 1, last = 0x7fff };
96+
HRESULT hr;
97+
HMENU hMenu = CreatePopupMenu();
98+
UINT cmf = GetKeyState(VK_SHIFT) < 0 ? CMF_EXTENDEDVERBS : 0;
99+
100+
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
101+
if ((int)lParam == -1)
102+
{
103+
RECT rect;
104+
GetWindowRect(hwnd, &rect);
105+
pt.x = (rect.left + rect.right) / 2;
106+
pt.y = rect.top;
107+
}
108+
109+
g_pContextMenu = pCM;
110+
hwnd = SHCreateWorkerWindowW(ShellContextMenuWindowProc, hwnd, 0, WS_VISIBLE | WS_CHILD, NULL, 0);
111+
if (!hwnd)
112+
goto die;
113+
hr = IContextMenu_QueryContextMenu(pCM, hMenu, 0, first, last, cmf | CMF_NODEFAULT);
114+
if (SUCCEEDED(hr))
115+
{
116+
UINT id;
117+
ModifyShellContextMenu(pCM, hMenu, first, PathFindExtensionW(File));
118+
id = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, pt.x, pt.y, hwnd, NULL);
119+
if (id)
120+
{
121+
UINT flags = (GetKeyState(VK_SHIFT) < 0 ? CMIC_MASK_SHIFT_DOWN : 0) |
122+
(GetKeyState(VK_CONTROL) < 0 ? CMIC_MASK_CONTROL_DOWN : 0);
123+
CMINVOKECOMMANDINFO ici = { sizeof(ici), flags, hwnd, MAKEINTRESOURCEA(id - first) };
124+
ici.nShow = SW_SHOW;
125+
hr = IContextMenu_InvokeCommand(pCM, &ici);
126+
}
127+
}
128+
DestroyWindow(hwnd);
129+
die:
130+
DestroyMenu(hMenu);
131+
g_pContextMenu = NULL;
132+
}
133+
134+
void
135+
DoShellContextMenuOnFile(HWND hwnd, PCWSTR File, LPARAM lParam)
136+
{
137+
HRESULT hr;
138+
IShellFolder *pSF;
139+
PCUITEMID_CHILD pidlItem;
140+
PIDLIST_ABSOLUTE pidl = ILCreateFromPath(File);
141+
if (pidl && SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &pidlItem)))
142+
{
143+
IContextMenu *pCM;
144+
hr = IShellFolder_GetUIObjectOf(pSF, hwnd, 1, &pidlItem, &IID_IContextMenu, NULL, (void**)&pCM);
145+
if (SUCCEEDED(hr))
146+
{
147+
DoShellContextMenu(hwnd, pCM, File, lParam);
148+
IContextMenu_Release(pCM);
149+
}
150+
IShellFolder_Release(pSF);
151+
}
152+
SHFree(pidl);
153+
}
154+
155+
void DisplayHelp(HWND hwnd)
156+
{
157+
SHELL_ErrorBox(hwnd, ERROR_NOT_SUPPORTED);
158+
}

sdk/include/reactos/shellutils.h

+7-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
extern "C" {
2424
#endif /* defined(__cplusplus) */
2525

26-
inline ULONG
26+
static inline ULONG
2727
Win32DbgPrint(const char *filename, int line, const char *lpFormat, ...)
2828
{
2929
char szMsg[512];
@@ -63,19 +63,19 @@ Win32DbgPrint(const char *filename, int line, const char *lpFormat, ...)
6363
# define IID_PPV_ARG(Itype, ppType) IID_##Itype, reinterpret_cast<void**>((static_cast<Itype**>(ppType)))
6464
# define IID_NULL_PPV_ARG(Itype, ppType) IID_##Itype, NULL, reinterpret_cast<void**>((static_cast<Itype**>(ppType)))
6565
#else
66-
# define IID_PPV_ARG(Itype, ppType) IID_##Itype, (void**)(ppType)
67-
# define IID_NULL_PPV_ARG(Itype, ppType) IID_##Itype, NULL, (void**)(ppType)
66+
# define IID_PPV_ARG(Itype, ppType) &IID_##Itype, (void**)(ppType)
67+
# define IID_NULL_PPV_ARG(Itype, ppType) &IID_##Itype, NULL, (void**)(ppType)
6868
#endif
6969

70-
inline HRESULT HResultFromWin32(DWORD hr)
70+
static inline HRESULT HResultFromWin32(DWORD hr)
7171
{
7272
// HRESULT_FROM_WIN32 will evaluate its parameter twice, this function will not.
7373
return HRESULT_FROM_WIN32(hr);
7474
}
7575

7676
#if 1
7777

78-
inline BOOL _ROS_FAILED_HELPER(HRESULT hr, const char* expr, const char* filename, int line)
78+
static inline BOOL _ROS_FAILED_HELPER(HRESULT hr, const char* expr, const char* filename, int line)
7979
{
8080
if (FAILED(hr))
8181
{
@@ -122,6 +122,8 @@ SHELL_ErrorBox(H hwndOwner, UINT Error = GetLastError())
122122
{
123123
return SHELL_ErrorBoxHelper(const_cast<HWND>(hwndOwner), Error);
124124
}
125+
#else
126+
#define SHELL_ErrorBox SHELL_ErrorBoxHelper
125127
#endif
126128

127129
#ifdef __cplusplus

0 commit comments

Comments
 (0)