From 223a85548c4f68e4ad703516e7d27cdb7619e106 Mon Sep 17 00:00:00 2001 From: lynnux Date: Wed, 16 Nov 2022 16:48:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=E5=BC=B9?= =?UTF-8?q?=E5=87=BAshell=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + ReadMe.md | 6 + build.rs | 1 + src/ShellMenu.cpp | 556 +++++++++++++++++++++++++++++++++++++++++ src/ShellMenu.h | 82 ++++++ src/lib.rs | 14 ++ src/shellmenu.rs | 41 +++ src/shellmenu_wrap.cpp | 18 ++ 8 files changed, 719 insertions(+) create mode 100644 src/ShellMenu.cpp create mode 100644 src/ShellMenu.h create mode 100644 src/shellmenu.rs create mode 100644 src/shellmenu_wrap.cpp diff --git a/Cargo.toml b/Cargo.toml index 454e12b..7beb667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ crate-type = ["cdylib"] [build-dependencies] embed-resource = "1" +cc = "1.0" diff --git a/ReadMe.md b/ReadMe.md index 111e922..2705b8b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -129,3 +129,9 @@ # 4.开启win10的dark mode(emacs29版本自带,适合29以下版本) 调用`(pop-select/ensure-all-window-dark-mode)`即可,不过目前标题可能不会立即刷新,建议加个`(w32-send-sys-command #xf030)`最大化就可以了 +# 5.弹出shell右键菜单 +``` +(pop-select/popup-shell-menu PATH X Y) +``` +PATH即路径,目录/文件都可以,X、Y即屏幕座标,如何都是0,那么会在当前鼠标指针位置弹出。 +我是拿来配合dired使用 diff --git a/build.rs b/build.rs index 3092f97..e697d13 100644 --- a/build.rs +++ b/build.rs @@ -2,4 +2,5 @@ fn main() { embed_resource::compile("src/res.rc"); + cc::Build::new().define("UNICODE", "1").file("src/ShellMenu.cpp").file("src/shellmenu_wrap.cpp").compile("foo"); } diff --git a/src/ShellMenu.cpp b/src/ShellMenu.cpp new file mode 100644 index 0000000..d16b5ae --- /dev/null +++ b/src/ShellMenu.cpp @@ -0,0 +1,556 @@ +/* +Copyright (C) 2004 Jacquelin POTIER +Dynamic aspect ratio code Copyright (C) 2004 Jacquelin POTIER + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +//----------------------------------------------------------------------------- +// Object: class helper shell context menu +//----------------------------------------------------------------------------- + +// #include "stdafx.h" +#include +#include +#include "shellmenu.h" + +//----------------------------------------------------------------------------- +// Name: CShellMenu +// Object: Constructor +// WARNING : CoInitialize or OleInitialize must have been called before creating object +// Parameters : +// in : HWND hWndDialog : handle of window that owns the menu +// int SpecialFolderCSIDL : CSIDL of special folder (CSIDL_DESKTOP, ...) +// out : +// return : +//----------------------------------------------------------------------------- +CShellMenu::CShellMenu(HWND hWndDialog,int SpecialFolderCSIDL) +{ + this->CommonConstructor(hWndDialog); + this->bFillMenuSuccess=this->FillMenu(SpecialFolderCSIDL); +} + +//----------------------------------------------------------------------------- +// Name: CShellMenu +// Object: Constructor +// WARNING : CoInitialize or OleInitialize must have been called before creating object +// Parameters : +// in : HWND hWndDialog : handle of window that owns the menu +// int SpecialFolderCSIDL : CSIDL of special folder (CSIDL_DESKTOP, ...) +// out : +// return : +//----------------------------------------------------------------------------- +CShellMenu::CShellMenu(HWND hWndDialog,int SpecialFolderCSIDL,BOOL bFolderBackgroundMenu) +{ + this->CommonConstructor(hWndDialog); + this->bFolderBackgroundMenu=bFolderBackgroundMenu; + this->bFillMenuSuccess=this->FillMenu(SpecialFolderCSIDL); +} + +//----------------------------------------------------------------------------- +// Name: CShellMenu +// Object: Constructor. +// WARNING : CoInitialize or OleInitialize must have been called before creating object +// Parameters : +// in : HWND hWndDialog : handle of window that owns the menu +// TCHAR* FileOrDirectoryPath : folder or file path +// out : +// return : +//----------------------------------------------------------------------------- +CShellMenu::CShellMenu(HWND hWndDialog,TCHAR* FileOrDirectoryPath) +{ + this->CommonConstructor(hWndDialog); + this->bFillMenuSuccess=this->FillMenu(FileOrDirectoryPath); +} + +//----------------------------------------------------------------------------- +// Name: CShellMenu +// Object: Constructor. +// WARNING : CoInitialize or OleInitialize must have been called before creating object +// Parameters : +// in : HWND hWndDialog : handle of window that owns the menu +// TCHAR* FileOrDirectoryPath : folder or file path +// out : +// return : +//----------------------------------------------------------------------------- +CShellMenu::CShellMenu(HWND hWndDialog,TCHAR* FileOrDirectoryPath,BOOL bFolderBackgroundMenu) +{ + this->CommonConstructor(hWndDialog); + this->bFolderBackgroundMenu=bFolderBackgroundMenu; + this->bFillMenuSuccess=this->FillMenu(FileOrDirectoryPath); +} + +//----------------------------------------------------------------------------- +// Name: CommonConstructor +// Object: Common Constructor. +// Parameters : +// in : HWND hWndDialog : handle of window that owns the menu +// out : +// return : +//----------------------------------------------------------------------------- +void CShellMenu::CommonConstructor(HWND hWndDialog) +{ + this->hWndDialog=hWndDialog; + this->pContextMenu=NULL; + this->pContextMenu2=NULL; + this->pContextMenu3=NULL; + this->bFolderBackgroundMenu=FALSE; + + // create menu + this->hPopUpMenu=::CreatePopupMenu(); + MENUINFO mnfo; + mnfo.cbSize = sizeof(mnfo); + mnfo.fMask = MIM_STYLE; + mnfo.dwStyle = MNS_CHECKORBMP; //| MNS_AUTODISMISS can make menu to disappear when used on toolbar + ::SetMenuInfo(this->hPopUpMenu, &mnfo); +} + +//----------------------------------------------------------------------------- +// Name: ~CShellMenu +// Object: Destructor +// Parameters : +// in : +// out : +// return : +//----------------------------------------------------------------------------- +CShellMenu::~CShellMenu(void) +{ + if (this->pContextMenu3) + this->pContextMenu3->Release(); + if (this->pContextMenu2) + this->pContextMenu2->Release(); + if (this->pContextMenu) + this->pContextMenu->Release(); + + // destroy menu + ::DestroyMenu(this->hPopUpMenu); +} + +//----------------------------------------------------------------------------- +// Name: IsCreationSuccessful +// Object: check menu creation success +// Parameters : +// in : +// out : +// return : TRUE if menu has been created successfully +//----------------------------------------------------------------------------- +BOOL CShellMenu::IsCreationSuccessful() +{ + return this->bFillMenuSuccess; +} + +//----------------------------------------------------------------------------- +// Name: SubClassWindowProc +// Object: subclass window proc of owning window +// Parameters : +// in : +// out : +// return : TRUE +//----------------------------------------------------------------------------- +BOOL CShellMenu::SubClassWindowProc() +{ + UINT_PTR uIdSubclass=0; + DWORD_PTR dwRefData=(DWORD_PTR)this; + return SetWindowSubclass(this->hWndDialog,CShellMenu::HookWndProc,uIdSubclass,dwRefData); +} + +//----------------------------------------------------------------------------- +// Name: UnSubClassWindowProc +// Object: remove window proc subclassing of owning window +// Parameters : +// in : +// out : +// return : TRUE +//----------------------------------------------------------------------------- +BOOL CShellMenu::UnSubClassWindowProc() +{ + UINT_PTR uIdSubclass=0; + // remove subclassing wndproc + return RemoveWindowSubclass(this->hWndDialog,CShellMenu::HookWndProc,uIdSubclass); +} + +//----------------------------------------------------------------------------- +// Name: BeforeParentMenuShow +// Object: MUST BE CALLED ONLY IF USED AS SUBMENU, BEFORE THE PARENT MENU TrackPopupMenu CALL +// Parameters : +// in : +// out : +// return : TRUE on success +//----------------------------------------------------------------------------- +BOOL CShellMenu::BeforeParentMenuShow() +{ + return this->SubClassWindowProc(); +} + +//----------------------------------------------------------------------------- +// Name: AfterParentMenuShow +// Object: MUST BE CALLED ONLY IF USED AS SUBMENU, AFTER THE PARENT MENU TrackPopupMenu CALL +// Parameters : +// in : +// out : +// return : TRUE on success +//----------------------------------------------------------------------------- +BOOL CShellMenu::AfterParentMenuShow() +{ + return this->UnSubClassWindowProc(); +} + +//----------------------------------------------------------------------------- +// Name: InvokeCommand +// Object: MUST BE CALLED ONLY IF USED AS SUBMENU, AFTER THE PARENT MENU TrackPopupMenu CALL +// Parameters : +// in : +// out : +// return : TRUE on success +//----------------------------------------------------------------------------- +HRESULT CShellMenu::InvokeCommand(CMINVOKECOMMANDINFO* pCmdInfo) +{ + if (!this->pContextMenu) + return E_FAIL; + + return this->pContextMenu->InvokeCommand(pCmdInfo); +} + +//----------------------------------------------------------------------------- +// Name: FillMenu +// Object: fill the created menu for special folder +// Parameters : +// in : int SpecialFolderCSIDL : CSIDL of special folder (CSIDL_DESKTOP, ...) +// out : +// return : TRUE on success +//----------------------------------------------------------------------------- +BOOL CShellMenu::FillMenu(int SpecialFolderCSIDL) +{ + BOOL bRet=FALSE; + HRESULT hResult; + ITEMIDLIST* pFullpidl=NULL; + ITEMIDLIST* pFolderId=NULL; + IShellFolder* pShellFolder=NULL; + IMalloc* pMalloc=NULL; + + // get pointer to malloc interface + hResult = ::SHGetMalloc(&pMalloc); + if (FAILED(hResult)||(!pMalloc)) + goto CleanUp; + + // get folder full idl list + hResult = ::SHGetFolderLocation(this->hWndDialog,SpecialFolderCSIDL,NULL,NULL,&pFullpidl); + if ( FAILED(hResult) || (!pFullpidl) ) + goto CleanUp; + + // get upper folder and object id from full pidl + hResult = ::SHBindToParent(pFullpidl, IID_IShellFolder, (void**)&pShellFolder,(LPCITEMIDLIST*)&pFolderId); + if ( FAILED(hResult) || (!pFolderId) || (!pShellFolder) ) + goto CleanUp; + + if (this->bFolderBackgroundMenu) + { + // for shellview background menu + //IShellView* pShellView=NULL; + //pShellFolder->CreateViewObject(this->hWndDialog,IID_IShellView,(void**)&pShellView); + //pShellView->GetItemObject(SVGIO_BACKGROUND,IID_IContextMenu,(void**)&this->pContextMenu); + + hResult = pShellFolder->CreateViewObject(this->hWndDialog,IID_IContextMenu,(void**)&this->pContextMenu); + if ( FAILED(hResult) || (!this->pContextMenu) ) + goto CleanUp; + bRet=this->FillContextMenuMember(pShellFolder); + } + else + { + // fill the menu from matching IShellFolder and folder id + bRet=this->FillMenu(pShellFolder,pFolderId); + } + + if (!bRet) + goto CleanUp; + + bRet=TRUE; + +CleanUp: + + if (pFullpidl) + pMalloc->Free(pFullpidl); + + if (pMalloc) + pMalloc->Release(); + + if (pShellFolder) + pShellFolder->Release(); + + return bRet; +} + +//----------------------------------------------------------------------------- +// Name: FillMenu +// Object: fill the created menu for folder or file +// Parameters : +// in : TCHAR* FileOrDirectoryPath : folder or file path +// out : +// return : TRUE on success +//----------------------------------------------------------------------------- +BOOL CShellMenu::FillMenu(TCHAR* FileOrDirectoryPath) +{ + BOOL bRet=FALSE; + HRESULT hResult; + WCHAR* pwscPath; + IShellFolder* pDesktopFolder=NULL; + IShellFolder* pShellFolder=NULL; + IMalloc* pMalloc=NULL; + ITEMIDLIST* pFullpidl=NULL; + ITEMIDLIST* pFolderId=NULL; + + // check parameter + if (::IsBadReadPtr(FileOrDirectoryPath,sizeof(TCHAR))) + return FALSE; + + // convert ansi to unicode if needed +#if ( defined(UNICODE) || defined(_UNICODE)) + pwscPath=FileOrDirectoryPath; +#else + size_t NbCharacters = strlen(FileOrDirectoryPath)+1; + + pwscPath = (WCHAR*) malloc(NbCharacters*sizeof(WCHAR)); + if (!pwscPath) + return FALSE; + + // Convert to Unicode. + if (::MultiByteToWideChar(CP_ACP, 0, FileOrDirectoryPath, (int)NbCharacters,pwscPath, (int)NbCharacters)==0) + { + free(pwscPath); + return FALSE; + } +#endif + + // get pointer to malloc interface + hResult = ::SHGetMalloc(&pMalloc); + if (FAILED(hResult)||(!pMalloc)) + goto CleanUp; + + // get root folder + hResult= ::SHGetDesktopFolder(&pDesktopFolder); + if (FAILED(hResult)||(!pDesktopFolder)) + goto CleanUp; + + // get object full pidl from path name + hResult = pDesktopFolder->ParseDisplayName(NULL, + NULL, + pwscPath, + NULL, + &pFullpidl, + NULL); + if ( FAILED(hResult) || (!pFullpidl) ) + goto CleanUp; + + // get upper folder and object id from full pidl + hResult = ::SHBindToParent(pFullpidl, IID_IShellFolder, (void**)&pShellFolder,(LPCITEMIDLIST*)&pFolderId); + if ( FAILED(hResult) || (!pFolderId) || (!pShellFolder) ) + goto CleanUp; + + if (this->bFolderBackgroundMenu) + { + // for shellview background menu + //IShellView* pShellView=NULL; + //pShellFolder->CreateViewObject(this->hWndDialog,IID_IShellView,(void**)&pShellView); + //pShellView->GetItemObject(SVGIO_BACKGROUND,IID_IContextMenu,(void**)&this->pContextMenu); + + hResult = pShellFolder->CreateViewObject(this->hWndDialog,IID_IContextMenu,(void**)&this->pContextMenu); + if ( FAILED(hResult) || (!this->pContextMenu) ) + goto CleanUp; + bRet=this->FillContextMenuMember(pShellFolder); + } + else + { + // fill the menu from matching IShellFolder and item (folder or file) id + bRet=this->FillMenu(pShellFolder,pFolderId); + } + + if (!bRet) + goto CleanUp; + + bRet=TRUE; + +CleanUp: + +#if ( (!defined(UNICODE)) && (!defined(_UNICODE)) ) + if (pwscPath) + free(pwscPath); +#endif + + if (pFullpidl) + pMalloc->Free(pFullpidl); + + if (pMalloc) + pMalloc->Release(); + + if (pShellFolder) + pShellFolder->Release(); + + if (pDesktopFolder) + pDesktopFolder->Release(); + + return bRet; +} + +//----------------------------------------------------------------------------- +// Name: FillMenu +// Object: fill the created menu for specified item +// Parameters : +// in : IShellFolder* pShellFolder : IShellFolder object +// LPCITEMIDLIST pItemIdList : id of folder or file +// out : +// return : TRUE on success +//----------------------------------------------------------------------------- +BOOL CShellMenu::FillMenu(IShellFolder* pShellFolder,LPCITEMIDLIST pItemIdList) +{ + HRESULT hResult; + // get IContextMenu interface for specified item + hResult = pShellFolder->GetUIObjectOf(hWndDialog,1,&pItemIdList,IID_IContextMenu,NULL,(void**)&this->pContextMenu); + if (FAILED(hResult)||(!this->pContextMenu)) + return FALSE; + + return this->FillContextMenuMember(pShellFolder); +} + +//----------------------------------------------------------------------------- +// Name: FillMenu +// Object: fill the created menu for specified item +// Parameters : +// in : IShellFolder* pShellFolder : IShellFolder object +// LPCITEMIDLIST pItemIdList : id of folder or file +// out : +// return : TRUE on success +//----------------------------------------------------------------------------- +BOOL CShellMenu::FillContextMenuMember(IShellFolder* pShellFolder) +{ + HRESULT hResult; + + // try to get IContextMenu3 or at least IContextMenu2 interface for specified item + hResult = this->pContextMenu->QueryInterface (IID_IContextMenu3,(void**)&this->pContextMenu3); + if (FAILED(hResult) || (!this->pContextMenu3) ) + this->pContextMenu->QueryInterface (IID_IContextMenu2,(void**)&this->pContextMenu2); + + // get popupmenu content + hResult = this->pContextMenu->QueryContextMenu(this->hPopUpMenu, + 0, + CShellMenu_idCmdFirst, + CShellMenu_idCmdLast, + CMF_NORMAL + ); + + return SUCCEEDED(hResult); +} + + +//----------------------------------------------------------------------------- +// Name: Show +// Object: MUST BE CALLED ONLY IF NO PARENT MENU (else menu will be shown for the parent menu TrackPopupMenu call) +// Parameters : +// in : int x : x menu position in screen coordinates +// int y : y menu position in screen coordinates +// out : +// return : TRUE on success, FALSE on error or if user has cancel operation +//----------------------------------------------------------------------------- +BOOL CShellMenu::Show(int x,int y) +{ + if ( (!this->pContextMenu) || (!this->hPopUpMenu) ) + return FALSE; + + UINT CmdId; + + // add window subclassing + this->SubClassWindowProc(); + + // show popupmenu + CmdId=(UINT)::TrackPopupMenuEx(this->hPopUpMenu,TPM_LEFTALIGN|TPM_RETURNCMD,x,y,this->hWndDialog,NULL); + + if (CmdId==0) + { + if (::GetLastError()==ERROR_POPUP_ALREADY_ACTIVE) + CmdId=(UINT)::TrackPopupMenuEx(this->hPopUpMenu,TPM_LEFTALIGN|TPM_RETURNCMD|TPM_RECURSE,x,y,this->hWndDialog,NULL); + } + + // remove subclassing + this->UnSubClassWindowProc(); + + // if one of our cmd id + if ( (CShellMenu_idCmdFirst<=CmdId) && (CmdId<=CShellMenu_idCmdLast) ) + { + CMINVOKECOMMANDINFO CmdInfo; + memset(&CmdInfo,0,sizeof(CMINVOKECOMMANDINFO)); + CmdInfo.cbSize=sizeof(CMINVOKECOMMANDINFO); + CmdInfo.lpVerb=MAKEINTRESOURCEA (CmdId - CShellMenu_idCmdFirst); + CmdInfo.nShow=SW_SHOWNORMAL; + CmdInfo.hwnd=hWndDialog; + + // invoke the required command + if (FAILED(this->pContextMenu->InvokeCommand(&CmdInfo))) + return FALSE; + } + return (CmdId!=0); +} + + +//----------------------------------------------------------------------------- +// Name: HookWndProc +// Object: sub classing window proc +// REQUIRED TO DISPLAY ALL CONTEXT MENU ITEMS (plugins items and "Send To" content) +// Parameters : +// in : WndProc params +// out : +// return : WndProc return +//----------------------------------------------------------------------------- +LRESULT CALLBACK CShellMenu::HookWndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam,UINT_PTR uIdSubclass,DWORD_PTR dwRefData) +{ + UNREFERENCED_PARAMETER(uIdSubclass); + + CShellMenu* pShellMenu=(CShellMenu*)dwRefData; + if (!pShellMenu) + return TRUE; + + switch (uMsg) + { + case WM_MENUCHAR: // only supported by IContextMenu3 + if (pShellMenu->pContextMenu3) + { + LRESULT lResult = 0; + pShellMenu->pContextMenu3->HandleMenuMsg2 (uMsg, wParam, lParam, &lResult); + return lResult; + } + break; + + case WM_DRAWITEM: + case WM_MEASUREITEM: + if (wParam) + break; // if wParam != 0 then the message is not menu-related + + case WM_INITMENUPOPUP: + { + if (pShellMenu->pContextMenu3) + { + LRESULT lResult = 0; + pShellMenu->pContextMenu3->HandleMenuMsg2 (uMsg, wParam, lParam, &lResult); + return lResult; + } + else if (pShellMenu->pContextMenu2) + { + pShellMenu->pContextMenu2->HandleMenuMsg (uMsg, wParam, lParam); + } + return (uMsg == WM_INITMENUPOPUP ? 0 : TRUE); // inform caller that we handled WM_INITPOPUPMENU by ourself + + } + break; + } + + return ::DefSubclassProc(hWnd,uMsg,wParam,lParam); +} diff --git a/src/ShellMenu.h b/src/ShellMenu.h new file mode 100644 index 0000000..fed80a3 --- /dev/null +++ b/src/ShellMenu.h @@ -0,0 +1,82 @@ +/* +Copyright (C) 2004 Jacquelin POTIER +Dynamic aspect ratio code Copyright (C) 2004 Jacquelin POTIER + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +//----------------------------------------------------------------------------- +// Object: class helper shell context menu +//----------------------------------------------------------------------------- + +#pragma once + +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 +#endif + +#include +#pragma warning (push) +#pragma warning(disable : 4005)// for '_stprintf' : macro redefinition in tchar.h +#include +#pragma warning (pop) +#include +#include +#pragma comment (lib,"comctl32.lib") + +#define CShellMenu_idCmdFirst 0xF000// Cmd must be on WORD see CMINVOKECOMMANDINFO.lpVerb +#define CShellMenu_idCmdLast 0xFFFF// Cmd must be on WORD see CMINVOKECOMMANDINFO.lpVerb + +class CShellMenu +{ +private: + BOOL bFolderBackgroundMenu; + BOOL bFillMenuSuccess; + HWND hWndDialog; + HMENU hPopUpMenu; + IContextMenu* pContextMenu; + IContextMenu2* pContextMenu2; + IContextMenu3* pContextMenu3; + + void CommonConstructor(HWND hWndDialog); + BOOL SubClassWindowProc(); + BOOL UnSubClassWindowProc(); + BOOL FillMenu(int SpecialFolderCSIDL); + BOOL FillMenu(TCHAR* FileOrDirectoryPath); + BOOL FillMenu(IShellFolder* pShellFolder,LPCITEMIDLIST pItemIdList); + BOOL FillContextMenuMember(IShellFolder* pShellFolder); + static LRESULT CALLBACK HookWndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam,UINT_PTR uIdSubclass,DWORD_PTR dwRefData); +public: + CShellMenu(HWND hWndDialog,int SpecialFolderCSIDL); + CShellMenu(HWND hWndDialog,int SpecialFolderCSIDL,BOOL bBackgroundMenu); + CShellMenu(HWND hWndDialog,TCHAR* FileOrDirectoryPath); + CShellMenu(HWND hWndDialog,TCHAR* FileOrDirectoryPath,BOOL bBackgroundMenu); + ~CShellMenu(void); + + BOOL IsCreationSuccessful(); + + ////////////////////////////////////////////////////////////////////////////// + // The following method should be called only if Shellmenu is NOT used as submenu + ////////////////////////////////////////////////////////////////////////////// + BOOL Show(int x,int y);// must be called only if no parent menu, else menu will be shown for the parent menu TrackPopupMenu call + + + ////////////////////////////////////////////////////////////////////////////// + // The following method should be called only if Shellmenu is used as submenu + ////////////////////////////////////////////////////////////////////////////// + BOOL BeforeParentMenuShow();// must be called only if used as submenu, before the parent menu TrackPopupMenu call + BOOL AfterParentMenuShow();// must be called only if used as submenu, after the parent menu TrackPopupMenu call + HMENU GetControlHandle();// return the shell menu HMENU (can be called to use Shell menu as a submenu) + HRESULT InvokeCommand(CMINVOKECOMMANDINFO* pCmdInfo);// must be called only if used as submenu, after the parent menu TrackPopupMenu call +}; diff --git a/src/lib.rs b/src/lib.rs index bc0f56d..797ec30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod beacon; mod gui; +mod shellmenu; mod transparent; // (module-load "pop_select") @@ -15,6 +16,7 @@ use emacs::{ }; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; +use winapi::um::debugapi::OutputDebugStringW; // Emacs won't load the module without this. emacs::plugin_is_GPL_compatible!(); @@ -76,3 +78,15 @@ fn ensure_all_window_dark_mode() -> Result { crate::gui::ensure_all_window_dark_mode(); Ok(0) } + +#[defun] +fn popup_shell_menu(path: String, x: usize, y: usize) -> Result { + if let Err(e) = crate::shellmenu::pop_shell_menu(path, x, y) { + let es = format!("popup_shell_menu error: {}", e); + let esw = to_wstring(&es); + unsafe { + OutputDebugStringW(esw.as_ptr()); + } + } + Ok(0) +} diff --git a/src/shellmenu.rs b/src/shellmenu.rs new file mode 100644 index 0000000..54f894c --- /dev/null +++ b/src/shellmenu.rs @@ -0,0 +1,41 @@ +extern crate native_windows_gui as nwg; +use nwg::NwgError; +use winapi::{shared::ntdef::LPCWSTR, um::winuser::*}; + +extern "C" { + fn PopupShellMenu(h: winapi::shared::windef::HWND, path: LPCWSTR, x: i32, y: i32); +} + +pub fn pop_shell_menu(path: String, x: usize, y: usize) -> Result<(), NwgError> { + // use winapi::um::winuser::GetForegroundWindow; + // let h = GetForegroundWindow(); + // 测试发现SetWindowSubclass跨线程不会成功,因为emacs module的运行线程不是gui线程。所以这里需要跟ctrl+tab那样的处理机制。 + + nwg::init()?; // 必须,会注册ngw的类 + + let mut window = Default::default(); + nwg::Window::builder() + // .size((1, 1)) + .ex_flags(WS_EX_TOOLWINDOW) // 无任务栏窗口 + .flags(nwg::WindowFlags::POPUP) // | nwg::WindowFlags::VISIBLE + // .position((-1, -1)) + .build(&mut window)?; + + let handler = + nwg::bind_raw_event_handler(&window.handle, 0x10000, move |hwnd, msg, _w, _l| { + if msg == (WM_USER + 1) { + unsafe { + let p = crate::to_wstring(&path); + PopupShellMenu(hwnd, p.as_ptr(), x as i32, y as i32); + } + nwg::stop_thread_dispatch(); + } + None + })?; + unsafe { + winapi::um::winuser::PostMessageW(window.handle.hwnd().unwrap(), WM_USER + 1, 0, 0); + } + nwg::dispatch_thread_events(); + nwg::unbind_raw_event_handler(&handler)?; + Ok(()) +} diff --git a/src/shellmenu_wrap.cpp b/src/shellmenu_wrap.cpp new file mode 100644 index 0000000..7364da4 --- /dev/null +++ b/src/shellmenu_wrap.cpp @@ -0,0 +1,18 @@ +// ο룺 +// https://github.com/electrotype/vscode-windows-explorer-context-menu/blob/master/executables/AutohotkeyContextMenu.ahk +// https://github.com/Unknown6656/TabbedExplorer/blob/91fb81f07b7df36639b03b723ec818e60437bf1c/NativeInterop/main.cpp +// https://github.com/baohaojun/system-config/blob/e936ae4cae5764abfe4a3657ae7470532963e8ef/gcode/oc2/oc2/ShellMenu.cpp +// https://github.com/tmpprj/tmpprj/blob/1b1e19d01f6dbfdacdb3000778ca822e57862ca6/SearchGUI/src/contextmenu.cc +#include "ShellMenu.h" +#include + +extern "C"{ + void PopupShellMenu(HWND emacs, const wchar_t* path, LONG x, LONG y){ + CShellMenu cm(emacs, (TCHAR*)path); + POINT pt; + if(x == 0 && y == 0){ + GetCursorPos(&pt); + } + cm.Show(pt.x, pt.y); + } +}