dsh-mini 0.0.0-stage → 0.1.1
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/README.md +230 -2
- package/app/HudWindow.cs +1278 -0
- package/app/RegionCapture.cs +552 -0
- package/app/SelectionWatcher.cs +850 -0
- package/app/Service.cs +412 -0
- package/app/app.manifest +22 -0
- package/app/bin/DSH/345/260/217/347/252/227.exe +0 -0
- package/app/bin/Microsoft.Web.WebView2.Core.dll +0 -0
- package/app/bin/Microsoft.Web.WebView2.WinForms.dll +0 -0
- package/app/bin/WebView2Loader.dll +0 -0
- package/app/build-app.ps1 +112 -0
- package/app/dev.ps1 +63 -0
- package/app/hud-page.g.cs +164 -0
- package/app/hud-page.js +157 -0
- package/app/probe/clear-composer.js +8 -0
- package/app/probe/dom-probe.js +40 -0
- package/app/probe/drag.ps1 +35 -0
- package/app/probe/dump-composer.js +5 -0
- package/app/probe/frame-probe.js +21 -0
- package/app/probe/selection-sample.txt +4 -0
- package/app/probe/top-probe.js +39 -0
- package/app/vendor/Microsoft.Web.WebView2.Core.dll +0 -0
- package/app/vendor/Microsoft.Web.WebView2.WinForms.dll +0 -0
- package/app/vendor/WebView2Loader.dll +0 -0
- package/cordis.patch.yml +16 -0
- package/lib/client.js +660 -0
- package/lib/index.js +521 -0
- package/package.json +69 -4
- package/scripts/hud-shot.ps1 +168 -0
- package/scripts/native-status.ps1 +63 -0
- package/scripts/win-hud.ps1 +264 -0
- package//345/274/200/345/217/221/346/226/271/346/241/210-WebView2/345/216/237/347/224/237/345/260/217/347/252/227.md +216 -0
package/app/Service.cs
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Diagnostics;
|
|
3
|
+
using System.Drawing;
|
|
4
|
+
using System.IO;
|
|
5
|
+
using System.Runtime.InteropServices;
|
|
6
|
+
using System.Text;
|
|
7
|
+
using System.Windows.Forms;
|
|
8
|
+
|
|
9
|
+
namespace DshHud
|
|
10
|
+
{
|
|
11
|
+
/// <summary>
|
|
12
|
+
/// Flat ini-style settings shared by the window shell, the background service and the
|
|
13
|
+
/// host plugin. Deliberately not JSON: powershell 5.1, C# 5 and node all have to agree
|
|
14
|
+
/// on it without pulling in a parser.
|
|
15
|
+
/// </summary>
|
|
16
|
+
internal sealed class HudConfig
|
|
17
|
+
{
|
|
18
|
+
internal bool CopyShot;
|
|
19
|
+
internal bool Ask;
|
|
20
|
+
internal string HotkeyFull;
|
|
21
|
+
internal string HotkeyRegion;
|
|
22
|
+
|
|
23
|
+
internal HudConfig()
|
|
24
|
+
{
|
|
25
|
+
CopyShot = true;
|
|
26
|
+
Ask = true;
|
|
27
|
+
HotkeyFull = "Ctrl+Shift+1";
|
|
28
|
+
HotkeyRegion = "Ctrl+Shift+2";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
internal static string Dir
|
|
32
|
+
{
|
|
33
|
+
get
|
|
34
|
+
{
|
|
35
|
+
string dir = Path.Combine(
|
|
36
|
+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "dsh-hud");
|
|
37
|
+
Directory.CreateDirectory(dir);
|
|
38
|
+
return dir;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
internal static string ConfigPath { get { return Path.Combine(Dir, "config.ini"); } }
|
|
43
|
+
internal static string QuotePath { get { return Path.Combine(Dir, "quote.txt"); } }
|
|
44
|
+
internal static string CmdPath { get { return Path.Combine(Dir, "cmd.txt"); } }
|
|
45
|
+
|
|
46
|
+
internal static HudConfig Load()
|
|
47
|
+
{
|
|
48
|
+
HudConfig cfg = new HudConfig();
|
|
49
|
+
try
|
|
50
|
+
{
|
|
51
|
+
if (!File.Exists(ConfigPath)) return cfg;
|
|
52
|
+
foreach (string raw in File.ReadAllLines(ConfigPath, Encoding.UTF8))
|
|
53
|
+
{
|
|
54
|
+
string line = raw.Trim();
|
|
55
|
+
if (line.Length == 0 || line.StartsWith("#")) continue;
|
|
56
|
+
int eq = line.IndexOf('=');
|
|
57
|
+
if (eq <= 0) continue;
|
|
58
|
+
string key = line.Substring(0, eq).Trim();
|
|
59
|
+
string val = line.Substring(eq + 1).Trim();
|
|
60
|
+
if (key == "copyShot") cfg.CopyShot = val != "0";
|
|
61
|
+
else if (key == "ask") cfg.Ask = val != "0";
|
|
62
|
+
else if (key == "hotkeyFull") cfg.HotkeyFull = val;
|
|
63
|
+
else if (key == "hotkeyRegion") cfg.HotkeyRegion = val;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch
|
|
67
|
+
{
|
|
68
|
+
}
|
|
69
|
+
return cfg;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
internal void Save()
|
|
73
|
+
{
|
|
74
|
+
try
|
|
75
|
+
{
|
|
76
|
+
StringBuilder sb = new StringBuilder();
|
|
77
|
+
sb.AppendLine("# DSH 小窗设置 / settings. The settings page writes this file too.");
|
|
78
|
+
sb.AppendLine("copyShot=" + (CopyShot ? "1" : "0"));
|
|
79
|
+
sb.AppendLine("ask=" + (Ask ? "1" : "0"));
|
|
80
|
+
sb.AppendLine("hotkeyFull=" + HotkeyFull);
|
|
81
|
+
sb.AppendLine("hotkeyRegion=" + HotkeyRegion);
|
|
82
|
+
File.WriteAllText(ConfigPath, sb.ToString(), Encoding.UTF8);
|
|
83
|
+
}
|
|
84
|
+
catch
|
|
85
|
+
{
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
internal static long Stamp()
|
|
90
|
+
{
|
|
91
|
+
try
|
|
92
|
+
{
|
|
93
|
+
if (!File.Exists(ConfigPath)) return 0;
|
|
94
|
+
return File.GetLastWriteTimeUtc(ConfigPath).Ticks;
|
|
95
|
+
}
|
|
96
|
+
catch
|
|
97
|
+
{
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// <summary>
|
|
104
|
+
/// Hidden window that only exists to own global hotkeys (RegisterHotKey needs a hwnd
|
|
105
|
+
/// and a message loop) and to be a BeginInvoke target for the watcher.
|
|
106
|
+
/// </summary>
|
|
107
|
+
internal sealed class HotkeyHost : Form
|
|
108
|
+
{
|
|
109
|
+
internal event Action<string> HotkeyPressed;
|
|
110
|
+
|
|
111
|
+
private const int WM_HOTKEY = 0x0312;
|
|
112
|
+
private const int ID_FULL = 0x1001;
|
|
113
|
+
private const int ID_REGION = 0x1002;
|
|
114
|
+
|
|
115
|
+
internal HotkeyHost()
|
|
116
|
+
{
|
|
117
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
118
|
+
ShowInTaskbar = false;
|
|
119
|
+
StartPosition = FormStartPosition.Manual;
|
|
120
|
+
Size = new Size(1, 1);
|
|
121
|
+
Location = new Point(-4000, -4000);
|
|
122
|
+
Text = "DSH 划词服务";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
internal void Bind(HudConfig cfg)
|
|
126
|
+
{
|
|
127
|
+
try
|
|
128
|
+
{
|
|
129
|
+
UnregisterHotKey(Handle, ID_FULL);
|
|
130
|
+
UnregisterHotKey(Handle, ID_REGION);
|
|
131
|
+
|
|
132
|
+
uint mods, vk;
|
|
133
|
+
if (TryParse(cfg.HotkeyFull, out mods, out vk))
|
|
134
|
+
{
|
|
135
|
+
if (!RegisterHotKey(Handle, ID_FULL, mods, vk))
|
|
136
|
+
SelectionWatcher.Log("hotkey full busy: " + cfg.HotkeyFull);
|
|
137
|
+
}
|
|
138
|
+
if (TryParse(cfg.HotkeyRegion, out mods, out vk))
|
|
139
|
+
{
|
|
140
|
+
if (!RegisterHotKey(Handle, ID_REGION, mods, vk))
|
|
141
|
+
SelectionWatcher.Log("hotkey region busy: " + cfg.HotkeyRegion);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (Exception ex)
|
|
145
|
+
{
|
|
146
|
+
SelectionWatcher.Log("hotkey bind failed: " + ex.Message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
internal static bool TryParse(string spec, out uint mods, out uint vk)
|
|
151
|
+
{
|
|
152
|
+
mods = 0;
|
|
153
|
+
vk = 0;
|
|
154
|
+
if (string.IsNullOrEmpty(spec)) return false;
|
|
155
|
+
|
|
156
|
+
string[] parts = spec.Split('+');
|
|
157
|
+
for (int i = 0; i < parts.Length; i++)
|
|
158
|
+
{
|
|
159
|
+
string part = parts[i].Trim();
|
|
160
|
+
if (part.Length == 0) continue;
|
|
161
|
+
string low = part.ToLowerInvariant();
|
|
162
|
+
|
|
163
|
+
if (low == "ctrl" || low == "control") { mods |= 0x0002; continue; }
|
|
164
|
+
if (low == "alt") { mods |= 0x0001; continue; }
|
|
165
|
+
if (low == "shift") { mods |= 0x0004; continue; }
|
|
166
|
+
if (low == "win") { mods |= 0x0008; continue; }
|
|
167
|
+
|
|
168
|
+
if (low.Length == 1)
|
|
169
|
+
{
|
|
170
|
+
char c = char.ToUpperInvariant(low[0]);
|
|
171
|
+
if (c >= 'A' && c <= 'Z') { vk = (uint)c; continue; }
|
|
172
|
+
if (c >= '0' && c <= '9') { vk = (uint)c; continue; }
|
|
173
|
+
}
|
|
174
|
+
if (low.Length >= 2 && low[0] == 'f')
|
|
175
|
+
{
|
|
176
|
+
int n;
|
|
177
|
+
if (int.TryParse(low.Substring(1), out n) && n >= 1 && n <= 24)
|
|
178
|
+
{
|
|
179
|
+
vk = (uint)(0x70 + n - 1);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (low == "printscreen" || low == "prtsc") { vk = 0x2C; continue; }
|
|
184
|
+
if (low == "space") { vk = 0x20; continue; }
|
|
185
|
+
if (low == "tab") { vk = 0x09; continue; }
|
|
186
|
+
if (low == "enter" || low == "return") { vk = 0x0D; continue; }
|
|
187
|
+
if (low == "esc" || low == "escape") { vk = 0x1B; continue; }
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return vk != 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
protected override void OnVisibleChanged(EventArgs e)
|
|
194
|
+
{
|
|
195
|
+
base.OnVisibleChanged(e);
|
|
196
|
+
if (!Visible)
|
|
197
|
+
{
|
|
198
|
+
// never let the helper window surface, even if someone calls Show()
|
|
199
|
+
Hide();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
protected override void WndProc(ref Message m)
|
|
204
|
+
{
|
|
205
|
+
if (m.Msg == WM_HOTKEY)
|
|
206
|
+
{
|
|
207
|
+
int id = m.WParam.ToInt32();
|
|
208
|
+
Action<string> handler = HotkeyPressed;
|
|
209
|
+
if (handler != null) handler(id == ID_REGION ? "region" : "full");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
base.WndProc(ref m);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
216
|
+
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
|
217
|
+
|
|
218
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
219
|
+
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// <summary>
|
|
223
|
+
/// Background process that keeps "问大肥鱼" alive without the window: the pill must
|
|
224
|
+
/// work even when the HUD was never opened. It also owns the global screenshot hotkeys
|
|
225
|
+
/// and hands work to the window shell over files (quote.txt / cmd.txt).
|
|
226
|
+
/// </summary>
|
|
227
|
+
internal sealed class SelectionService : ApplicationContext
|
|
228
|
+
{
|
|
229
|
+
private readonly HotkeyHost host;
|
|
230
|
+
private readonly SelectionWatcher watcher;
|
|
231
|
+
private readonly NotifyIcon tray;
|
|
232
|
+
private readonly ToolStripMenuItem askItem;
|
|
233
|
+
private readonly string exePath;
|
|
234
|
+
private readonly string url;
|
|
235
|
+
private HudConfig config;
|
|
236
|
+
private long configStamp;
|
|
237
|
+
private readonly System.Windows.Forms.Timer configTimer;
|
|
238
|
+
|
|
239
|
+
internal SelectionService(string url)
|
|
240
|
+
{
|
|
241
|
+
this.url = url;
|
|
242
|
+
exePath = Application.ExecutablePath;
|
|
243
|
+
config = HudConfig.Load();
|
|
244
|
+
configStamp = HudConfig.Stamp();
|
|
245
|
+
|
|
246
|
+
host = new HotkeyHost();
|
|
247
|
+
IntPtr forceHandle = host.Handle; // create it now: hotkeys/message loop need it
|
|
248
|
+
host.HotkeyPressed += OnHotkey;
|
|
249
|
+
|
|
250
|
+
watcher = new SelectionWatcher(host);
|
|
251
|
+
watcher.QuoteRequested += DeliverQuote;
|
|
252
|
+
watcher.Enabled = config.Ask;
|
|
253
|
+
|
|
254
|
+
tray = new NotifyIcon();
|
|
255
|
+
tray.Icon = SystemIcons.Application;
|
|
256
|
+
tray.Text = "DSH 划词服务";
|
|
257
|
+
tray.Visible = true;
|
|
258
|
+
tray.DoubleClick += delegate { OpenHud(); };
|
|
259
|
+
|
|
260
|
+
ContextMenuStrip menu = new ContextMenuStrip();
|
|
261
|
+
menu.Items.Add("打开小窗", null, delegate { OpenHud(); });
|
|
262
|
+
askItem = new ToolStripMenuItem("划词问大肥鱼");
|
|
263
|
+
askItem.CheckOnClick = true;
|
|
264
|
+
askItem.Checked = config.Ask;
|
|
265
|
+
askItem.CheckedChanged += delegate
|
|
266
|
+
{
|
|
267
|
+
config.Ask = askItem.Checked;
|
|
268
|
+
config.Save();
|
|
269
|
+
configStamp = HudConfig.Stamp();
|
|
270
|
+
watcher.Enabled = config.Ask;
|
|
271
|
+
};
|
|
272
|
+
menu.Items.Add(askItem);
|
|
273
|
+
menu.Items.Add(new ToolStripSeparator());
|
|
274
|
+
menu.Items.Add("退出", null, delegate { ExitThread(); });
|
|
275
|
+
tray.ContextMenuStrip = menu;
|
|
276
|
+
|
|
277
|
+
host.Bind(config);
|
|
278
|
+
SelectionWatcher.Log("service started url=" + (url == null ? "none" : "yes"));
|
|
279
|
+
|
|
280
|
+
configTimer = new System.Windows.Forms.Timer();
|
|
281
|
+
configTimer.Interval = 2500;
|
|
282
|
+
configTimer.Tick += OnConfigTick;
|
|
283
|
+
configTimer.Start();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// <summary>Diagnostic hook: pillrect.txt holds "x,y,w,h" and previews the pill placement.</summary>
|
|
287
|
+
private void CheckPillRect()
|
|
288
|
+
{
|
|
289
|
+
try
|
|
290
|
+
{
|
|
291
|
+
string path = Path.Combine(HudConfig.Dir, "pillrect.txt");
|
|
292
|
+
if (!File.Exists(path)) return;
|
|
293
|
+
string raw = File.ReadAllText(path).Trim();
|
|
294
|
+
File.Delete(path);
|
|
295
|
+
string[] parts = raw.Split(',');
|
|
296
|
+
if (parts.Length != 4) return;
|
|
297
|
+
Rectangle rect = new Rectangle(int.Parse(parts[0]), int.Parse(parts[1]),
|
|
298
|
+
int.Parse(parts[2]), int.Parse(parts[3]));
|
|
299
|
+
watcher.DebugPillAt(rect);
|
|
300
|
+
}
|
|
301
|
+
catch (Exception ex)
|
|
302
|
+
{
|
|
303
|
+
SelectionWatcher.Log("pillrect failed: " + ex.Message);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private void OnConfigTick(object sender, EventArgs e)
|
|
308
|
+
{
|
|
309
|
+
CheckPillRect();
|
|
310
|
+
long stamp = HudConfig.Stamp();
|
|
311
|
+
if (stamp == configStamp) return;
|
|
312
|
+
configStamp = stamp;
|
|
313
|
+
config = HudConfig.Load();
|
|
314
|
+
watcher.Enabled = config.Ask;
|
|
315
|
+
askItem.Checked = config.Ask;
|
|
316
|
+
host.Bind(config);
|
|
317
|
+
SelectionWatcher.Log("config reloaded: hotkeyFull=" + config.HotkeyFull +
|
|
318
|
+
" hotkeyRegion=" + config.HotkeyRegion);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private void OnHotkey(string kind)
|
|
322
|
+
{
|
|
323
|
+
try
|
|
324
|
+
{
|
|
325
|
+
SelectionWatcher.Log("hotkey " + kind);
|
|
326
|
+
File.WriteAllText(HudConfig.CmdPath, kind, Encoding.UTF8);
|
|
327
|
+
OpenHud();
|
|
328
|
+
}
|
|
329
|
+
catch (Exception ex)
|
|
330
|
+
{
|
|
331
|
+
SelectionWatcher.Log("hotkey failed: " + ex.Message);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/// <summary>The pill was clicked: park the text, then bring the window up.</summary>
|
|
336
|
+
private void DeliverQuote(string text)
|
|
337
|
+
{
|
|
338
|
+
try
|
|
339
|
+
{
|
|
340
|
+
File.WriteAllText(HudConfig.QuotePath, text, Encoding.UTF8);
|
|
341
|
+
SelectionWatcher.Log("quote saved " + text.Length + " chars");
|
|
342
|
+
}
|
|
343
|
+
catch (Exception ex)
|
|
344
|
+
{
|
|
345
|
+
SelectionWatcher.Log("quote save failed: " + ex.Message);
|
|
346
|
+
}
|
|
347
|
+
OpenHud();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private void OpenHud()
|
|
351
|
+
{
|
|
352
|
+
try
|
|
353
|
+
{
|
|
354
|
+
IntPtr hwnd = FindHudWindow();
|
|
355
|
+
if (hwnd != IntPtr.Zero)
|
|
356
|
+
{
|
|
357
|
+
Native.ShowWindow(hwnd, Native.SW_RESTORE);
|
|
358
|
+
Native.SetForegroundWindow(hwnd);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (string.IsNullOrEmpty(url))
|
|
362
|
+
{
|
|
363
|
+
SelectionWatcher.Log("spawn skipped: no url");
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
Process.Start(exePath, "--url \"" + url + "\" --title \"DSH 小窗\"");
|
|
367
|
+
}
|
|
368
|
+
catch (Exception ex)
|
|
369
|
+
{
|
|
370
|
+
SelectionWatcher.Log("spawn failed: " + ex.Message);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private static IntPtr FindHudWindow()
|
|
375
|
+
{
|
|
376
|
+
try
|
|
377
|
+
{
|
|
378
|
+
string name = Path.GetFileNameWithoutExtension(Application.ExecutablePath);
|
|
379
|
+
foreach (Process p in Process.GetProcessesByName(name))
|
|
380
|
+
{
|
|
381
|
+
try
|
|
382
|
+
{
|
|
383
|
+
if (p.MainWindowHandle != IntPtr.Zero) return p.MainWindowHandle;
|
|
384
|
+
}
|
|
385
|
+
catch
|
|
386
|
+
{
|
|
387
|
+
}
|
|
388
|
+
finally
|
|
389
|
+
{
|
|
390
|
+
p.Dispose();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch
|
|
395
|
+
{
|
|
396
|
+
}
|
|
397
|
+
return IntPtr.Zero;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
protected override void Dispose(bool disposing)
|
|
401
|
+
{
|
|
402
|
+
if (disposing)
|
|
403
|
+
{
|
|
404
|
+
try { configTimer.Stop(); configTimer.Dispose(); } catch { }
|
|
405
|
+
try { if (watcher != null) watcher.Dispose(); } catch { }
|
|
406
|
+
try { if (tray != null) { tray.Visible = false; tray.Dispose(); } } catch { }
|
|
407
|
+
try { if (host != null) host.Dispose(); } catch { }
|
|
408
|
+
}
|
|
409
|
+
base.Dispose(disposing);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
package/app/app.manifest
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
|
3
|
+
<assemblyIdentity version="1.0.0.0" name="DshHud.app" />
|
|
4
|
+
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
5
|
+
<windowsSettings>
|
|
6
|
+
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
|
7
|
+
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
|
|
8
|
+
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
|
9
|
+
</windowsSettings>
|
|
10
|
+
</application>
|
|
11
|
+
<dependency>
|
|
12
|
+
<dependentAssembly>
|
|
13
|
+
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
|
|
14
|
+
processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
|
|
15
|
+
</dependentAssembly>
|
|
16
|
+
</dependency>
|
|
17
|
+
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
|
18
|
+
<application>
|
|
19
|
+
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
|
20
|
+
</application>
|
|
21
|
+
</compatibility>
|
|
22
|
+
</assembly>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$Clean
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
|
|
7
|
+
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
8
|
+
$vendor = Join-Path $here 'vendor'
|
|
9
|
+
$bin = Join-Path $here 'bin'
|
|
10
|
+
New-Item -ItemType Directory -Force -Path $vendor, $bin | Out-Null
|
|
11
|
+
|
|
12
|
+
if ($Clean) {
|
|
13
|
+
Get-ChildItem $bin -File -ErrorAction SilentlyContinue | Remove-Item -Force
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
$csc = 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe'
|
|
17
|
+
if (-not (Test-Path $csc)) { throw "csc.exe not found at $csc" }
|
|
18
|
+
|
|
19
|
+
# --- vendor: WebView2 assemblies ---
|
|
20
|
+
# Order: app\vendor (committed) -> %TEMP%\wv2 (cache) -> NuGet download.
|
|
21
|
+
$wv2Version = '1.0.4314.35'
|
|
22
|
+
$wv2root = Join-Path $env:TEMP 'wv2'
|
|
23
|
+
$deps = @(
|
|
24
|
+
@{ src = "$wv2root\lib\net462\Microsoft.Web.WebView2.Core.dll"; dst = 'Microsoft.Web.WebView2.Core.dll' },
|
|
25
|
+
@{ src = "$wv2root\lib\net462\Microsoft.Web.WebView2.WinForms.dll"; dst = 'Microsoft.Web.WebView2.WinForms.dll' },
|
|
26
|
+
@{ src = "$wv2root\runtimes\win-x64\native\WebView2Loader.dll"; dst = 'WebView2Loader.dll' }
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
function Test-Cache {
|
|
30
|
+
foreach ($d in $deps) {
|
|
31
|
+
if (-not (Test-Path $d.src)) { return $false }
|
|
32
|
+
}
|
|
33
|
+
return $true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
$missing = @()
|
|
37
|
+
foreach ($d in $deps) {
|
|
38
|
+
if ($Clean -or -not (Test-Path (Join-Path $vendor $d.dst))) { $missing += $d }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if ($missing.Count -gt 0 -and -not (Test-Cache)) {
|
|
42
|
+
Write-Output 'WebView2 assemblies not cached - downloading from NuGet...'
|
|
43
|
+
$pkg = Join-Path $env:TEMP ("webview2-" + $wv2Version + ".zip")
|
|
44
|
+
$url = 'https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/' + $wv2Version
|
|
45
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
46
|
+
Invoke-WebRequest -Uri $url -OutFile $pkg -UseBasicParsing
|
|
47
|
+
if (Test-Path $wv2root) { Remove-Item $wv2root -Recurse -Force }
|
|
48
|
+
New-Item -ItemType Directory -Force -Path $wv2root | Out-Null
|
|
49
|
+
Expand-Archive -Path $pkg -DestinationPath $wv2root -Force
|
|
50
|
+
Remove-Item $pkg -Force
|
|
51
|
+
if (-not (Test-Cache)) { throw "WebView2 package did not contain the expected files under $wv2root" }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
foreach ($d in $missing) {
|
|
55
|
+
Copy-Item $d.src (Join-Path $vendor $d.dst) -Force
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# --- generate the page-script constant from hud-page.js ---
|
|
59
|
+
$jsPath = Join-Path $here 'hud-page.js'
|
|
60
|
+
$js = [System.IO.File]::ReadAllText($jsPath, [System.Text.Encoding]::UTF8)
|
|
61
|
+
$escaped = $js.Replace('"', '""')
|
|
62
|
+
$gen = "namespace DshHud`r`n{`r`n internal static class HudPage`r`n {`r`n internal const string Script = @`"$escaped`";`r`n }`r`n}`r`n"
|
|
63
|
+
[System.IO.File]::WriteAllText((Join-Path $here 'hud-page.g.cs'), $gen, (New-Object System.Text.UTF8Encoding($true)))
|
|
64
|
+
|
|
65
|
+
# --- compile ---
|
|
66
|
+
# Built from code points on purpose: if this file is ever saved without a BOM,
|
|
67
|
+
# powershell 5.1 reads it as GBK and a literal '小窗' would become mojibake.
|
|
68
|
+
$exeBase = 'DSH' + [char]0x5C0F + [char]0x7A97
|
|
69
|
+
$exe = Join-Path $bin ($exeBase + '.exe')
|
|
70
|
+
$refs = @(
|
|
71
|
+
'System.dll',
|
|
72
|
+
'System.Core.dll',
|
|
73
|
+
'System.Drawing.dll',
|
|
74
|
+
'System.Windows.Forms.dll',
|
|
75
|
+
'System.Xml.dll'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
$cscArgs = @('/nologo', '/target:winexe', '/platform:x64', '/optimize+', '/langversion:5')
|
|
79
|
+
$cscArgs += ('/out:' + $exe)
|
|
80
|
+
$cscArgs += ('/win32manifest:' + (Join-Path $here 'app.manifest'))
|
|
81
|
+
foreach ($r in $refs) { $cscArgs += ('/r:' + $r) }
|
|
82
|
+
$cscArgs += ('/r:' + (Join-Path $vendor 'Microsoft.Web.WebView2.Core.dll'))
|
|
83
|
+
$cscArgs += ('/r:' + (Join-Path $vendor 'Microsoft.Web.WebView2.WinForms.dll'))
|
|
84
|
+
# UIA gives us the selection rectangle (and a passive read that needs no Ctrl+C).
|
|
85
|
+
$uiaRoot = 'C:\Windows\Microsoft.NET\assembly\GAC_MSIL'
|
|
86
|
+
$uiaClient = Join-Path $uiaRoot 'UIAutomationClient\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationClient.dll'
|
|
87
|
+
$uiaTypes = Join-Path $uiaRoot 'UIAutomationTypes\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationTypes.dll'
|
|
88
|
+
foreach ($uia in @($uiaClient, $uiaTypes)) {
|
|
89
|
+
if (-not (Test-Path $uia)) { throw "UIA assembly missing: $uia" }
|
|
90
|
+
$cscArgs += ('/r:' + $uia)
|
|
91
|
+
}
|
|
92
|
+
# TextPattern.GetBoundingRectangles returns System.Windows.Rect, so WindowsBase is needed.
|
|
93
|
+
$windowsBase = Join-Path $uiaRoot 'WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll'
|
|
94
|
+
if (-not (Test-Path $windowsBase)) { throw "WindowsBase missing: $windowsBase" }
|
|
95
|
+
$cscArgs += ('/r:' + $windowsBase)
|
|
96
|
+
$cscArgs += (Join-Path $here 'HudWindow.cs')
|
|
97
|
+
$cscArgs += (Join-Path $here 'SelectionWatcher.cs')
|
|
98
|
+
$cscArgs += (Join-Path $here 'Service.cs')
|
|
99
|
+
$cscArgs += (Join-Path $here 'RegionCapture.cs')
|
|
100
|
+
$cscArgs += (Join-Path $here 'hud-page.g.cs')
|
|
101
|
+
|
|
102
|
+
& $csc @cscArgs
|
|
103
|
+
if ($LASTEXITCODE -ne 0) { throw "compile failed with exit code $LASTEXITCODE" }
|
|
104
|
+
|
|
105
|
+
# --- stage runtime files next to the exe ---
|
|
106
|
+
Copy-Item (Join-Path $vendor 'Microsoft.Web.WebView2.Core.dll') $bin -Force
|
|
107
|
+
Copy-Item (Join-Path $vendor 'Microsoft.Web.WebView2.WinForms.dll') $bin -Force
|
|
108
|
+
Copy-Item (Join-Path $vendor 'WebView2Loader.dll') $bin -Force
|
|
109
|
+
|
|
110
|
+
Write-Output ''
|
|
111
|
+
Write-Output 'build ok:'
|
|
112
|
+
Get-ChildItem $bin -File | ForEach-Object { ' ' + $_.Name + ' ' + $_.Length + ' bytes' }
|
package/app/dev.ps1
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$Build,
|
|
3
|
+
[string]$Url = '',
|
|
4
|
+
[string]$Eval = '',
|
|
5
|
+
[int]$WaitSeconds = 9
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
$ErrorActionPreference = 'Continue'
|
|
9
|
+
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
10
|
+
$exe = Join-Path $here 'bin\DSH小窗.exe'
|
|
11
|
+
$stateDir = Join-Path $env:LOCALAPPDATA 'dsh-hud'
|
|
12
|
+
|
|
13
|
+
function Get-TokenUrl {
|
|
14
|
+
$body = '{"href":"http://127.0.0.1:58791/"}'
|
|
15
|
+
try {
|
|
16
|
+
$r = Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:58791/dsh-hud/open' `
|
|
17
|
+
-ContentType 'application/json' -Body $body -TimeoutSec 25
|
|
18
|
+
if ($r.url) { return $r.url }
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
return ''
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Get-Process -Name 'DSH小窗' -ErrorAction SilentlyContinue | Stop-Process -Force
|
|
25
|
+
Start-Sleep -Milliseconds 800
|
|
26
|
+
|
|
27
|
+
if ($Build) {
|
|
28
|
+
$blog = Join-Path $env:TEMP 'build-out.txt'
|
|
29
|
+
Start-Process -FilePath 'powershell.exe' `
|
|
30
|
+
-ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $here 'build-app.ps1')) `
|
|
31
|
+
-NoNewWindow -Wait -RedirectStandardOutput $blog -RedirectStandardError ($blog + '.err')
|
|
32
|
+
$lines = Get-Content $blog -Encoding Default
|
|
33
|
+
if ($lines -match 'error CS') {
|
|
34
|
+
$lines | Where-Object { $_ -match 'error CS' } | ForEach-Object { Write-Output $_ }
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
Write-Output 'build ok'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
$target = $Url
|
|
41
|
+
if ([string]::IsNullOrEmpty($target)) { $target = Get-TokenUrl }
|
|
42
|
+
if ([string]::IsNullOrEmpty($target)) {
|
|
43
|
+
Write-Output 'no url: pass -Url or have the dsh-hud-window plugin running'
|
|
44
|
+
exit 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$p = Start-Process -FilePath $exe -ArgumentList @('--url', $target, '--title', 'DSH 小窗', '--debug') -PassThru
|
|
48
|
+
Write-Output ('started pid ' + $p.Id)
|
|
49
|
+
Start-Sleep -Seconds $WaitSeconds
|
|
50
|
+
|
|
51
|
+
if (-not [string]::IsNullOrEmpty($Eval)) {
|
|
52
|
+
$js = [System.IO.File]::ReadAllText($Eval, [System.Text.Encoding]::UTF8)
|
|
53
|
+
[System.IO.File]::WriteAllText((Join-Path $stateDir 'eval.js'), $js, [System.Text.UTF8Encoding]::new($false))
|
|
54
|
+
Start-Sleep -Seconds 3
|
|
55
|
+
$log = Join-Path $stateDir 'hud.log'
|
|
56
|
+
if (Test-Path $log) {
|
|
57
|
+
$t = [System.IO.File]::ReadAllText($log, [System.Text.Encoding]::UTF8)
|
|
58
|
+
Write-Output ('log length: ' + $t.Length)
|
|
59
|
+
if ($t.Length -gt 6000) { Write-Output $t.Substring(0, 6000) } else { Write-Output $t }
|
|
60
|
+
} else {
|
|
61
|
+
Write-Output 'no hud.log'
|
|
62
|
+
}
|
|
63
|
+
}
|