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

Reply via email to