fish-dock-plugin-filemgr 0.0.0-stage → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,260 @@
1
+ # Windows 原生文件夹/文件选择对话框的 C# 实现(IFileOpenDialog)。
2
+ # 由 pick-folder.ps1 / pick-files.ps1 通过 Add-Type -TypeDefinition 编译,避免依赖 .cs 文件。
3
+ $FolderPickerSource = @'
4
+ // Windows 原生"选择文件夹"对话框(IFileOpenDialog + FOS_PICKFOLDERS)。
5
+ // 与 FISH Dock 宿主 paper.rs:588 用的是同一个 COM 接口,所以外观和系统资源管理器
6
+ // 的"选择文件夹"完全一致,而不是 WinForms 那棵 1995 年的老树。
7
+ using System;
8
+ using System.Runtime.InteropServices;
9
+
10
+ namespace FishDockPick
11
+ {
12
+ [ComImport, Guid("42f85136-db7e-439c-85f1-e4075d135fc8"),
13
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
14
+ internal interface IFileDialog
15
+ {
16
+ [PreserveSig] int Show(IntPtr parent);
17
+ void SetFileTypes(uint c, IntPtr rg);
18
+ void SetFileTypeIndex(uint i);
19
+ void GetFileTypeIndex(out uint i);
20
+ void Advise(IntPtr e, out uint c);
21
+ void Unadvise(uint c);
22
+ void SetOptions(uint o);
23
+ void GetOptions(out uint o);
24
+ void SetDefaultFolder(IShellItem si);
25
+ void SetFolder(IShellItem si);
26
+ void GetFolder(out IShellItem si);
27
+ void GetCurrentSelection(out IShellItem si);
28
+ void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string name);
29
+ void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string name);
30
+ void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string title);
31
+ void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string text);
32
+ void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string label);
33
+ void GetResult(out IShellItem si);
34
+ void AddPlace(IShellItem si, int fdap);
35
+ void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string ext);
36
+ void Close(int hr);
37
+ void SetClientGuid(ref Guid guid);
38
+ void ClearClientData();
39
+ void SetFilter(IntPtr filter);
40
+ }
41
+
42
+ [ComImport, Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"),
43
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
44
+ internal interface IShellItem
45
+ {
46
+ void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
47
+ void GetParent(out IShellItem parent);
48
+ void GetDisplayName(uint sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string name);
49
+ void GetAttributes(uint sfgaoMask, out uint sfgaoAttribs);
50
+ void Compare(IShellItem psi, uint hint, out int order);
51
+ }
52
+
53
+ [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
54
+ internal class FileOpenDialog { }
55
+
56
+ // IFileOpenDialog — 多选文件需要这个接口(继承 IFileDialog,多了 GetResults)。
57
+ [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"),
58
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
59
+ internal interface IFileOpenDialog
60
+ {
61
+ [PreserveSig] int Show(IntPtr parent);
62
+ void SetFileTypes(uint c, IntPtr rg);
63
+ void SetFileTypeIndex(uint i);
64
+ void GetFileTypeIndex(out uint i);
65
+ void Advise(IntPtr e, out uint c);
66
+ void Unadvise(uint c);
67
+ void SetOptions(uint o);
68
+ void GetOptions(out uint o);
69
+ void SetDefaultFolder(IShellItem si);
70
+ void SetFolder(IShellItem si);
71
+ void GetFolder(out IShellItem si);
72
+ void GetCurrentSelection(out IShellItem si);
73
+ void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string name);
74
+ void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string name);
75
+ void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string title);
76
+ void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string text);
77
+ void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string label);
78
+ void GetResult(out IShellItem si);
79
+ void AddPlace(IShellItem si, int fdap);
80
+ void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string ext);
81
+ void Close(int hr);
82
+ void SetClientGuid(ref Guid guid);
83
+ void ClearClientData();
84
+ void SetFilter(IntPtr filter);
85
+ void GetResults(out IShellItemArray ppenum);
86
+ void GetSelectedItems(out IShellItemArray ppsai);
87
+ }
88
+
89
+ [ComImport, Guid("b63ea76d-1f85-456f-a19c-48159efa858b"),
90
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
91
+ internal interface IShellItemArray
92
+ {
93
+ void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
94
+ void GetPropertyStore(int flags, ref Guid riid, out IntPtr ppv);
95
+ void GetPropertyDescriptionList(IntPtr keyType, ref Guid riid, out IntPtr ppv);
96
+ void GetAttributes(uint AttribFlags, uint sfgaoMask, out uint psfgaoAttribs);
97
+ void GetCount(out uint pdwNumItems);
98
+ void GetItemAt(uint dwIndex, out IShellItem ppsi);
99
+ void EnumItems(out IntPtr ppenumShellItems);
100
+ }
101
+
102
+ public static class FolderPicker
103
+ {
104
+ private const uint FOS_PICKFOLDERS = 0x20;
105
+ private const uint FOS_FORCEFILESYSTEM = 0x40;
106
+ private const uint FOS_PATHMUSTEXIST = 0x800;
107
+ private const uint FOS_ALLOWMULTISELECT = 0x200;
108
+ private const uint SIGDN_FILESYSPATH = 0x80058000;
109
+ private const int ERROR_CANCELLED = unchecked((int)0x800704C7);
110
+
111
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
112
+ private static extern void SHCreateItemFromParsingName(
113
+ [MarshalAs(UnmanagedType.LPWStr)] string pszPath,
114
+ IntPtr pbc,
115
+ ref Guid riid,
116
+ [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv);
117
+
118
+ [DllImport("ole32.dll")]
119
+ private static extern int CoInitializeEx(IntPtr pvReserved, uint dwCoInit);
120
+
121
+ [DllImport("ole32.dll")]
122
+ private static extern void CoUninitialize();
123
+
124
+ [DllImport("user32.dll")]
125
+ private static extern IntPtr GetForegroundWindow();
126
+
127
+ [DllImport("user32.dll")]
128
+ private static extern bool SetForegroundWindow(IntPtr hWnd);
129
+
130
+ [DllImport("user32.dll")]
131
+ private static extern bool AllowSetForegroundWindow(int dwProcessId);
132
+
133
+ // 只初始化 COM 和创建对话框对象,不弹窗——供自检用。
134
+ public static string Selftest()
135
+ {
136
+ int hr = CoInitializeEx(IntPtr.Zero, 2 /* APARTMENTTHREADED */);
137
+ try
138
+ {
139
+ IFileDialog dlg = (IFileDialog)new FileOpenDialog();
140
+ uint opts;
141
+ dlg.GetOptions(out opts);
142
+ dlg.SetOptions(opts | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
143
+ dlg.SetTitle("选择文件夹");
144
+ return "COM_OK hr=0x" + hr.ToString("X8");
145
+ }
146
+ finally
147
+ {
148
+ if (hr >= 0) CoUninitialize();
149
+ }
150
+ }
151
+
152
+ public static string Pick(string initial, string title)
153
+ {
154
+ int hr = CoInitializeEx(IntPtr.Zero, 2 /* APARTMENTTHREADED */);
155
+ try
156
+ {
157
+ IFileDialog dlg = (IFileDialog)new FileOpenDialog();
158
+ uint opts;
159
+ dlg.GetOptions(out opts);
160
+ dlg.SetOptions(opts | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
161
+
162
+ if (!string.IsNullOrEmpty(title)) dlg.SetTitle(title);
163
+
164
+ if (!string.IsNullOrEmpty(initial))
165
+ {
166
+ try
167
+ {
168
+ Guid iid = new Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe");
169
+ IShellItem folder;
170
+ SHCreateItemFromParsingName(initial, IntPtr.Zero, ref iid, out folder);
171
+ if (folder != null) dlg.SetFolder(folder);
172
+ }
173
+ catch { /* 起始目录不存在就退回系统默认 */ }
174
+ }
175
+
176
+ // 关键:本进程是被宿主当子进程拉起来的、且宿主页面还是隐藏窗口,
177
+ // 系统默认会把对话框排在后面,用户看到的是"点了没反应"。
178
+ // 先让操作系统允许本进程抢前台,再 Show 时把父窗口设为 NULL,
179
+ // 弹出来之后主动 SetForegroundWindow,保证一定浮在最上面。
180
+ AllowSetForegroundWindow(-1 /* ASFW_ANY */);
181
+
182
+ int show = dlg.Show(IntPtr.Zero);
183
+ if (show == ERROR_CANCELLED || show != 0) return null;
184
+
185
+ IShellItem item;
186
+ dlg.GetResult(out item);
187
+ if (item == null) return null;
188
+
189
+ string path;
190
+ item.GetDisplayName(SIGDN_FILESYSPATH, out path);
191
+ return path;
192
+ }
193
+ finally
194
+ {
195
+ if (hr >= 0) CoUninitialize();
196
+ }
197
+ }
198
+
199
+ /// <summary>
200
+ /// 弹出多选文件对话框(无 FOS_PICKFOLDERS,加 FOS_ALLOWMULTISELECT)。
201
+ /// 返回选中的路径数组,取消返回空数组。
202
+ /// </summary>
203
+ public static string[] PickFiles(string initial, string title)
204
+ {
205
+ int hr = CoInitializeEx(IntPtr.Zero, 2 /* APARTMENTTHREADED */);
206
+ try
207
+ {
208
+ var dlg = (IFileOpenDialog)new FileOpenDialog();
209
+ uint opts;
210
+ dlg.GetOptions(out opts);
211
+ dlg.SetOptions(opts | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_ALLOWMULTISELECT);
212
+
213
+ if (!string.IsNullOrEmpty(title)) dlg.SetTitle(title);
214
+
215
+ if (!string.IsNullOrEmpty(initial))
216
+ {
217
+ try
218
+ {
219
+ Guid iid = new Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe");
220
+ IShellItem folder;
221
+ SHCreateItemFromParsingName(initial, IntPtr.Zero, ref iid, out folder);
222
+ if (folder != null) ((IFileDialog)dlg).SetFolder(folder);
223
+ }
224
+ catch { /* 起始目录不存在就退回系统默认 */ }
225
+ }
226
+
227
+ AllowSetForegroundWindow(-1 /* ASFW_ANY */);
228
+
229
+ int show = ((IFileDialog)dlg).Show(IntPtr.Zero);
230
+ if (show == ERROR_CANCELLED || show != 0) return new string[0];
231
+
232
+ IShellItemArray results;
233
+ dlg.GetResults(out results);
234
+ if (results == null) return new string[0];
235
+
236
+ uint count;
237
+ results.GetCount(out count);
238
+ var paths = new string[count];
239
+ for (uint i = 0; i < count; i++)
240
+ {
241
+ IShellItem item;
242
+ results.GetItemAt(i, out item);
243
+ if (item != null)
244
+ {
245
+ string path;
246
+ item.GetDisplayName(SIGDN_FILESYSPATH, out path);
247
+ paths[i] = path;
248
+ }
249
+ }
250
+ return paths;
251
+ }
252
+ finally
253
+ {
254
+ if (hr >= 0) CoUninitialize();
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ '@