using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace FluidExplorer.Services.Shell;
///
/// 交给 Windows 外壳去做的动作(打开、属性、打开方式、在资源管理器中显示…)。
/// 全部走系统原版行为,保证和资源管理器完全一致。
///
public static class ShellActions
{
/// 用默认程序/默认动作打开(= 双击)。
public static bool Open(string path)
{
try
{
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
return true;
}
catch
{
return false;
}
}
/// 打开"打开方式"选择器。
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;
}
}
/// 调用系统原版"属性"对话框(含只读/隐藏复选框、磁盘清理等)。
public static bool ShowProperties(IntPtr ownerHwnd, IReadOnlyList paths)
{
if (paths.Count == 0) return false;
var info = new SHELLEXECUTEINFO
{
cbSize = Marshal.SizeOf(),
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;
}
}
/// 在系统资源管理器中定位并选中该文件(用于"在资源管理器中显示")。
public static bool RevealInExplorer(string path)
{
try
{
Process.Start(new ProcessStartInfo("explorer.exe", $"/select,\"{path}\"") { UseShellExecute = true });
return true;
}
catch
{
return false;
}
}
/// 运行对话框式的"运行"入口(用于 shell: 位置)。
public static bool OpenShellLocation(string parsingName)
{
try
{
Process.Start(new ProcessStartInfo("explorer.exe", parsingName) { UseShellExecute = true });
return true;
}
catch
{
return false;
}
}
/// 把"打开"当作动词执行(用于右键菜单的"打开")。
public static bool Execute(string path, string verb)
{
var info = new SHELLEXECUTEINFO
{
cbSize = Marshal.SizeOf(),
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);
/// 把文件放入剪贴板(CF_HDROP,与其他程序互通)。
public static bool SetClipboardFiles(IReadOnlyList 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);
}