Skip to content

Commit

Permalink
添加shell的copy/cut/paste功能
Browse files Browse the repository at this point in the history
  • Loading branch information
lynnux committed Nov 18, 2022
1 parent 99368e3 commit 80cdd19
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 20 deletions.
36 changes: 35 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<usize> {
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<usize> {
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<usize> {
crate::shellmenu::shell_pastefiles(path);
Ok(0)
}
55 changes: 53 additions & 2 deletions src/shellmenu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -22,7 +31,12 @@ pub fn shellmenu_init() {
}
}

pub fn pop_shell_menu(paths: Vec<String>, x: i32, y: i32, show_extra_head: i32) -> Result<(), NwgError> {
pub fn pop_shell_menu(
paths: Vec<String>,
x: i32,
y: i32,
show_extra_head: i32,
) -> Result<(), NwgError> {
SHELL_PARENT_WND.with(|wnd| {
if (*wnd.borrow()).is_none() {
nwg::init().ok(); // 必须,会注册ngw的类
Expand Down Expand Up @@ -81,3 +95,40 @@ pub fn pop_shell_menu(paths: Vec<String>, x: i32, y: i32, show_extra_head: i32)
});
Ok(())
}

pub fn shell_copyfiles(paths: Vec<String>) {
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<String>) {
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());
}
}
242 changes: 225 additions & 17 deletions src/shellmenu_wrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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的代码,shell的copy/cut/paste代码来自https://github.com/VioletGiraffe/file-commander
#include "ShellContextMenu.h"
#include <algorithm>

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<CSearchInfo> paths;
while(*path){
CSearchInfo cs;
cs.filePath = *path;
paths.push_back(cs);
++path;
bool copyObjectsToClipboard(const std::vector<std::wstring>& objects,
void* parentWindow);
bool cutObjectsToClipboard(const std::vector<std::wstring>& 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<CSearchInfo> 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<std::wstring> paths;
while (*path) {
paths.push_back(*path);
++path;
}
copyObjectsToClipboard(paths, GetForegroundWindow());
}

void CutPathsToClipboard(const wchar_t** path) {
std::vector<std::wstring> 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<ITEMIDLIST*>& idArray)
: _array(idArray) {}
~CItemIdArrayReleaser() {
for (ITEMIDLIST* item : _array) CoTaskMemFree(item);
}

CItemIdArrayReleaser& operator=(const CItemIdArrayReleaser&) = delete;

private:
const std::vector<ITEMIDLIST*>& _array;
};

bool prepareContextMenuForObjects(std::vector<std::wstring> objects,
void* parentWindow, HMENU& hmenu,
IContextMenu*& imenu) {
// CO_INIT_HELPER(COINIT_APARTMENTTHREADED);

if (objects.empty()) return false;

std::vector<ITEMIDLIST*> ids;
std::vector<LPCITEMIDLIST> 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<std::wstring>& 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<std::wstring>& 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<std::wstring>(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);
}

0 comments on commit 80cdd19

Please sign in to comment.