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
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Diagnostics;
|
|
4
|
+
using System.Drawing;
|
|
5
|
+
using System.Runtime.InteropServices;
|
|
6
|
+
using System.Text;
|
|
7
|
+
using System.Windows.Forms;
|
|
8
|
+
|
|
9
|
+
namespace DshHud
|
|
10
|
+
{
|
|
11
|
+
/// <summary>One snap candidate: a top-level window as it was just before the overlay opened.</summary>
|
|
12
|
+
internal sealed class CaptureTarget
|
|
13
|
+
{
|
|
14
|
+
internal Rectangle Rect; // screen pixels
|
|
15
|
+
internal string Title;
|
|
16
|
+
|
|
17
|
+
internal CaptureTarget(Rectangle rect, string title)
|
|
18
|
+
{
|
|
19
|
+
Rect = rect;
|
|
20
|
+
Title = title;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// <summary>
|
|
25
|
+
/// Full-screen picker for the "框选" screenshot key.
|
|
26
|
+
///
|
|
27
|
+
/// It does not grab the screen itself: the caller freezes the desktop into a bitmap,
|
|
28
|
+
/// hands it over, and this window just paints it. What you see under the overlay is
|
|
29
|
+
/// therefore exactly what gets cropped, with no second capture and no flicker.
|
|
30
|
+
///
|
|
31
|
+
/// Unit handling: this process is per-monitor DPI aware, and WinForms reports client
|
|
32
|
+
/// sizes in logical units while the desktop bitmap is in physical pixels. Mixing the
|
|
33
|
+
/// two silently produces half-size or 1.5x selections, so everything here - the frozen
|
|
34
|
+
/// bitmap, the selection, the snap candidates, the cursor - stays in PHYSICAL SCREEN
|
|
35
|
+
/// pixels, and the only conversion happens in ScreenToClientRect() at paint time. The
|
|
36
|
+
/// window itself is forced onto the physical virtual screen with SetWindowPos, because
|
|
37
|
+
/// assigning Bounds in logical units lands somewhere else at 150% scaling.
|
|
38
|
+
///
|
|
39
|
+
/// Hit testing does NOT use WindowFromPoint: the overlay covers every pixel, so
|
|
40
|
+
/// WindowFromPoint would only ever return the overlay. The caller snapshots the window
|
|
41
|
+
/// list first (EnumWindows is top-most first) and this form picks the first rectangle
|
|
42
|
+
/// containing the cursor - what WindowFromPoint would have said.
|
|
43
|
+
///
|
|
44
|
+
/// Interaction: moving the mouse highlights the window under the cursor (a plain click
|
|
45
|
+
/// captures just that window); dragging any distance switches to a free rectangle;
|
|
46
|
+
/// Esc or right-click cancels.
|
|
47
|
+
/// </summary>
|
|
48
|
+
internal sealed class RegionCaptureForm : Form
|
|
49
|
+
{
|
|
50
|
+
private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
|
|
51
|
+
private const int DWMWA_CLOAKED = 14;
|
|
52
|
+
private const int GWL_EXSTYLE = -20;
|
|
53
|
+
private const long WS_EX_TOOLWINDOW = 0x00000080;
|
|
54
|
+
|
|
55
|
+
private const int HWND_TOPMOST = -1;
|
|
56
|
+
private const uint SWP_NOACTIVATE = 0x0010;
|
|
57
|
+
private const uint SWP_SHOWWINDOW = 0x0040;
|
|
58
|
+
|
|
59
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
60
|
+
private struct RECT
|
|
61
|
+
{
|
|
62
|
+
public int Left;
|
|
63
|
+
public int Top;
|
|
64
|
+
public int Right;
|
|
65
|
+
public int Bottom;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
69
|
+
|
|
70
|
+
[DllImport("user32.dll")]
|
|
71
|
+
private static extern bool EnumWindows(EnumWindowsProc callback, IntPtr lParam);
|
|
72
|
+
|
|
73
|
+
[DllImport("user32.dll")]
|
|
74
|
+
private static extern bool IsWindowVisible(IntPtr hWnd);
|
|
75
|
+
|
|
76
|
+
[DllImport("user32.dll")]
|
|
77
|
+
private static extern bool GetWindowRect(IntPtr hWnd, out RECT r);
|
|
78
|
+
|
|
79
|
+
[DllImport("user32.dll")]
|
|
80
|
+
private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int index);
|
|
81
|
+
|
|
82
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
83
|
+
private static extern int GetWindowText(IntPtr hWnd, StringBuilder sb, int max);
|
|
84
|
+
|
|
85
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
86
|
+
private static extern int GetClassName(IntPtr hWnd, StringBuilder sb, int max);
|
|
87
|
+
|
|
88
|
+
[DllImport("user32.dll")]
|
|
89
|
+
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint pid);
|
|
90
|
+
|
|
91
|
+
[DllImport("user32.dll")]
|
|
92
|
+
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
93
|
+
|
|
94
|
+
[DllImport("user32.dll")]
|
|
95
|
+
private static extern bool SetWindowPos(IntPtr hWnd, int after, int x, int y, int cx, int cy, uint flags);
|
|
96
|
+
|
|
97
|
+
private delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdc, ref RECT rect, IntPtr data);
|
|
98
|
+
|
|
99
|
+
[DllImport("user32.dll")]
|
|
100
|
+
private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr clip, MonitorEnumProc callback, IntPtr data);
|
|
101
|
+
|
|
102
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
103
|
+
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO info);
|
|
104
|
+
|
|
105
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
106
|
+
private struct MONITORINFO
|
|
107
|
+
{
|
|
108
|
+
public int cbSize;
|
|
109
|
+
public RECT rcMonitor;
|
|
110
|
+
public RECT rcWork;
|
|
111
|
+
public int dwFlags;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
[DllImport("dwmapi.dll")]
|
|
115
|
+
private static extern int DwmGetWindowAttribute(IntPtr hWnd, int attr, out RECT value, int size);
|
|
116
|
+
|
|
117
|
+
[DllImport("dwmapi.dll")]
|
|
118
|
+
private static extern int DwmGetWindowAttribute(IntPtr hWnd, int attr, out int value, int size);
|
|
119
|
+
|
|
120
|
+
private readonly Bitmap frozen;
|
|
121
|
+
private readonly Rectangle screen; // virtual screen, physical pixels
|
|
122
|
+
private readonly List<CaptureTarget> targets;
|
|
123
|
+
|
|
124
|
+
private Rectangle selection = Rectangle.Empty; // physical screen pixels
|
|
125
|
+
private string selectionHint = "";
|
|
126
|
+
private bool dragging;
|
|
127
|
+
private Point dragStart; // physical screen pixels
|
|
128
|
+
private Rectangle result;
|
|
129
|
+
|
|
130
|
+
internal RegionCaptureForm(Bitmap frozen, Rectangle virtualScreen, List<CaptureTarget> targets)
|
|
131
|
+
{
|
|
132
|
+
this.frozen = frozen;
|
|
133
|
+
this.screen = virtualScreen;
|
|
134
|
+
this.targets = targets ?? new List<CaptureTarget>();
|
|
135
|
+
|
|
136
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
137
|
+
StartPosition = FormStartPosition.Manual;
|
|
138
|
+
ShowInTaskbar = false;
|
|
139
|
+
TopMost = true;
|
|
140
|
+
AutoScaleMode = AutoScaleMode.None;
|
|
141
|
+
BackColor = Color.Black;
|
|
142
|
+
KeyPreview = true;
|
|
143
|
+
Cursor = Cursors.Cross;
|
|
144
|
+
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
|
145
|
+
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
|
146
|
+
SetStyle(ControlStyles.UserPaint, true);
|
|
147
|
+
DoubleBuffered = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// <summary>The chosen rectangle, in physical screen pixels. Valid when ShowDialog returned OK.</summary>
|
|
151
|
+
internal Rectangle SelectedRect
|
|
152
|
+
{
|
|
153
|
+
get { return result; }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// <summary>
|
|
157
|
+
/// The virtual desktop rectangle in PHYSICAL pixels.
|
|
158
|
+
///
|
|
159
|
+
/// SystemInformation.VirtualScreen cannot be used here: in a per-monitor-v2 process
|
|
160
|
+
/// GetSystemMetrics (which backs it) answers for the calling thread's DPI context,
|
|
161
|
+
/// so it reports the 150%-scaled logical size and the overlay would cover only the
|
|
162
|
+
/// top-left two thirds of the screen. Monitor rectangles are always physical.
|
|
163
|
+
/// </summary>
|
|
164
|
+
internal static Rectangle PhysicalVirtualScreen()
|
|
165
|
+
{
|
|
166
|
+
Rectangle union = Rectangle.Empty;
|
|
167
|
+
bool first = true;
|
|
168
|
+
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate(IntPtr hMonitor, IntPtr hdc, ref RECT r, IntPtr data)
|
|
169
|
+
{
|
|
170
|
+
MONITORINFO mi = new MONITORINFO();
|
|
171
|
+
mi.cbSize = Marshal.SizeOf(typeof(MONITORINFO));
|
|
172
|
+
if (GetMonitorInfo(hMonitor, ref mi))
|
|
173
|
+
{
|
|
174
|
+
Rectangle m = Rectangle.FromLTRB(mi.rcMonitor.Left, mi.rcMonitor.Top,
|
|
175
|
+
mi.rcMonitor.Right, mi.rcMonitor.Bottom);
|
|
176
|
+
if (first)
|
|
177
|
+
{
|
|
178
|
+
union = m;
|
|
179
|
+
first = false;
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
{
|
|
183
|
+
union = Rectangle.Union(union, m);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}, IntPtr.Zero);
|
|
188
|
+
|
|
189
|
+
if (first)
|
|
190
|
+
{
|
|
191
|
+
// Nothing enumerated (should not happen); fall back to the primary monitor.
|
|
192
|
+
return Screen.PrimaryScreen.Bounds;
|
|
193
|
+
}
|
|
194
|
+
return union;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// <summary>Grabs the whole virtual desktop, in physical pixels.</summary>
|
|
198
|
+
internal static Bitmap GrabDesktop(Rectangle virtualScreen)
|
|
199
|
+
{
|
|
200
|
+
Bitmap bmp = new Bitmap(virtualScreen.Width, virtualScreen.Height,
|
|
201
|
+
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
202
|
+
using (Graphics g = Graphics.FromImage(bmp))
|
|
203
|
+
{
|
|
204
|
+
g.CopyFromScreen(virtualScreen.X, virtualScreen.Y, 0, 0, virtualScreen.Size,
|
|
205
|
+
CopyPixelOperation.SourceCopy);
|
|
206
|
+
}
|
|
207
|
+
return bmp;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// <summary>
|
|
211
|
+
/// Snapshot of the top-level windows, top-most first, minus our own process, the
|
|
212
|
+
/// shell surfaces and DWM-cloaked ghosts (a UWP window the shell put away still
|
|
213
|
+
/// reports IsWindowVisible and would otherwise swallow the whole screen).
|
|
214
|
+
/// </summary>
|
|
215
|
+
internal static List<CaptureTarget> CollectTargets()
|
|
216
|
+
{
|
|
217
|
+
List<CaptureTarget> list = new List<CaptureTarget>();
|
|
218
|
+
uint self = (uint)Process.GetCurrentProcess().Id;
|
|
219
|
+
|
|
220
|
+
EnumWindows(delegate(IntPtr h, IntPtr l)
|
|
221
|
+
{
|
|
222
|
+
try
|
|
223
|
+
{
|
|
224
|
+
if (!IsWindowVisible(h)) return true;
|
|
225
|
+
|
|
226
|
+
long ex = GetWindowLongPtr(h, GWL_EXSTYLE).ToInt64();
|
|
227
|
+
if ((ex & WS_EX_TOOLWINDOW) != 0) return true;
|
|
228
|
+
if (IsShellSurface(h)) return true;
|
|
229
|
+
|
|
230
|
+
uint pid;
|
|
231
|
+
GetWindowThreadProcessId(h, out pid);
|
|
232
|
+
if (pid == self) return true;
|
|
233
|
+
if (IsCloaked(h)) return true;
|
|
234
|
+
|
|
235
|
+
StringBuilder sb = new StringBuilder(256);
|
|
236
|
+
if (GetWindowText(h, sb, sb.Capacity) <= 0) return true;
|
|
237
|
+
string title = sb.ToString().Trim();
|
|
238
|
+
if (title.Length == 0) return true;
|
|
239
|
+
|
|
240
|
+
Rectangle r = FrameBounds(h);
|
|
241
|
+
if (r.Width < 40 || r.Height < 40) return true;
|
|
242
|
+
|
|
243
|
+
list.Add(new CaptureTarget(r, title));
|
|
244
|
+
}
|
|
245
|
+
catch
|
|
246
|
+
{
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}, IntPtr.Zero);
|
|
250
|
+
|
|
251
|
+
return list;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
protected override void OnHandleCreated(EventArgs e)
|
|
255
|
+
{
|
|
256
|
+
base.OnHandleCreated(e);
|
|
257
|
+
// Force the physical rectangle: a logical-units conversion inside WinForms would
|
|
258
|
+
// otherwise shrink the overlay to the logical screen at 150% scaling.
|
|
259
|
+
SetWindowPos(Handle, HWND_TOPMOST, screen.X, screen.Y, screen.Width, screen.Height,
|
|
260
|
+
SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
protected override void OnShown(EventArgs e)
|
|
264
|
+
{
|
|
265
|
+
base.OnShown(e);
|
|
266
|
+
// Take the keyboard so Esc works, and seed the highlight at the cursor.
|
|
267
|
+
Activate();
|
|
268
|
+
SetForegroundWindow(Handle);
|
|
269
|
+
SeedFromCursor(Cursor.Position);
|
|
270
|
+
Invalidate();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private void SeedFromCursor(Point screenPt)
|
|
274
|
+
{
|
|
275
|
+
Rectangle rect;
|
|
276
|
+
string hint;
|
|
277
|
+
CandidateAt(screenPt, out rect, out hint);
|
|
278
|
+
selection = rect;
|
|
279
|
+
selectionHint = hint;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
protected override void OnPaint(PaintEventArgs e)
|
|
283
|
+
{
|
|
284
|
+
Graphics g = e.Graphics;
|
|
285
|
+
|
|
286
|
+
// The frozen desktop, stretched into whatever client rectangle the screen maps
|
|
287
|
+
// to (identity at 100%, a scaled mapping if WinForms' units disagree with ours).
|
|
288
|
+
Rectangle screenInClient = ScreenToClientRect(screen);
|
|
289
|
+
if (frozen != null && screenInClient.Width > 0 && screenInClient.Height > 0)
|
|
290
|
+
{
|
|
291
|
+
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
|
|
292
|
+
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
|
|
293
|
+
g.DrawImage(frozen, screenInClient);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
Rectangle sel = ScreenToClientRect(selection);
|
|
297
|
+
sel.Intersect(new Rectangle(0, 0, ClientSize.Width, ClientSize.Height));
|
|
298
|
+
if (sel.Width > 2 && sel.Height > 2)
|
|
299
|
+
{
|
|
300
|
+
// Veil in four bands around the selection instead of a clipped fill: cheaper
|
|
301
|
+
// on a 2560x1600 backdrop and no aliasing seams along the edges.
|
|
302
|
+
using (SolidBrush veil = new SolidBrush(Color.FromArgb(120, 0, 0, 0)))
|
|
303
|
+
{
|
|
304
|
+
int cw = ClientSize.Width;
|
|
305
|
+
int ch = ClientSize.Height;
|
|
306
|
+
g.FillRectangle(veil, 0, 0, cw, sel.Top);
|
|
307
|
+
g.FillRectangle(veil, 0, sel.Bottom, cw, ch - sel.Bottom);
|
|
308
|
+
g.FillRectangle(veil, 0, sel.Top, sel.Left, sel.Height);
|
|
309
|
+
g.FillRectangle(veil, sel.Right, sel.Top, cw - sel.Right, sel.Height);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
using (Pen border = new Pen(Color.FromArgb(59, 130, 246), 2f))
|
|
313
|
+
{
|
|
314
|
+
g.DrawRectangle(border, sel.X, sel.Y, sel.Width - 1, sel.Height - 1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
DrawSizeTag(g, sel);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
DrawHint(g);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private void DrawSizeTag(Graphics g, Rectangle sel)
|
|
324
|
+
{
|
|
325
|
+
string text = selection.Width + " x " + selection.Height;
|
|
326
|
+
if (selectionHint.Length > 0)
|
|
327
|
+
{
|
|
328
|
+
text = selectionHint + " " + text;
|
|
329
|
+
if (text.Length > 56) text = text.Substring(0, 56) + "\u2026";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
using (Font font = new Font("Microsoft YaHei UI", 8.5f, FontStyle.Regular))
|
|
333
|
+
using (SolidBrush back = new SolidBrush(Color.FromArgb(215, 20, 20, 24)))
|
|
334
|
+
using (SolidBrush fore = new SolidBrush(Color.White))
|
|
335
|
+
{
|
|
336
|
+
Size sz = TextRenderer.MeasureText(text, font);
|
|
337
|
+
int padX = 9;
|
|
338
|
+
int padY = 4;
|
|
339
|
+
int w = sz.Width + padX * 2;
|
|
340
|
+
int h = sz.Height + padY * 2;
|
|
341
|
+
int x = sel.X;
|
|
342
|
+
int y = sel.Top - h - 6;
|
|
343
|
+
if (y < 0) y = sel.Top + 6;
|
|
344
|
+
if (x + w > ClientSize.Width) x = ClientSize.Width - w - 4;
|
|
345
|
+
if (x < 4) x = 4;
|
|
346
|
+
g.FillRectangle(back, x, y, w, h);
|
|
347
|
+
g.DrawString(text, font, fore, x + padX, y + padY);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private void DrawHint(Graphics g)
|
|
352
|
+
{
|
|
353
|
+
const string hint = "\u5355\u51FB\u7A97\u53E3\u622A\u53D6\u8BE5\u7A97\u53E3 \u00B7 \u62D6\u62FD\u6846\u9009\u4EFB\u610F\u533A\u57DF \u00B7 Esc \u53D6\u6D88";
|
|
354
|
+
using (Font font = new Font("Microsoft YaHei UI", 9f, FontStyle.Regular))
|
|
355
|
+
using (SolidBrush back = new SolidBrush(Color.FromArgb(215, 20, 20, 24)))
|
|
356
|
+
using (SolidBrush fore = new SolidBrush(Color.White))
|
|
357
|
+
{
|
|
358
|
+
Size sz = TextRenderer.MeasureText(hint, font);
|
|
359
|
+
int padX = 14;
|
|
360
|
+
int padY = 7;
|
|
361
|
+
int w = sz.Width + padX * 2;
|
|
362
|
+
int h = sz.Height + padY * 2;
|
|
363
|
+
int x = (ClientSize.Width - w) / 2;
|
|
364
|
+
if (x < 4) x = 4;
|
|
365
|
+
int y = 26;
|
|
366
|
+
g.FillRectangle(back, x, y, w, h);
|
|
367
|
+
g.DrawString(hint, font, fore, x + padX, y + padY);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
protected override void OnMouseMove(MouseEventArgs e)
|
|
372
|
+
{
|
|
373
|
+
base.OnMouseMove(e);
|
|
374
|
+
if (dragging)
|
|
375
|
+
{
|
|
376
|
+
selection = Normalize(dragStart, Cursor.Position);
|
|
377
|
+
selectionHint = "\u6846\u9009";
|
|
378
|
+
}
|
|
379
|
+
else
|
|
380
|
+
{
|
|
381
|
+
SeedFromCursor(Cursor.Position);
|
|
382
|
+
}
|
|
383
|
+
Invalidate();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
protected override void OnMouseDown(MouseEventArgs e)
|
|
387
|
+
{
|
|
388
|
+
base.OnMouseDown(e);
|
|
389
|
+
if (e.Button == MouseButtons.Right)
|
|
390
|
+
{
|
|
391
|
+
DialogResult = DialogResult.Cancel;
|
|
392
|
+
Close();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (e.Button != MouseButtons.Left) return;
|
|
396
|
+
|
|
397
|
+
dragging = true;
|
|
398
|
+
dragStart = Cursor.Position;
|
|
399
|
+
selection = new Rectangle(dragStart, Size.Empty);
|
|
400
|
+
selectionHint = "\u6846\u9009";
|
|
401
|
+
Invalidate();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
protected override void OnMouseUp(MouseEventArgs e)
|
|
405
|
+
{
|
|
406
|
+
base.OnMouseUp(e);
|
|
407
|
+
if (e.Button != MouseButtons.Left || !dragging) return;
|
|
408
|
+
|
|
409
|
+
dragging = false;
|
|
410
|
+
Point end = Cursor.Position;
|
|
411
|
+
Rectangle dragged = Normalize(dragStart, end);
|
|
412
|
+
|
|
413
|
+
Rectangle picked;
|
|
414
|
+
if (dragged.Width < 6 || dragged.Height < 6)
|
|
415
|
+
{
|
|
416
|
+
// A plain click (or a shaky one) means "this window".
|
|
417
|
+
string hint;
|
|
418
|
+
CandidateAt(end, out picked, out hint);
|
|
419
|
+
}
|
|
420
|
+
else
|
|
421
|
+
{
|
|
422
|
+
picked = dragged;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
Rectangle inScreen = picked;
|
|
426
|
+
inScreen.Intersect(screen);
|
|
427
|
+
if (inScreen.Width < 2 || inScreen.Height < 2)
|
|
428
|
+
{
|
|
429
|
+
selection = Rectangle.Empty;
|
|
430
|
+
Invalidate();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
SelectionWatcher.Log("picker pick " + inScreen.Width + "x" + inScreen.Height +
|
|
435
|
+
" at " + inScreen.X + "," + inScreen.Y);
|
|
436
|
+
result = inScreen;
|
|
437
|
+
DialogResult = DialogResult.OK;
|
|
438
|
+
Close();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
protected override void OnKeyDown(KeyEventArgs e)
|
|
442
|
+
{
|
|
443
|
+
base.OnKeyDown(e);
|
|
444
|
+
if (e.KeyCode == Keys.Escape)
|
|
445
|
+
{
|
|
446
|
+
DialogResult = DialogResult.Cancel;
|
|
447
|
+
Close();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
protected override void Dispose(bool disposing)
|
|
452
|
+
{
|
|
453
|
+
base.Dispose(disposing);
|
|
454
|
+
if (disposing && frozen != null)
|
|
455
|
+
{
|
|
456
|
+
frozen.Dispose();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/// <summary>
|
|
461
|
+
/// What is under a physical screen point: the top-most snapshotted window containing
|
|
462
|
+
/// it, or the monitor that owns the point when the desktop is empty there.
|
|
463
|
+
/// </summary>
|
|
464
|
+
private void CandidateAt(Point screenPt, out Rectangle rect, out string hint)
|
|
465
|
+
{
|
|
466
|
+
for (int i = 0; i < targets.Count; i++)
|
|
467
|
+
{
|
|
468
|
+
CaptureTarget t = targets[i];
|
|
469
|
+
if (t.Rect.Contains(screenPt))
|
|
470
|
+
{
|
|
471
|
+
rect = t.Rect;
|
|
472
|
+
hint = t.Title;
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
rect = screen;
|
|
478
|
+
hint = "\u5C4F\u5E55";
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/// <summary>
|
|
482
|
+
/// Maps a physical screen rectangle into client pixels through WinForms' own
|
|
483
|
+
/// screen-to-client mapping, so any unit mismatch between its bookkeeping and ours
|
|
484
|
+
/// cancels out instead of skewing the selection.
|
|
485
|
+
/// </summary>
|
|
486
|
+
private Rectangle ScreenToClientRect(Rectangle screenRect)
|
|
487
|
+
{
|
|
488
|
+
Point topLeft = PointToClient(new Point(screenRect.Left, screenRect.Top));
|
|
489
|
+
Point bottomRight = PointToClient(new Point(screenRect.Right, screenRect.Bottom));
|
|
490
|
+
return new Rectangle(topLeft.X, topLeft.Y,
|
|
491
|
+
bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/// <summary>
|
|
495
|
+
/// Window rect without the invisible resize border that GetWindowRect reports for
|
|
496
|
+
/// DWM-composited windows; the capture should match what the eye sees.
|
|
497
|
+
/// </summary>
|
|
498
|
+
private static Rectangle FrameBounds(IntPtr hwnd)
|
|
499
|
+
{
|
|
500
|
+
RECT r;
|
|
501
|
+
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, out r, Marshal.SizeOf(typeof(RECT))) == 0)
|
|
502
|
+
{
|
|
503
|
+
Rectangle dwm = Rectangle.FromLTRB(r.Left, r.Top, r.Right, r.Bottom);
|
|
504
|
+
if (dwm.Width > 2 && dwm.Height > 2) return dwm;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
RECT raw;
|
|
508
|
+
if (GetWindowRect(hwnd, out raw))
|
|
509
|
+
{
|
|
510
|
+
return Rectangle.FromLTRB(raw.Left, raw.Top, raw.Right, raw.Bottom);
|
|
511
|
+
}
|
|
512
|
+
return Rectangle.Empty;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private static bool IsCloaked(IntPtr hwnd)
|
|
516
|
+
{
|
|
517
|
+
try
|
|
518
|
+
{
|
|
519
|
+
int cloaked;
|
|
520
|
+
if (DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, out cloaked, sizeof(int)) == 0)
|
|
521
|
+
{
|
|
522
|
+
return cloaked != 0;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch
|
|
526
|
+
{
|
|
527
|
+
}
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private static bool IsShellSurface(IntPtr hwnd)
|
|
532
|
+
{
|
|
533
|
+
StringBuilder sb = new StringBuilder(128);
|
|
534
|
+
if (GetClassName(hwnd, sb, sb.Capacity) <= 0) return false;
|
|
535
|
+
string cls = sb.ToString().ToLowerInvariant();
|
|
536
|
+
return cls == "progman"
|
|
537
|
+
|| cls == "workerw"
|
|
538
|
+
|| cls == "shell_traywnd"
|
|
539
|
+
|| cls == "shell_secondarytraywnd"
|
|
540
|
+
|| cls == "desktopwindowxamlsource";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
private static Rectangle Normalize(Point a, Point b)
|
|
544
|
+
{
|
|
545
|
+
int x = Math.Min(a.X, b.X);
|
|
546
|
+
int y = Math.Min(a.Y, b.Y);
|
|
547
|
+
int w = Math.Abs(a.X - b.X);
|
|
548
|
+
int h = Math.Abs(a.Y - b.Y);
|
|
549
|
+
return new Rectangle(x, y, w, h);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|