From 80cdd1909432706ad1f55470128b86dc6315398d Mon Sep 17 00:00:00 2001 From: lynnux Date: Fri, 18 Nov 2022 16:31:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0shell=E7=9A=84copy/cut/paste?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 36 +++++- src/shellmenu.rs | 55 +++++++++- src/shellmenu_wrap.cpp | 242 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 313 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1a696fe..c4a8512 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ fn popup_shell_menu(paths: Vector, x: i32, y: i32, show_extra_head: i32) -> Resu if let Ok(ss) = paths.get(i) { v.push(ss); } - } + } if let Err(e) = crate::shellmenu::pop_shell_menu(v, x, y, show_extra_head) { let es = format!("popup_shell_menu error: {}", e); let esw = to_wstring(&es); @@ -99,3 +99,37 @@ fn popup_shell_menu(paths: Vector, x: i32, y: i32, show_extra_head: i32) -> Resu } Ok(0) } + +#[defun] +fn shell_copyfiles(paths: Vector) -> Result { + let mut v = Vec::new(); + if let Ok(s) = paths.size() { + for i in 0..s { + if let Ok(ss) = paths.get(i) { + v.push(ss); + } + } + crate::shellmenu::shell_copyfiles(v); + } + Ok(0) +} + +#[defun] +fn shell_cutfiles(paths: Vector) -> Result { + let mut v = Vec::new(); + if let Ok(s) = paths.size() { + for i in 0..s { + if let Ok(ss) = paths.get(i) { + v.push(ss); + } + } + crate::shellmenu::shell_cutfiles(v); + } + Ok(0) +} + +#[defun] +fn shell_pastefiles(path: String) -> Result { + crate::shellmenu::shell_pastefiles(path); + Ok(0) +} diff --git a/src/shellmenu.rs b/src/shellmenu.rs index ba585ce..f35894f 100644 --- a/src/shellmenu.rs +++ b/src/shellmenu.rs @@ -7,7 +7,16 @@ use winapi::{shared::ntdef::LPCWSTR, um::winuser::*}; // 再者,按MSDN说明TrackPopupMenuEx的父窗口需要前置,否则点空白处menu不会退出,所以这里是窗口最大化,并且设置成透明1(测试完全透明不行) extern "C" { - fn PopupShellMenu(h: winapi::shared::windef::HWND, path: *const LPCWSTR, x: i32, y: i32, showExtraHead: i32); + fn PopupShellMenu( + h: winapi::shared::windef::HWND, + path: *const LPCWSTR, + x: i32, + y: i32, + showExtraHead: i32, + ); + fn CopyPathsToClipboard(path: *const LPCWSTR); + fn CutPathsToClipboard(path: *const LPCWSTR); + fn PasteToPathFromClipboard(path: LPCWSTR); } thread_local! { @@ -22,7 +31,12 @@ pub fn shellmenu_init() { } } -pub fn pop_shell_menu(paths: Vec, x: i32, y: i32, show_extra_head: i32) -> Result<(), NwgError> { +pub fn pop_shell_menu( + paths: Vec, + x: i32, + y: i32, + show_extra_head: i32, +) -> Result<(), NwgError> { SHELL_PARENT_WND.with(|wnd| { if (*wnd.borrow()).is_none() { nwg::init().ok(); // 必须,会注册ngw的类 @@ -81,3 +95,40 @@ pub fn pop_shell_menu(paths: Vec, x: i32, y: i32, show_extra_head: i32) }); Ok(()) } + +pub fn shell_copyfiles(paths: Vec) { + let mut vp = Vec::new(); + for p in &paths { + vp.push(crate::to_wstring(&p)); + } + let mut vpr = Vec::new(); + for p in &vp { + vpr.push(p.as_ptr()); + } + vpr.push(std::ptr::null()); // 以0结尾 + unsafe { + CopyPathsToClipboard(vpr.as_ptr()); + } +} + +pub fn shell_cutfiles(paths: Vec) { + let mut vp = Vec::new(); + for p in &paths { + vp.push(crate::to_wstring(&p)); + } + let mut vpr = Vec::new(); + for p in &vp { + vpr.push(p.as_ptr()); + } + vpr.push(std::ptr::null()); // 以0结尾 + unsafe { + CutPathsToClipboard(vpr.as_ptr()); + } +} + +pub fn shell_pastefiles(path: String) { + let p = crate::to_wstring(&path); + unsafe { + PasteToPathFromClipboard(p.as_ptr()); + } +} diff --git a/src/shellmenu_wrap.cpp b/src/shellmenu_wrap.cpp index 334ad97..d5c9077 100644 --- a/src/shellmenu_wrap.cpp +++ b/src/shellmenu_wrap.cpp @@ -3,26 +3,234 @@ // 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 -// õgrepwinĴ +// shellõgrepwinĴ룬shellcopy/cut/pastehttps://github.com/VioletGiraffe/file-commander #include "ShellContextMenu.h" +#include -extern "C"{ - void PopupShellMenu(HWND emacs, const wchar_t** path, LONG x, LONG y, LONG showExtraHead){ - POINT pt; - if(x == 0 && y == 0){ - GetCursorPos(&pt); - } - - CShellContextMenu cm; - std::vector paths; - while(*path){ - CSearchInfo cs; - cs.filePath = *path; - paths.push_back(cs); - ++path; +bool copyObjectsToClipboard(const std::vector& objects, + void* parentWindow); +bool cutObjectsToClipboard(const std::vector& objects, + void* parentWindow); +bool pasteFilesAndFoldersFromClipboard(std::wstring destFolder, + void* parentWindow); + +extern "C" { +void PopupShellMenu(HWND emacs, const wchar_t** path, LONG x, LONG y, + LONG showExtraHead) { + POINT pt; + if (x == 0 && y == 0) { + GetCursorPos(&pt); + } + + CShellContextMenu cm; + std::vector paths; + while (*path) { + CSearchInfo cs; + cs.filePath = *path; + paths.push_back(cs); + ++path; + } + + cm.SetObjects(paths); + cm.ShowContextMenu(emacs, pt, showExtraHead != 0); +} + +// ģCTRL+C +void CopyPathsToClipboard(const wchar_t** path) { + std::vector paths; + while (*path) { + paths.push_back(*path); + ++path; + } + copyObjectsToClipboard(paths, GetForegroundWindow()); +} + +void CutPathsToClipboard(const wchar_t** path) { + std::vector paths; + while (*path) { + paths.push_back(*path); + ++path; + } + cutObjectsToClipboard(paths, GetForegroundWindow()); +} + +// ģCTRL+VҪĿ· +void PasteToPathFromClipboard(const wchar_t* target) { + pasteFilesAndFoldersFromClipboard(target, GetForegroundWindow()); +} +} + +// https://github.com/VioletGiraffe/file-commander/blob/master/file-commander-core/src/shell/cshell.cpp +struct ComInitializer { + ComInitializer() { + const auto result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + // assert_r(SUCCEEDED(result)); + } + + ~ComInitializer() { CoUninitialize(); } +}; + +class CComInterfaceReleaser { + public: + explicit CComInterfaceReleaser(IUnknown* i) : _i(i) {} + ~CComInterfaceReleaser() { + if (_i) _i->Release(); + } + + private: + IUnknown* _i; +}; + +class CItemIdArrayReleaser { + public: + explicit CItemIdArrayReleaser(const std::vector& idArray) + : _array(idArray) {} + ~CItemIdArrayReleaser() { + for (ITEMIDLIST* item : _array) CoTaskMemFree(item); + } + + CItemIdArrayReleaser& operator=(const CItemIdArrayReleaser&) = delete; + + private: + const std::vector& _array; +}; + +bool prepareContextMenuForObjects(std::vector objects, + void* parentWindow, HMENU& hmenu, + IContextMenu*& imenu) { + // CO_INIT_HELPER(COINIT_APARTMENTTHREADED); + + if (objects.empty()) return false; + + std::vector ids; + std::vector relativeIds; + IShellFolder* ifolder = 0; + for (size_t i = 0, nItems = objects.size(); i < nItems; ++i) { + auto& item = objects[i]; + std::replace(item.begin(), item.end(), '/', '\\'); + // item.pop_back(); // TODO: ??? + ids.emplace_back(nullptr); + HRESULT result = + SHParseDisplayName(item.c_str(), nullptr, &ids.back(), 0, + nullptr); // TODO: avoid c_str() somehow? + if (!SUCCEEDED(result) || !ids.back()) { + ids.pop_back(); + continue; } - cm.SetObjects(paths); - cm.ShowContextMenu(emacs, pt, showExtraHead != 0); + relativeIds.emplace_back(nullptr); + result = SHBindToParent(ids.back(), IID_IShellFolder, (void**)&ifolder, + &relativeIds.back()); + if (!SUCCEEDED(result) || !relativeIds.back()) + relativeIds.pop_back(); + else if (i < nItems - 1 && ifolder) { + ifolder->Release(); + ifolder = nullptr; + } } + + CItemIdArrayReleaser arrayReleaser(ids); + + if (!parentWindow) return false; + if (!ifolder) return false; + if (relativeIds.empty()) return false; + + // assert_r(parentWindow); + // assert_and_return_message_r(ifolder, "Error getting ifolder", false); + // assert_and_return_message_r(!relativeIds.empty(), "RelativeIds is empty", + // false); + + imenu = 0; + HRESULT result = + ifolder->GetUIObjectOf((HWND)parentWindow, (UINT)relativeIds.size(), + (const ITEMIDLIST**)relativeIds.data(), + IID_IContextMenu, 0, (void**)&imenu); + if (!SUCCEEDED(result) || !imenu) return false; + + hmenu = CreatePopupMenu(); + if (!hmenu) return false; + return ( + SUCCEEDED(imenu->QueryContextMenu(hmenu, 0, 1, 0x7FFF, CMF_NORMAL))); +} + +bool copyObjectsToClipboard(const std::vector& objects, + void* parentWindow) { + // CO_INIT_HELPER(COINIT_APARTMENTTHREADED); + ComInitializer ci; + + IContextMenu* imenu = 0; + HMENU hMenu = NULL; + if (!prepareContextMenuForObjects(objects, parentWindow, hMenu, imenu) || + !hMenu || !imenu) + return false; + + CComInterfaceReleaser menuReleaser(imenu); + + const char command[] = "Copy"; + + CMINVOKECOMMANDINFO info = {0}; + info.cbSize = sizeof(info); + info.hwnd = (HWND)parentWindow; + info.lpVerb = command; + info.nShow = SW_SHOWNORMAL; + const auto result = imenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info); + + DestroyMenu(hMenu); + + return SUCCEEDED(result); +} + +bool cutObjectsToClipboard(const std::vector& objects, + void* parentWindow) { + // CO_INIT_HELPER(COINIT_APARTMENTTHREADED); + ComInitializer ci; + + IContextMenu* imenu = 0; + HMENU hMenu = NULL; + if (!prepareContextMenuForObjects(objects, parentWindow, hMenu, imenu) || + !hMenu || !imenu) + return false; + + CComInterfaceReleaser menuReleaser(imenu); + + const char command[] = "Cut"; + + CMINVOKECOMMANDINFO info = {0}; + info.cbSize = sizeof(info); + info.hwnd = (HWND)parentWindow; + info.lpVerb = command; + info.nShow = SW_SHOWNORMAL; + const auto result = imenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info); + + DestroyMenu(hMenu); + + return SUCCEEDED(result); +} + +bool pasteFilesAndFoldersFromClipboard(std::wstring destFolder, + void* parentWindow) { + // CO_INIT_HELPER(COINIT_APARTMENTTHREADED); + ComInitializer ci; + + IContextMenu* imenu = 0; + HMENU hMenu = NULL; + if (!prepareContextMenuForObjects(std::vector(1, destFolder), + parentWindow, hMenu, imenu) || + !hMenu || !imenu) + return false; + + CComInterfaceReleaser menuReleaser(imenu); + + const char command[] = "Paste"; + + CMINVOKECOMMANDINFO info = {0}; + info.cbSize = sizeof(info); + info.hwnd = (HWND)parentWindow; + info.lpVerb = command; + info.nShow = SW_SHOWNORMAL; + const auto result = imenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info); + + DestroyMenu(hMenu); + + return SUCCEEDED(result); }