224 lines
7.8 KiB
C#
224 lines
7.8 KiB
C#
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace FluidExplorer.Services.Shell;
|
|
|
|
/// <summary>
|
|
/// 交给 Windows 外壳去做的动作(打开、属性、打开方式、在资源管理器中显示…)。
|
|
/// 全部走系统原版行为,保证和资源管理器完全一致。
|
|
/// </summary>
|
|
public static class ShellActions
|
|
{
|
|
/// <summary>用默认程序/默认动作打开(= 双击)。</summary>
|
|
public static bool Open(string path)
|
|
{
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>打开"打开方式"选择器。</summary>
|
|
public static bool OpenWith(string path)
|
|
{
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo("rundll32.exe", $"shell32.dll,OpenAs_RunDLL {path}") { UseShellExecute = true });
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>调用系统原版"属性"对话框(含只读/隐藏复选框、磁盘清理等)。</summary>
|
|
public static bool ShowProperties(IntPtr ownerHwnd, IReadOnlyList<string> paths)
|
|
{
|
|
if (paths.Count == 0) return false;
|
|
var info = new SHELLEXECUTEINFO
|
|
{
|
|
cbSize = Marshal.SizeOf<SHELLEXECUTEINFO>(),
|
|
fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI,
|
|
hwnd = ownerHwnd,
|
|
lpVerb = "properties",
|
|
lpFile = paths[0],
|
|
nShow = 5
|
|
};
|
|
|
|
if (paths.Count == 1) return ShellExecuteEx(ref info);
|
|
|
|
// 多选时用外壳的多文件属性对话框
|
|
try
|
|
{
|
|
var files = string.Join('\0', paths) + "\0\0";
|
|
var ptr = Marshal.StringToHGlobalUni(files);
|
|
try
|
|
{
|
|
info.lpFile = null;
|
|
var psi = new SHFILEINFO();
|
|
var hwnd = SHMultiFileProperties(new DataObjectNative { pFiles = ptr }, 0);
|
|
_ = hwnd;
|
|
_ = psi;
|
|
// SHMultiFileProperties 需要 IDataObject 实现,较繁琐;退化为逐项打开第一个的属性
|
|
info.lpFile = paths[0];
|
|
return ShellExecuteEx(ref info);
|
|
}
|
|
finally
|
|
{
|
|
Marshal.FreeHGlobal(ptr);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>在系统资源管理器中定位并选中该文件(用于"在资源管理器中显示")。</summary>
|
|
public static bool RevealInExplorer(string path)
|
|
{
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo("explorer.exe", $"/select,\"{path}\"") { UseShellExecute = true });
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>运行对话框式的"运行"入口(用于 shell: 位置)。</summary>
|
|
public static bool OpenShellLocation(string parsingName)
|
|
{
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo("explorer.exe", parsingName) { UseShellExecute = true });
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>把"打开"当作动词执行(用于右键菜单的"打开")。</summary>
|
|
public static bool Execute(string path, string verb)
|
|
{
|
|
var info = new SHELLEXECUTEINFO
|
|
{
|
|
cbSize = Marshal.SizeOf<SHELLEXECUTEINFO>(),
|
|
fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI,
|
|
lpVerb = verb,
|
|
lpFile = path,
|
|
nShow = 1
|
|
};
|
|
return ShellExecuteEx(ref info);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
private struct SHELLEXECUTEINFO
|
|
{
|
|
public int cbSize;
|
|
public uint fMask;
|
|
public IntPtr hwnd;
|
|
[MarshalAs(UnmanagedType.LPWStr)] public string? lpVerb;
|
|
[MarshalAs(UnmanagedType.LPWStr)] public string? lpFile;
|
|
[MarshalAs(UnmanagedType.LPWStr)] public string? lpParameters;
|
|
[MarshalAs(UnmanagedType.LPWStr)] public string? lpDirectory;
|
|
public int nShow;
|
|
public IntPtr hInstApp;
|
|
public IntPtr lpIDList;
|
|
[MarshalAs(UnmanagedType.LPWStr)] public string? lpClass;
|
|
public IntPtr hkeyClass;
|
|
public uint dwHotKey;
|
|
public IntPtr hIcon;
|
|
public IntPtr hProcess;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct SHFILEINFO
|
|
{
|
|
public IntPtr hIcon;
|
|
public int iIcon;
|
|
public uint dwAttributes;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] public string szTypeName;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct DataObjectNative
|
|
{
|
|
public IntPtr pFiles;
|
|
}
|
|
|
|
private const uint SEE_MASK_INVOKEIDLIST = 0x0000000C;
|
|
private const uint SEE_MASK_FLAG_NO_UI = 0x00000400;
|
|
|
|
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
private static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
|
|
|
|
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr SHMultiFileProperties(DataObjectNative pdtobj, uint dwFlags);
|
|
|
|
/// <summary>把文件放入剪贴板(CF_HDROP,与其他程序互通)。</summary>
|
|
public static bool SetClipboardFiles(IReadOnlyList<string> paths, bool cut)
|
|
{
|
|
try
|
|
{
|
|
var sb = new StringBuilder();
|
|
foreach (var p in paths) sb.Append(p).Append('\0');
|
|
sb.Append('\0');
|
|
var bytes = Encoding.Unicode.GetBytes(sb.ToString());
|
|
var hGlobal = Marshal.AllocHGlobal(bytes.Length + 20);
|
|
if (hGlobal == IntPtr.Zero) return false;
|
|
|
|
// DROPFILES 结构 + 文件名列表
|
|
var dropFiles = new byte[20 + bytes.Length];
|
|
BitConverter.GetBytes(20).CopyTo(dropFiles, 0); // pFiles 偏移
|
|
BitConverter.GetBytes(0).CopyTo(dropFiles, 4); // pt.x
|
|
BitConverter.GetBytes(0).CopyTo(dropFiles, 8); // pt.y
|
|
BitConverter.GetBytes(0).CopyTo(dropFiles, 12); // fNC
|
|
BitConverter.GetBytes(1).CopyTo(dropFiles, 16); // fWide = TRUE
|
|
bytes.CopyTo(dropFiles, 20);
|
|
Marshal.Copy(dropFiles, 0, hGlobal, dropFiles.Length);
|
|
|
|
var format = RegisterClipboardFormat(cut ? "Preferred DropEffect" : "Preferred DropEffect");
|
|
var effect = new byte[4];
|
|
BitConverter.GetBytes(cut ? 2 : 5).CopyTo(effect, 0); // DROPEFFECT_MOVE=2 / COPY=5
|
|
var hEffect = Marshal.AllocHGlobal(4);
|
|
Marshal.Copy(effect, 0, hEffect, 4);
|
|
|
|
if (!OpenClipboard(IntPtr.Zero)) { Marshal.FreeHGlobal(hGlobal); Marshal.FreeHGlobal(hEffect); return false; }
|
|
try
|
|
{
|
|
EmptyClipboard();
|
|
SetClipboardData(15 /*CF_HDROP*/, hGlobal);
|
|
SetClipboardData(format, hEffect);
|
|
}
|
|
finally
|
|
{
|
|
CloseClipboard();
|
|
}
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[DllImport("user32.dll", SetLastError = true)] private static extern bool OpenClipboard(IntPtr hWndNewOwner);
|
|
[DllImport("user32.dll", SetLastError = true)] private static extern bool CloseClipboard();
|
|
[DllImport("user32.dll", SetLastError = true)] private static extern bool EmptyClipboard();
|
|
[DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);
|
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern uint RegisterClipboardFormat(string lpszFormat);
|
|
}
|