dsh-workspace-explorer 1.1.1 → 1.1.2
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.
- package/CHANGELOG.md +7 -0
- package/README.md +6 -6
- package/docs/screenshots/grid-favorites.png +0 -0
- package/docs/screenshots/stl-preview.png +0 -0
- package/docs/screenshots/workspace-search.png +0 -0
- package/lib/client.js +64 -27
- package/package.json +1 -1
- package/src/client/conversation-drop.js +6 -13
- package/src/client/main.js +57 -14
- package/src/client/sidebar-motion.js +69 -69
- package/src/document-types.js +2 -2
- package/src/host/ExplorerShell.cs +183 -183
- package/src/host/files.js +24 -5
- package/src/host/pdf-assets.js +30 -30
- package/src/host/shell-files.js +81 -81
- package/src/thumbnail-types.js +18 -18
- package/src/ui/composer-drop.js +119 -119
- package/src/ui/favorites.js +176 -176
- package/src/ui/file-menu.js +125 -125
- package/src/ui/horizontal-wheel.js +11 -11
- package/src/ui/pdf-preview.js +135 -135
- package/src/ui/preview-unavailable.js +6 -6
- package/src/ui/preview-zoom.js +47 -47
- package/src/ui/thumbnail.js +105 -105
- package/src/vendor/ui/extra-views.js +1 -1
|
@@ -1,184 +1,184 @@
|
|
|
1
|
-
using System;
|
|
2
|
-
using System.Runtime.InteropServices;
|
|
3
|
-
|
|
4
|
-
public static class ExplorerShell {
|
|
5
|
-
[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
|
|
6
|
-
private static extern IShellItem SHCreateItemFromParsingName(string path, IntPtr binding, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
|
|
7
|
-
|
|
8
|
-
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
9
|
-
private static extern bool MoveFileEx(string source, string destination, uint flags);
|
|
10
|
-
|
|
11
|
-
[DllImport("user32.dll")]
|
|
12
|
-
public static extern uint GetClipboardSequenceNumber();
|
|
13
|
-
|
|
14
|
-
[DllImport("user32.dll")]
|
|
15
|
-
private static extern bool OpenClipboard(IntPtr window);
|
|
16
|
-
|
|
17
|
-
[DllImport("user32.dll")]
|
|
18
|
-
private static extern bool CloseClipboard();
|
|
19
|
-
|
|
20
|
-
[DllImport("user32.dll")]
|
|
21
|
-
private static extern bool EmptyClipboard();
|
|
22
|
-
|
|
23
|
-
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
24
|
-
private static extern uint RegisterClipboardFormat(string format);
|
|
25
|
-
|
|
26
|
-
[DllImport("user32.dll")]
|
|
27
|
-
private static extern IntPtr GetClipboardData(uint format);
|
|
28
|
-
|
|
29
|
-
[DllImport("kernel32.dll")]
|
|
30
|
-
private static extern IntPtr GlobalLock(IntPtr memory);
|
|
31
|
-
|
|
32
|
-
[DllImport("kernel32.dll")]
|
|
33
|
-
private static extern bool GlobalUnlock(IntPtr memory);
|
|
34
|
-
|
|
35
|
-
[DllImport("kernel32.dll")]
|
|
36
|
-
private static extern UIntPtr GlobalSize(IntPtr memory);
|
|
37
|
-
|
|
38
|
-
public static bool ClipboardIsCut() {
|
|
39
|
-
if (!OpenClipboard(IntPtr.Zero)) throw new InvalidOperationException("Clipboard unavailable");
|
|
40
|
-
try {
|
|
41
|
-
IntPtr memory = GetClipboardData(RegisterClipboardFormat("Preferred DropEffect"));
|
|
42
|
-
if (memory == IntPtr.Zero || GlobalSize(memory).ToUInt64() < 4) return false;
|
|
43
|
-
IntPtr data = GlobalLock(memory);
|
|
44
|
-
if (data == IntPtr.Zero) return false;
|
|
45
|
-
try { return (Marshal.ReadInt32(data) & 2) != 0; }
|
|
46
|
-
finally { GlobalUnlock(memory); }
|
|
47
|
-
} finally { CloseClipboard(); }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public static int Rename(string source, string destination) {
|
|
51
|
-
return MoveFileEx(source, destination, 0) ? 0 : Marshal.GetLastWin32Error();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public static void ClearClipboardIfUnchanged(uint sequence) {
|
|
55
|
-
if (!OpenClipboard(IntPtr.Zero)) return;
|
|
56
|
-
try {
|
|
57
|
-
if (sequence == GetClipboardSequenceNumber()) EmptyClipboard();
|
|
58
|
-
} finally { CloseClipboard(); }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public static bool Transfer(string source, string destination, bool cut) {
|
|
62
|
-
IFileOperation operation = (IFileOperation)new FileOperation();
|
|
63
|
-
try {
|
|
64
|
-
const uint silent = 0x4, keepBoth = 0x8, noConfirmation = 0x10;
|
|
65
|
-
const uint noErrorUI = 0x400, onlySelected = 0x2000, earlyFailure = 0x100000;
|
|
66
|
-
const uint keepExtension = 0x200000, noCopyHooks = 0x800000, undoRecord = 0x20000000;
|
|
67
|
-
operation.SetOperationFlags(silent | keepBoth | noConfirmation | noErrorUI | onlySelected
|
|
68
|
-
| earlyFailure | keepExtension | noCopyHooks | undoRecord);
|
|
69
|
-
Guid iid = typeof(IShellItem).GUID;
|
|
70
|
-
IShellItem item = SHCreateItemFromParsingName(source, IntPtr.Zero, iid);
|
|
71
|
-
try {
|
|
72
|
-
IShellItem folder = SHCreateItemFromParsingName(destination, IntPtr.Zero, iid);
|
|
73
|
-
try {
|
|
74
|
-
if (cut) operation.MoveItem(item, folder, null, IntPtr.Zero);
|
|
75
|
-
else operation.CopyItem(item, folder, null, IntPtr.Zero);
|
|
76
|
-
operation.PerformOperations();
|
|
77
|
-
return !operation.GetAnyOperationsAborted();
|
|
78
|
-
} finally { Marshal.ReleaseComObject(folder); }
|
|
79
|
-
} finally { Marshal.ReleaseComObject(item); }
|
|
80
|
-
} finally { Marshal.ReleaseComObject(operation); }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
public static bool Recycle(string source) {
|
|
84
|
-
IFileOperation operation = (IFileOperation)new FileOperation();
|
|
85
|
-
try {
|
|
86
|
-
const uint silent = 0x4, noConfirmation = 0x10, allowUndo = 0x40, noErrorUI = 0x400;
|
|
87
|
-
const uint onlySelected = 0x2000, warnPermanent = 0x4000, recycle = 0x80000;
|
|
88
|
-
const uint earlyFailure = 0x100000, noCopyHooks = 0x800000, undoRecord = 0x20000000;
|
|
89
|
-
operation.SetOperationFlags(silent | noConfirmation | allowUndo | noErrorUI | onlySelected | warnPermanent
|
|
90
|
-
| recycle | earlyFailure | noCopyHooks | undoRecord);
|
|
91
|
-
IShellItem item = SHCreateItemFromParsingName(source, IntPtr.Zero, typeof(IShellItem).GUID);
|
|
92
|
-
try {
|
|
93
|
-
RecycleOnly sink = new RecycleOnly();
|
|
94
|
-
operation.DeleteItem(item, sink);
|
|
95
|
-
operation.PerformOperations();
|
|
96
|
-
return sink.Recycled && !operation.GetAnyOperationsAborted();
|
|
97
|
-
} finally { Marshal.ReleaseComObject(item); }
|
|
98
|
-
} finally { Marshal.ReleaseComObject(operation); }
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
[ComImport, Guid("3AD05575-8857-4850-9277-11B85BDB8E09")]
|
|
102
|
-
private class FileOperation { }
|
|
103
|
-
|
|
104
|
-
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
105
|
-
public interface IShellItem { }
|
|
106
|
-
|
|
107
|
-
// Keep the declaration in the native IFileOperation vtable order.
|
|
108
|
-
[ComImport, Guid("947AAB5F-0A5C-4C13-B4D6-4BF7836FC9F8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
109
|
-
private interface IFileOperation {
|
|
110
|
-
void Advise(IntPtr sink, out uint cookie);
|
|
111
|
-
void Unadvise(uint cookie);
|
|
112
|
-
void SetOperationFlags(uint flags);
|
|
113
|
-
void SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string message);
|
|
114
|
-
void SetProgressDialog(IntPtr dialog);
|
|
115
|
-
void SetProperties(IntPtr properties);
|
|
116
|
-
void SetOwnerWindow(IntPtr window);
|
|
117
|
-
void ApplyPropertiesToItem(IShellItem item);
|
|
118
|
-
void ApplyPropertiesToItems(IntPtr items);
|
|
119
|
-
void RenameItem(IShellItem item, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr sink);
|
|
120
|
-
void RenameItems(IntPtr items, [MarshalAs(UnmanagedType.LPWStr)] string name);
|
|
121
|
-
void MoveItem(IShellItem item, IShellItem destination, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr sink);
|
|
122
|
-
void MoveItems(IntPtr items, IShellItem destination);
|
|
123
|
-
void CopyItem(IShellItem item, IShellItem destination, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr sink);
|
|
124
|
-
void CopyItems(IntPtr items, IShellItem destination);
|
|
125
|
-
void DeleteItem(IShellItem item, IFileOperationProgressSink sink);
|
|
126
|
-
void DeleteItems(IntPtr items);
|
|
127
|
-
void NewItem(IShellItem destination, uint attributes, [MarshalAs(UnmanagedType.LPWStr)] string name,
|
|
128
|
-
[MarshalAs(UnmanagedType.LPWStr)] string template, IntPtr sink);
|
|
129
|
-
void PerformOperations();
|
|
130
|
-
[return: MarshalAs(UnmanagedType.Bool)]
|
|
131
|
-
bool GetAnyOperationsAborted();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
[ComImport, Guid("04B0F1A7-9490-44BC-96E1-4296A31252E2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
135
|
-
public interface IFileOperationProgressSink {
|
|
136
|
-
[PreserveSig] int StartOperations();
|
|
137
|
-
[PreserveSig] int FinishOperations(int result);
|
|
138
|
-
[PreserveSig] int PreRenameItem(uint flags, IShellItem item, IntPtr name);
|
|
139
|
-
[PreserveSig] int PostRenameItem(uint flags, IShellItem item, IntPtr name, int result, IShellItem created);
|
|
140
|
-
[PreserveSig] int PreMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name);
|
|
141
|
-
[PreserveSig] int PostMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created);
|
|
142
|
-
[PreserveSig] int PreCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name);
|
|
143
|
-
[PreserveSig] int PostCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created);
|
|
144
|
-
[PreserveSig] int PreDeleteItem(uint flags, IShellItem item);
|
|
145
|
-
[PreserveSig] int PostDeleteItem(uint flags, IShellItem item, int result, IShellItem created);
|
|
146
|
-
[PreserveSig] int PreNewItem(uint flags, IShellItem folder, IntPtr name);
|
|
147
|
-
[PreserveSig] int PostNewItem(uint flags, IShellItem folder, IntPtr name, IntPtr template, uint attrs, int result, IShellItem created);
|
|
148
|
-
[PreserveSig] int UpdateProgress(uint total, uint completed);
|
|
149
|
-
[PreserveSig] int ResetTimer();
|
|
150
|
-
[PreserveSig] int PauseTimer();
|
|
151
|
-
[PreserveSig] int ResumeTimer();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
[ComVisible(true), ClassInterface(ClassInterfaceType.None)]
|
|
155
|
-
public sealed class RecycleOnly : IFileOperationProgressSink {
|
|
156
|
-
public bool Recycled { get; private set; }
|
|
157
|
-
|
|
158
|
-
public int PreDeleteItem(uint flags, IShellItem item) {
|
|
159
|
-
const uint recycleIfPossible = 0x80;
|
|
160
|
-
const int cancelled = unchecked((int)0x800704C7);
|
|
161
|
-
return (flags & recycleIfPossible) != 0 ? 0 : cancelled;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public int PostDeleteItem(uint flags, IShellItem item, int result, IShellItem created) {
|
|
165
|
-
Recycled = result >= 0 && created != null;
|
|
166
|
-
return 0;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
public int StartOperations() { return 0; }
|
|
170
|
-
public int FinishOperations(int result) { return 0; }
|
|
171
|
-
public int PreRenameItem(uint flags, IShellItem item, IntPtr name) { return 0; }
|
|
172
|
-
public int PostRenameItem(uint flags, IShellItem item, IntPtr name, int result, IShellItem created) { return 0; }
|
|
173
|
-
public int PreMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name) { return 0; }
|
|
174
|
-
public int PostMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created) { return 0; }
|
|
175
|
-
public int PreCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name) { return 0; }
|
|
176
|
-
public int PostCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created) { return 0; }
|
|
177
|
-
public int PreNewItem(uint flags, IShellItem folder, IntPtr name) { return 0; }
|
|
178
|
-
public int PostNewItem(uint flags, IShellItem folder, IntPtr name, IntPtr template, uint attrs, int result, IShellItem created) { return 0; }
|
|
179
|
-
public int UpdateProgress(uint total, uint completed) { return 0; }
|
|
180
|
-
public int ResetTimer() { return 0; }
|
|
181
|
-
public int PauseTimer() { return 0; }
|
|
182
|
-
public int ResumeTimer() { return 0; }
|
|
183
|
-
}
|
|
1
|
+
using System;
|
|
2
|
+
using System.Runtime.InteropServices;
|
|
3
|
+
|
|
4
|
+
public static class ExplorerShell {
|
|
5
|
+
[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
|
|
6
|
+
private static extern IShellItem SHCreateItemFromParsingName(string path, IntPtr binding, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
|
|
7
|
+
|
|
8
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
9
|
+
private static extern bool MoveFileEx(string source, string destination, uint flags);
|
|
10
|
+
|
|
11
|
+
[DllImport("user32.dll")]
|
|
12
|
+
public static extern uint GetClipboardSequenceNumber();
|
|
13
|
+
|
|
14
|
+
[DllImport("user32.dll")]
|
|
15
|
+
private static extern bool OpenClipboard(IntPtr window);
|
|
16
|
+
|
|
17
|
+
[DllImport("user32.dll")]
|
|
18
|
+
private static extern bool CloseClipboard();
|
|
19
|
+
|
|
20
|
+
[DllImport("user32.dll")]
|
|
21
|
+
private static extern bool EmptyClipboard();
|
|
22
|
+
|
|
23
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
24
|
+
private static extern uint RegisterClipboardFormat(string format);
|
|
25
|
+
|
|
26
|
+
[DllImport("user32.dll")]
|
|
27
|
+
private static extern IntPtr GetClipboardData(uint format);
|
|
28
|
+
|
|
29
|
+
[DllImport("kernel32.dll")]
|
|
30
|
+
private static extern IntPtr GlobalLock(IntPtr memory);
|
|
31
|
+
|
|
32
|
+
[DllImport("kernel32.dll")]
|
|
33
|
+
private static extern bool GlobalUnlock(IntPtr memory);
|
|
34
|
+
|
|
35
|
+
[DllImport("kernel32.dll")]
|
|
36
|
+
private static extern UIntPtr GlobalSize(IntPtr memory);
|
|
37
|
+
|
|
38
|
+
public static bool ClipboardIsCut() {
|
|
39
|
+
if (!OpenClipboard(IntPtr.Zero)) throw new InvalidOperationException("Clipboard unavailable");
|
|
40
|
+
try {
|
|
41
|
+
IntPtr memory = GetClipboardData(RegisterClipboardFormat("Preferred DropEffect"));
|
|
42
|
+
if (memory == IntPtr.Zero || GlobalSize(memory).ToUInt64() < 4) return false;
|
|
43
|
+
IntPtr data = GlobalLock(memory);
|
|
44
|
+
if (data == IntPtr.Zero) return false;
|
|
45
|
+
try { return (Marshal.ReadInt32(data) & 2) != 0; }
|
|
46
|
+
finally { GlobalUnlock(memory); }
|
|
47
|
+
} finally { CloseClipboard(); }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static int Rename(string source, string destination) {
|
|
51
|
+
return MoveFileEx(source, destination, 0) ? 0 : Marshal.GetLastWin32Error();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static void ClearClipboardIfUnchanged(uint sequence) {
|
|
55
|
+
if (!OpenClipboard(IntPtr.Zero)) return;
|
|
56
|
+
try {
|
|
57
|
+
if (sequence == GetClipboardSequenceNumber()) EmptyClipboard();
|
|
58
|
+
} finally { CloseClipboard(); }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public static bool Transfer(string source, string destination, bool cut) {
|
|
62
|
+
IFileOperation operation = (IFileOperation)new FileOperation();
|
|
63
|
+
try {
|
|
64
|
+
const uint silent = 0x4, keepBoth = 0x8, noConfirmation = 0x10;
|
|
65
|
+
const uint noErrorUI = 0x400, onlySelected = 0x2000, earlyFailure = 0x100000;
|
|
66
|
+
const uint keepExtension = 0x200000, noCopyHooks = 0x800000, undoRecord = 0x20000000;
|
|
67
|
+
operation.SetOperationFlags(silent | keepBoth | noConfirmation | noErrorUI | onlySelected
|
|
68
|
+
| earlyFailure | keepExtension | noCopyHooks | undoRecord);
|
|
69
|
+
Guid iid = typeof(IShellItem).GUID;
|
|
70
|
+
IShellItem item = SHCreateItemFromParsingName(source, IntPtr.Zero, iid);
|
|
71
|
+
try {
|
|
72
|
+
IShellItem folder = SHCreateItemFromParsingName(destination, IntPtr.Zero, iid);
|
|
73
|
+
try {
|
|
74
|
+
if (cut) operation.MoveItem(item, folder, null, IntPtr.Zero);
|
|
75
|
+
else operation.CopyItem(item, folder, null, IntPtr.Zero);
|
|
76
|
+
operation.PerformOperations();
|
|
77
|
+
return !operation.GetAnyOperationsAborted();
|
|
78
|
+
} finally { Marshal.ReleaseComObject(folder); }
|
|
79
|
+
} finally { Marshal.ReleaseComObject(item); }
|
|
80
|
+
} finally { Marshal.ReleaseComObject(operation); }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public static bool Recycle(string source) {
|
|
84
|
+
IFileOperation operation = (IFileOperation)new FileOperation();
|
|
85
|
+
try {
|
|
86
|
+
const uint silent = 0x4, noConfirmation = 0x10, allowUndo = 0x40, noErrorUI = 0x400;
|
|
87
|
+
const uint onlySelected = 0x2000, warnPermanent = 0x4000, recycle = 0x80000;
|
|
88
|
+
const uint earlyFailure = 0x100000, noCopyHooks = 0x800000, undoRecord = 0x20000000;
|
|
89
|
+
operation.SetOperationFlags(silent | noConfirmation | allowUndo | noErrorUI | onlySelected | warnPermanent
|
|
90
|
+
| recycle | earlyFailure | noCopyHooks | undoRecord);
|
|
91
|
+
IShellItem item = SHCreateItemFromParsingName(source, IntPtr.Zero, typeof(IShellItem).GUID);
|
|
92
|
+
try {
|
|
93
|
+
RecycleOnly sink = new RecycleOnly();
|
|
94
|
+
operation.DeleteItem(item, sink);
|
|
95
|
+
operation.PerformOperations();
|
|
96
|
+
return sink.Recycled && !operation.GetAnyOperationsAborted();
|
|
97
|
+
} finally { Marshal.ReleaseComObject(item); }
|
|
98
|
+
} finally { Marshal.ReleaseComObject(operation); }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[ComImport, Guid("3AD05575-8857-4850-9277-11B85BDB8E09")]
|
|
102
|
+
private class FileOperation { }
|
|
103
|
+
|
|
104
|
+
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
105
|
+
public interface IShellItem { }
|
|
106
|
+
|
|
107
|
+
// Keep the declaration in the native IFileOperation vtable order.
|
|
108
|
+
[ComImport, Guid("947AAB5F-0A5C-4C13-B4D6-4BF7836FC9F8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
109
|
+
private interface IFileOperation {
|
|
110
|
+
void Advise(IntPtr sink, out uint cookie);
|
|
111
|
+
void Unadvise(uint cookie);
|
|
112
|
+
void SetOperationFlags(uint flags);
|
|
113
|
+
void SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string message);
|
|
114
|
+
void SetProgressDialog(IntPtr dialog);
|
|
115
|
+
void SetProperties(IntPtr properties);
|
|
116
|
+
void SetOwnerWindow(IntPtr window);
|
|
117
|
+
void ApplyPropertiesToItem(IShellItem item);
|
|
118
|
+
void ApplyPropertiesToItems(IntPtr items);
|
|
119
|
+
void RenameItem(IShellItem item, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr sink);
|
|
120
|
+
void RenameItems(IntPtr items, [MarshalAs(UnmanagedType.LPWStr)] string name);
|
|
121
|
+
void MoveItem(IShellItem item, IShellItem destination, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr sink);
|
|
122
|
+
void MoveItems(IntPtr items, IShellItem destination);
|
|
123
|
+
void CopyItem(IShellItem item, IShellItem destination, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr sink);
|
|
124
|
+
void CopyItems(IntPtr items, IShellItem destination);
|
|
125
|
+
void DeleteItem(IShellItem item, IFileOperationProgressSink sink);
|
|
126
|
+
void DeleteItems(IntPtr items);
|
|
127
|
+
void NewItem(IShellItem destination, uint attributes, [MarshalAs(UnmanagedType.LPWStr)] string name,
|
|
128
|
+
[MarshalAs(UnmanagedType.LPWStr)] string template, IntPtr sink);
|
|
129
|
+
void PerformOperations();
|
|
130
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
131
|
+
bool GetAnyOperationsAborted();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
[ComImport, Guid("04B0F1A7-9490-44BC-96E1-4296A31252E2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
135
|
+
public interface IFileOperationProgressSink {
|
|
136
|
+
[PreserveSig] int StartOperations();
|
|
137
|
+
[PreserveSig] int FinishOperations(int result);
|
|
138
|
+
[PreserveSig] int PreRenameItem(uint flags, IShellItem item, IntPtr name);
|
|
139
|
+
[PreserveSig] int PostRenameItem(uint flags, IShellItem item, IntPtr name, int result, IShellItem created);
|
|
140
|
+
[PreserveSig] int PreMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name);
|
|
141
|
+
[PreserveSig] int PostMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created);
|
|
142
|
+
[PreserveSig] int PreCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name);
|
|
143
|
+
[PreserveSig] int PostCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created);
|
|
144
|
+
[PreserveSig] int PreDeleteItem(uint flags, IShellItem item);
|
|
145
|
+
[PreserveSig] int PostDeleteItem(uint flags, IShellItem item, int result, IShellItem created);
|
|
146
|
+
[PreserveSig] int PreNewItem(uint flags, IShellItem folder, IntPtr name);
|
|
147
|
+
[PreserveSig] int PostNewItem(uint flags, IShellItem folder, IntPtr name, IntPtr template, uint attrs, int result, IShellItem created);
|
|
148
|
+
[PreserveSig] int UpdateProgress(uint total, uint completed);
|
|
149
|
+
[PreserveSig] int ResetTimer();
|
|
150
|
+
[PreserveSig] int PauseTimer();
|
|
151
|
+
[PreserveSig] int ResumeTimer();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
[ComVisible(true), ClassInterface(ClassInterfaceType.None)]
|
|
155
|
+
public sealed class RecycleOnly : IFileOperationProgressSink {
|
|
156
|
+
public bool Recycled { get; private set; }
|
|
157
|
+
|
|
158
|
+
public int PreDeleteItem(uint flags, IShellItem item) {
|
|
159
|
+
const uint recycleIfPossible = 0x80;
|
|
160
|
+
const int cancelled = unchecked((int)0x800704C7);
|
|
161
|
+
return (flags & recycleIfPossible) != 0 ? 0 : cancelled;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public int PostDeleteItem(uint flags, IShellItem item, int result, IShellItem created) {
|
|
165
|
+
Recycled = result >= 0 && created != null;
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public int StartOperations() { return 0; }
|
|
170
|
+
public int FinishOperations(int result) { return 0; }
|
|
171
|
+
public int PreRenameItem(uint flags, IShellItem item, IntPtr name) { return 0; }
|
|
172
|
+
public int PostRenameItem(uint flags, IShellItem item, IntPtr name, int result, IShellItem created) { return 0; }
|
|
173
|
+
public int PreMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name) { return 0; }
|
|
174
|
+
public int PostMoveItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created) { return 0; }
|
|
175
|
+
public int PreCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name) { return 0; }
|
|
176
|
+
public int PostCopyItem(uint flags, IShellItem item, IShellItem folder, IntPtr name, int result, IShellItem created) { return 0; }
|
|
177
|
+
public int PreNewItem(uint flags, IShellItem folder, IntPtr name) { return 0; }
|
|
178
|
+
public int PostNewItem(uint flags, IShellItem folder, IntPtr name, IntPtr template, uint attrs, int result, IShellItem created) { return 0; }
|
|
179
|
+
public int UpdateProgress(uint total, uint completed) { return 0; }
|
|
180
|
+
public int ResetTimer() { return 0; }
|
|
181
|
+
public int PauseTimer() { return 0; }
|
|
182
|
+
public int ResumeTimer() { return 0; }
|
|
183
|
+
}
|
|
184
184
|
}
|
package/src/host/files.js
CHANGED
|
@@ -56,12 +56,30 @@ function fileVersion(info) {
|
|
|
56
56
|
return JSON.stringify([info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs]);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
const pythonEncodingAliases = new Map([
|
|
60
|
+
['utf-8-sig', 'utf-8'], ['cp936', 'gbk'], ['cp932', 'shift_jis'],
|
|
61
|
+
['cp1252', 'windows-1252'], ['latin-1', 'latin1'], ['iso-8859-1', 'latin1'],
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
function pythonSourceEncoding(data) {
|
|
65
|
+
if (data[0] === 0xef && data[1] === 0xbb && data[2] === 0xbf) return 'utf-8';
|
|
66
|
+
const [first, second = ''] = data.toString('latin1').split(/\r\n?|\n/, 2);
|
|
67
|
+
const cookie = /^[ \t\f]*#.*?coding[=:][ \t]*([-_.a-zA-Z0-9]+)/;
|
|
68
|
+
const declared = cookie.exec(first)?.[1]
|
|
69
|
+
?? (/^[ \t\f]*(?:#|$)/.test(first) ? cookie.exec(second)?.[1] : undefined);
|
|
70
|
+
const label = declared?.toLowerCase().replaceAll('_', '-') ?? 'utf-8';
|
|
71
|
+
return pythonEncodingAliases.get(label) ?? label;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function decodePreviewText(data, eof, encoding = 'utf-8') {
|
|
75
|
+
let decoder;
|
|
76
|
+
try { decoder = new TextDecoder(encoding, { fatal: true }); }
|
|
77
|
+
catch { decoder = new TextDecoder('utf-8', { fatal: true }); }
|
|
60
78
|
let text;
|
|
61
79
|
try {
|
|
62
|
-
text =
|
|
80
|
+
text = encoding === 'latin1' ? data.toString('latin1') : decoder.decode(data, { stream: !eof });
|
|
63
81
|
} catch {
|
|
64
|
-
throw new BrowseError('此文件不是 UTF-8 文本');
|
|
82
|
+
throw new BrowseError(decoder.encoding === 'utf-8' ? '此文件不是 UTF-8 文本' : '无法按文件声明的编码预览');
|
|
65
83
|
}
|
|
66
84
|
if (text.includes('\0')) throw new BrowseError('此格式没有可用的文本预览');
|
|
67
85
|
return text;
|
|
@@ -560,8 +578,9 @@ export function registerDesktopFiles(ctx) {
|
|
|
560
578
|
return json({ url: thumbnailEndpoint + '?ticket=' + token });
|
|
561
579
|
}
|
|
562
580
|
if (operation === 'text') {
|
|
563
|
-
const
|
|
564
|
-
|
|
581
|
+
const python = typeof input === 'string' && /\.py$/i.test(input);
|
|
582
|
+
const { data, eof } = await disks.read(input, maxText, signal, workspace, { sniffText: !python });
|
|
583
|
+
return json({ text: decodePreviewText(data, eof, python ? pythonSourceEncoding(data) : undefined), eof });
|
|
565
584
|
}
|
|
566
585
|
if (operation === 'bytes' || operation === 'office') {
|
|
567
586
|
const converter = operation === 'office' ? ctx.officeToPdf : undefined;
|
package/src/host/pdf-assets.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
4
|
-
import { pdfAssetsPath } from '../document-types.js';
|
|
5
|
-
|
|
6
|
-
const require = createRequire(import.meta.url);
|
|
7
|
-
const assetName = /^(?:build\/pdf(?:\.worker)?\.min\.mjs|cmaps\/[\w.-]+\.bcmap|standard_fonts\/[\w.-]+\.(?:pfb|ttf)|wasm\/[\w.-]+\.(?:wasm|js))$/;
|
|
8
|
-
|
|
9
|
-
export function registerPdfAssets(ctx) {
|
|
10
|
-
ctx.connection.fetch.register({
|
|
11
|
-
path: pdfAssetsPath,
|
|
12
|
-
methods: ['GET'],
|
|
13
|
-
requestBody: 'buffered',
|
|
14
|
-
async fetch(request) {
|
|
15
|
-
const file = new URL(request.url).searchParams.get('file');
|
|
16
|
-
if (!file || !assetName.test(file)) return new Response(null, { status: 404 });
|
|
17
|
-
try {
|
|
18
|
-
const root = dirname(require.resolve('pdfjs-dist/package.json'));
|
|
19
|
-
const data = await readFile(join(root, file));
|
|
20
|
-
const contentType = file.endsWith('.wasm') ? 'application/wasm'
|
|
21
|
-
: /\.m?js$/.test(file) ? 'text/javascript' : 'application/octet-stream';
|
|
22
|
-
return new Response(data, { headers: {
|
|
23
|
-
'content-type': contentType, 'x-content-type-options': 'nosniff', 'cache-control': 'no-store',
|
|
24
|
-
} });
|
|
25
|
-
} catch {
|
|
26
|
-
return new Response(null, { status: 404 });
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
}
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { pdfAssetsPath } from '../document-types.js';
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const assetName = /^(?:build\/pdf(?:\.worker)?\.min\.mjs|cmaps\/[\w.-]+\.bcmap|standard_fonts\/[\w.-]+\.(?:pfb|ttf)|wasm\/[\w.-]+\.(?:wasm|js))$/;
|
|
8
|
+
|
|
9
|
+
export function registerPdfAssets(ctx) {
|
|
10
|
+
ctx.connection.fetch.register({
|
|
11
|
+
path: pdfAssetsPath,
|
|
12
|
+
methods: ['GET'],
|
|
13
|
+
requestBody: 'buffered',
|
|
14
|
+
async fetch(request) {
|
|
15
|
+
const file = new URL(request.url).searchParams.get('file');
|
|
16
|
+
if (!file || !assetName.test(file)) return new Response(null, { status: 404 });
|
|
17
|
+
try {
|
|
18
|
+
const root = dirname(require.resolve('pdfjs-dist/package.json'));
|
|
19
|
+
const data = await readFile(join(root, file));
|
|
20
|
+
const contentType = file.endsWith('.wasm') ? 'application/wasm'
|
|
21
|
+
: /\.m?js$/.test(file) ? 'text/javascript' : 'application/octet-stream';
|
|
22
|
+
return new Response(data, { headers: {
|
|
23
|
+
'content-type': contentType, 'x-content-type-options': 'nosniff', 'cache-control': 'no-store',
|
|
24
|
+
} });
|
|
25
|
+
} catch {
|
|
26
|
+
return new Response(null, { status: 404 });
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
package/src/host/shell-files.js
CHANGED
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { win32 as path } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
|
-
|
|
6
|
-
const execute = promisify(execFile);
|
|
7
|
-
const helper = fileURLToPath(new URL('./ExplorerShell.cs', import.meta.url));
|
|
8
|
-
const script = String.raw`
|
|
9
|
-
$ErrorActionPreference = 'Stop'
|
|
10
|
-
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
11
|
-
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
12
|
-
Add-Type -Path $env:DSH_EXPLORER_SHELL
|
|
13
|
-
$request = ConvertFrom-Json ([Console]::In.ReadToEnd())
|
|
14
|
-
if ($request.action -in 'stage', 'clipboard') {
|
|
15
|
-
Add-Type -AssemblyName System.Windows.Forms
|
|
16
|
-
}
|
|
17
|
-
switch ($request.action) {
|
|
18
|
-
'stage' {
|
|
19
|
-
$paths = New-Object System.Collections.Specialized.StringCollection
|
|
20
|
-
foreach ($source in $request.sources) { [void]$paths.Add($source) }
|
|
21
|
-
$data = New-Object System.Windows.Forms.DataObject
|
|
22
|
-
$data.SetFileDropList($paths)
|
|
23
|
-
$effect = if ($request.cut) { 2 } else { 1 }
|
|
24
|
-
$stream = New-Object System.IO.MemoryStream -ArgumentList (,[BitConverter]::GetBytes([int]$effect))
|
|
25
|
-
$data.SetData('Preferred DropEffect', $false, $stream)
|
|
26
|
-
[System.Windows.Forms.Clipboard]::SetDataObject($data, $true, 8, 30)
|
|
27
|
-
@{ ok = $true } | ConvertTo-Json -Compress
|
|
28
|
-
}
|
|
29
|
-
'clipboard' {
|
|
30
|
-
for ($attempt = 0; $attempt -lt 3; $attempt++) {
|
|
31
|
-
try {
|
|
32
|
-
$sequence = [ExplorerShell]::GetClipboardSequenceNumber()
|
|
33
|
-
$paths = @([System.Windows.Forms.Clipboard]::GetFileDropList())
|
|
34
|
-
$cut = if ($paths.Count -gt 0) { [ExplorerShell]::ClipboardIsCut() } else { $false }
|
|
35
|
-
if ($sequence -eq [ExplorerShell]::GetClipboardSequenceNumber()) {
|
|
36
|
-
@{ paths = $paths; cut = $cut; sequence = $sequence } | ConvertTo-Json -Compress
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
} catch {
|
|
40
|
-
if ($attempt -eq 2) { throw }
|
|
41
|
-
}
|
|
42
|
-
Start-Sleep -Milliseconds 40
|
|
43
|
-
}
|
|
44
|
-
throw 'Clipboard changed'
|
|
45
|
-
}
|
|
46
|
-
'clearCut' {
|
|
47
|
-
[ExplorerShell]::ClearClipboardIfUnchanged($request.sequence)
|
|
48
|
-
@{ ok = $true } | ConvertTo-Json -Compress
|
|
49
|
-
}
|
|
50
|
-
'transfer' {
|
|
51
|
-
$results = @(foreach ($source in $request.sources) {
|
|
52
|
-
try { [ExplorerShell]::Transfer($source, $request.destination, $request.cut) }
|
|
53
|
-
catch { $false }
|
|
54
|
-
})
|
|
55
|
-
@{ results = $results } | ConvertTo-Json -Compress
|
|
56
|
-
}
|
|
57
|
-
'rename' {
|
|
58
|
-
@{ code = [ExplorerShell]::Rename($request.source, $request.destination) } | ConvertTo-Json -Compress
|
|
59
|
-
}
|
|
60
|
-
'recycle' {
|
|
61
|
-
$results = @(foreach ($source in $request.sources) {
|
|
62
|
-
try { [ExplorerShell]::Recycle($source) }
|
|
63
|
-
catch { $false }
|
|
64
|
-
})
|
|
65
|
-
@{ results = $results } | ConvertTo-Json -Compress
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
`;
|
|
69
|
-
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
|
70
|
-
|
|
71
|
-
export async function windowsFileOperation(request) {
|
|
72
|
-
const powershell = path.join(process.env.SystemRoot || 'C:\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
73
|
-
const result = execute(powershell, ['-NoProfile', '-NonInteractive', '-STA', '-EncodedCommand', encoded], {
|
|
74
|
-
windowsHide: true, maxBuffer: 256 * 1024,
|
|
75
|
-
...(['transfer', 'recycle'].includes(request.action) ? {} : { timeout: 15000 }),
|
|
76
|
-
env: { ...process.env, DSH_EXPLORER_SHELL: helper },
|
|
77
|
-
});
|
|
78
|
-
result.child.stdin.on('error', () => {}); // The process result reports early exit or spawn failure.
|
|
79
|
-
result.child.stdin.end(JSON.stringify(request), 'utf8');
|
|
80
|
-
const { stdout } = await result;
|
|
81
|
-
return JSON.parse(stdout.trim());
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { win32 as path } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
|
|
6
|
+
const execute = promisify(execFile);
|
|
7
|
+
const helper = fileURLToPath(new URL('./ExplorerShell.cs', import.meta.url));
|
|
8
|
+
const script = String.raw`
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
11
|
+
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
12
|
+
Add-Type -Path $env:DSH_EXPLORER_SHELL
|
|
13
|
+
$request = ConvertFrom-Json ([Console]::In.ReadToEnd())
|
|
14
|
+
if ($request.action -in 'stage', 'clipboard') {
|
|
15
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
16
|
+
}
|
|
17
|
+
switch ($request.action) {
|
|
18
|
+
'stage' {
|
|
19
|
+
$paths = New-Object System.Collections.Specialized.StringCollection
|
|
20
|
+
foreach ($source in $request.sources) { [void]$paths.Add($source) }
|
|
21
|
+
$data = New-Object System.Windows.Forms.DataObject
|
|
22
|
+
$data.SetFileDropList($paths)
|
|
23
|
+
$effect = if ($request.cut) { 2 } else { 1 }
|
|
24
|
+
$stream = New-Object System.IO.MemoryStream -ArgumentList (,[BitConverter]::GetBytes([int]$effect))
|
|
25
|
+
$data.SetData('Preferred DropEffect', $false, $stream)
|
|
26
|
+
[System.Windows.Forms.Clipboard]::SetDataObject($data, $true, 8, 30)
|
|
27
|
+
@{ ok = $true } | ConvertTo-Json -Compress
|
|
28
|
+
}
|
|
29
|
+
'clipboard' {
|
|
30
|
+
for ($attempt = 0; $attempt -lt 3; $attempt++) {
|
|
31
|
+
try {
|
|
32
|
+
$sequence = [ExplorerShell]::GetClipboardSequenceNumber()
|
|
33
|
+
$paths = @([System.Windows.Forms.Clipboard]::GetFileDropList())
|
|
34
|
+
$cut = if ($paths.Count -gt 0) { [ExplorerShell]::ClipboardIsCut() } else { $false }
|
|
35
|
+
if ($sequence -eq [ExplorerShell]::GetClipboardSequenceNumber()) {
|
|
36
|
+
@{ paths = $paths; cut = $cut; sequence = $sequence } | ConvertTo-Json -Compress
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
if ($attempt -eq 2) { throw }
|
|
41
|
+
}
|
|
42
|
+
Start-Sleep -Milliseconds 40
|
|
43
|
+
}
|
|
44
|
+
throw 'Clipboard changed'
|
|
45
|
+
}
|
|
46
|
+
'clearCut' {
|
|
47
|
+
[ExplorerShell]::ClearClipboardIfUnchanged($request.sequence)
|
|
48
|
+
@{ ok = $true } | ConvertTo-Json -Compress
|
|
49
|
+
}
|
|
50
|
+
'transfer' {
|
|
51
|
+
$results = @(foreach ($source in $request.sources) {
|
|
52
|
+
try { [ExplorerShell]::Transfer($source, $request.destination, $request.cut) }
|
|
53
|
+
catch { $false }
|
|
54
|
+
})
|
|
55
|
+
@{ results = $results } | ConvertTo-Json -Compress
|
|
56
|
+
}
|
|
57
|
+
'rename' {
|
|
58
|
+
@{ code = [ExplorerShell]::Rename($request.source, $request.destination) } | ConvertTo-Json -Compress
|
|
59
|
+
}
|
|
60
|
+
'recycle' {
|
|
61
|
+
$results = @(foreach ($source in $request.sources) {
|
|
62
|
+
try { [ExplorerShell]::Recycle($source) }
|
|
63
|
+
catch { $false }
|
|
64
|
+
})
|
|
65
|
+
@{ results = $results } | ConvertTo-Json -Compress
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
|
70
|
+
|
|
71
|
+
export async function windowsFileOperation(request) {
|
|
72
|
+
const powershell = path.join(process.env.SystemRoot || 'C:\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
73
|
+
const result = execute(powershell, ['-NoProfile', '-NonInteractive', '-STA', '-EncodedCommand', encoded], {
|
|
74
|
+
windowsHide: true, maxBuffer: 256 * 1024,
|
|
75
|
+
...(['transfer', 'recycle'].includes(request.action) ? {} : { timeout: 15000 }),
|
|
76
|
+
env: { ...process.env, DSH_EXPLORER_SHELL: helper },
|
|
77
|
+
});
|
|
78
|
+
result.child.stdin.on('error', () => {}); // The process result reports early exit or spawn failure.
|
|
79
|
+
result.child.stdin.end(JSON.stringify(request), 'utf8');
|
|
80
|
+
const { stdout } = await result;
|
|
81
|
+
return JSON.parse(stdout.trim());
|
|
82
82
|
}
|