deepseek-computer-use 0.1.2 → 0.2.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/CHANGELOG.md +11 -0
- package/README.md +16 -3
- package/lib/index.js +7 -0
- package/lib/update-client.js +426 -0
- package/native/Driver.cs +1906 -1906
- package/native/Json.cs +274 -274
- package/native/Overlay.cs +732 -732
- package/native/Program.cs +160 -160
- package/native/audio/Program.cs +95 -95
- package/native/wgcfw/Program.cs +249 -249
- package/package.json +20 -6
package/native/Overlay.cs
CHANGED
|
@@ -1,732 +1,732 @@
|
|
|
1
|
-
using System;
|
|
2
|
-
using System.Drawing;
|
|
3
|
-
using System.Drawing.Drawing2D;
|
|
4
|
-
using System.Drawing.Imaging;
|
|
5
|
-
using System.IO;
|
|
6
|
-
using System.Runtime.InteropServices;
|
|
7
|
-
using System.Windows.Forms;
|
|
8
|
-
|
|
9
|
-
// "DSH is using your computer" indicator, modelled on Codex's overlays:
|
|
10
|
-
// * Display Overlay - a breathing accent border around the whole virtual screen
|
|
11
|
-
// (Codex: "border size breath" / "border offset breath")
|
|
12
|
-
// * Cursor Overlay - a virtual cursor that glides to every pointer action, with
|
|
13
|
-
// optional suppression of the real system cursor
|
|
14
|
-
// * Esc - kill switch; writes a cancel file the helper checks before
|
|
15
|
-
// it injects anything
|
|
16
|
-
// Everything is layered, per-pixel alpha, click-through and never takes focus.
|
|
17
|
-
public sealed class Config
|
|
18
|
-
{
|
|
19
|
-
public static string CancelFile = "";
|
|
20
|
-
public static string CursorFile = "";
|
|
21
|
-
public static string AccentHex = "#3f7fd8";
|
|
22
|
-
public static string Title = "DSH 正在使用您的电脑";
|
|
23
|
-
public static string Hint = "Esc 取消";
|
|
24
|
-
public static bool SuppressCursor = false;
|
|
25
|
-
public static int OwnerPid = 0;
|
|
26
|
-
public static int Corner1 = 0; // unused placeholder kept for arg compatibility
|
|
27
|
-
public static Color Accent = Color.FromArgb(63, 127, 216);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public static class Native
|
|
31
|
-
{
|
|
32
|
-
[StructLayout(LayoutKind.Sequential)] public struct POINT { public int X, Y; }
|
|
33
|
-
[StructLayout(LayoutKind.Sequential)] public struct SIZE { public int CX, CY; }
|
|
34
|
-
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
35
|
-
public struct BLENDFUNCTION { public byte BlendOp; public byte BlendFlags; public byte SourceConstantAlpha; public byte AlphaFormat; }
|
|
36
|
-
|
|
37
|
-
[DllImport("user32.dll")] public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref POINT pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pptSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags);
|
|
38
|
-
[DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hWnd);
|
|
39
|
-
[DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
|
40
|
-
[DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint flags);
|
|
41
|
-
[DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
|
|
42
|
-
[DllImport("gdi32.dll")] public static extern bool DeleteDC(IntPtr hdc);
|
|
43
|
-
[DllImport("gdi32.dll")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr h);
|
|
44
|
-
[DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr h);
|
|
45
|
-
[DllImport("user32.dll")] public static extern IntPtr SetSystemCursor(IntPtr hcur, uint id);
|
|
46
|
-
[DllImport("user32.dll")] public static extern bool SystemParametersInfo(uint action, uint param, IntPtr pv, uint winIni);
|
|
47
|
-
[DllImport("user32.dll")] public static extern IntPtr CreateCursor(IntPtr inst, int x, int y, int w, int h, byte[] andMask, byte[] xorMask);
|
|
48
|
-
[DllImport("user32.dll")] public static extern IntPtr CopyIcon(IntPtr hIcon);
|
|
49
|
-
[DllImport("user32.dll")] public static extern bool DestroyCursor(IntPtr hCursor);
|
|
50
|
-
|
|
51
|
-
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
|
52
|
-
public const uint SWP_NOSIZE = 0x0001, SWP_NOMOVE = 0x0002, SWP_NOACTIVATE = 0x0010, SWP_SHOWWINDOW = 0x0040;
|
|
53
|
-
public const int ULW_ALPHA = 2;
|
|
54
|
-
public const uint SPI_SETCURSORS = 0x0057;
|
|
55
|
-
|
|
56
|
-
public static void ApplyLayered(Form form, Bitmap surface)
|
|
57
|
-
{
|
|
58
|
-
IntPtr screenDc = GetDC(IntPtr.Zero);
|
|
59
|
-
IntPtr memDc = CreateCompatibleDC(screenDc);
|
|
60
|
-
IntPtr hBitmap = surface.GetHbitmap(Color.FromArgb(0));
|
|
61
|
-
IntPtr oldBitmap = SelectObject(memDc, hBitmap);
|
|
62
|
-
try
|
|
63
|
-
{
|
|
64
|
-
POINT dst = new POINT { X = form.Left, Y = form.Top };
|
|
65
|
-
POINT src = new POINT { X = 0, Y = 0 };
|
|
66
|
-
SIZE size = new SIZE { CX = surface.Width, CY = surface.Height };
|
|
67
|
-
BLENDFUNCTION blend = new BLENDFUNCTION { BlendOp = 0, BlendFlags = 0, SourceConstantAlpha = 255, AlphaFormat = 1 };
|
|
68
|
-
UpdateLayeredWindow(form.Handle, screenDc, ref dst, ref size, memDc, ref src, 0, ref blend, ULW_ALPHA);
|
|
69
|
-
}
|
|
70
|
-
finally
|
|
71
|
-
{
|
|
72
|
-
SelectObject(memDc, oldBitmap);
|
|
73
|
-
DeleteObject(hBitmap);
|
|
74
|
-
DeleteDC(memDc);
|
|
75
|
-
ReleaseDC(IntPtr.Zero, screenDc);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
public static void Raise(Form form)
|
|
80
|
-
{
|
|
81
|
-
try { SetWindowPos(form.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW); } catch { }
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/// Path of the "cursors are hidden" flag. It carries our PID so a long-lived
|
|
85
|
-
/// supervisor can tell "the overlay is still hiding them" from "the overlay died
|
|
86
|
-
/// and left the desktop without a pointer".
|
|
87
|
-
public static string CursorFlagPath()
|
|
88
|
-
{
|
|
89
|
-
string dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "dsh-computer-use");
|
|
90
|
-
return Path.Combine(dir, "cursor-hidden.flag");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
static IntPtr _blankCursor;
|
|
94
|
-
|
|
95
|
-
public static void SuppressSystemCursors(bool suppress)
|
|
96
|
-
{
|
|
97
|
-
try
|
|
98
|
-
{
|
|
99
|
-
if (!suppress)
|
|
100
|
-
{
|
|
101
|
-
SystemParametersInfo(SPI_SETCURSORS, 0, IntPtr.Zero, 0);
|
|
102
|
-
try { File.Delete(CursorFlagPath()); } catch { }
|
|
103
|
-
if (_blankCursor != IntPtr.Zero) { try { DestroyCursor(_blankCursor); } catch { } _blankCursor = IntPtr.Zero; }
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
// Cache the blank cursor: Windows resets the cursors on resume, display
|
|
107
|
-
// change and some logins, so the controller calls this again periodically
|
|
108
|
-
// (Codex calls the same "system cursor re-suppression").
|
|
109
|
-
if (_blankCursor == IntPtr.Zero)
|
|
110
|
-
{
|
|
111
|
-
byte[] andMask = new byte[128];
|
|
112
|
-
byte[] xorMask = new byte[128];
|
|
113
|
-
for (int i = 0; i < andMask.Length; i++) andMask[i] = 0xFF;
|
|
114
|
-
_blankCursor = CreateCursor(IntPtr.Zero, 0, 0, 32, 32, andMask, xorMask);
|
|
115
|
-
}
|
|
116
|
-
IntPtr blank = _blankCursor;
|
|
117
|
-
if (blank == IntPtr.Zero) return;
|
|
118
|
-
uint[] ids = new uint[] { 32512, 32513, 32514, 32515, 32516, 32631, 32640, 32641, 32642, 32643, 32644, 32645, 32646, 32648, 32649, 32650, 32651, 32671 };
|
|
119
|
-
foreach (uint id in ids) SetSystemCursor(CopyIcon(blank), id);
|
|
120
|
-
try
|
|
121
|
-
{
|
|
122
|
-
Directory.CreateDirectory(Path.GetDirectoryName(CursorFlagPath()));
|
|
123
|
-
File.WriteAllText(CursorFlagPath(), System.Diagnostics.Process.GetCurrentProcess().Id.ToString());
|
|
124
|
-
}
|
|
125
|
-
catch { }
|
|
126
|
-
}
|
|
127
|
-
catch { }
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
public sealed class CursorForm : Form
|
|
132
|
-
{
|
|
133
|
-
public PointF Position = new PointF(-2000, -2000);
|
|
134
|
-
public PointF Target = new PointF(-2000, -2000);
|
|
135
|
-
public DateTime Last = DateTime.MinValue;
|
|
136
|
-
public bool Live = false;
|
|
137
|
-
float pulse = 0f;
|
|
138
|
-
const int Box = 96;
|
|
139
|
-
|
|
140
|
-
public CursorForm()
|
|
141
|
-
{
|
|
142
|
-
FormBorderStyle = FormBorderStyle.None;
|
|
143
|
-
ShowInTaskbar = false;
|
|
144
|
-
TopMost = true;
|
|
145
|
-
StartPosition = FormStartPosition.Manual;
|
|
146
|
-
Size = new Size(Box, Box);
|
|
147
|
-
BackColor = Color.Black;
|
|
148
|
-
SetStyle(ControlStyles.Opaque, true);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
protected override bool ShowWithoutActivation { get { return true; } }
|
|
152
|
-
protected override void OnPaint(PaintEventArgs e) { }
|
|
153
|
-
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
154
|
-
|
|
155
|
-
protected override CreateParams CreateParams
|
|
156
|
-
{
|
|
157
|
-
get
|
|
158
|
-
{
|
|
159
|
-
CreateParams cp = base.CreateParams;
|
|
160
|
-
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
161
|
-
return cp;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public void Aim(float x, float y)
|
|
166
|
-
{
|
|
167
|
-
Target = new PointF(x, y);
|
|
168
|
-
if (!Live) { Position = Target; Live = true; }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
public void Tick(double idleMs)
|
|
172
|
-
{
|
|
173
|
-
Position = new PointF(
|
|
174
|
-
Position.X + (Target.X - Position.X) * 0.35f,
|
|
175
|
-
Position.Y + (Target.Y - Position.Y) * 0.35f);
|
|
176
|
-
pulse = (float)((Math.Sin(DateTime.UtcNow.Ticks / 900000.0) + 1.0) / 2.0);
|
|
177
|
-
double fade = 1.0; // stays visible for the whole overlay lifetime
|
|
178
|
-
Left = (int)Position.X - Box / 2;
|
|
179
|
-
Top = (int)Position.Y - Box / 2;
|
|
180
|
-
|
|
181
|
-
using (Bitmap surface = new Bitmap(Box, Box, PixelFormat.Format32bppArgb))
|
|
182
|
-
{
|
|
183
|
-
using (Graphics g = Graphics.FromImage(surface))
|
|
184
|
-
{
|
|
185
|
-
g.Clear(Color.Transparent);
|
|
186
|
-
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
187
|
-
int alpha = (int)(255 * fade);
|
|
188
|
-
float cx = Box / 2f, cy = Box / 2f;
|
|
189
|
-
|
|
190
|
-
// very soft halo so the pointer reads on busy backgrounds
|
|
191
|
-
using (GraphicsPath haloPath = new GraphicsPath())
|
|
192
|
-
{
|
|
193
|
-
haloPath.AddEllipse(cx - 26f, cy - 26f, 52f, 52f);
|
|
194
|
-
using (PathGradientBrush halo = new PathGradientBrush(haloPath))
|
|
195
|
-
{
|
|
196
|
-
halo.CenterColor = Color.FromArgb((int)(alpha * 0.20), Config.Accent);
|
|
197
|
-
halo.SurroundColors = new Color[] { Color.FromArgb(0, Config.Accent) };
|
|
198
|
-
g.FillPath(halo, haloPath);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// classic pointer, drawn with a dark outline and a soft drop shadow
|
|
203
|
-
PointF[] arrow = new PointF[] {
|
|
204
|
-
new PointF(cx - 9f, cy - 17f),
|
|
205
|
-
new PointF(cx - 9f, cy + 11f),
|
|
206
|
-
new PointF(cx - 3.5f, cy + 5.5f),
|
|
207
|
-
new PointF(cx + 0.5f, cy + 15f),
|
|
208
|
-
new PointF(cx + 5f, cy + 13f),
|
|
209
|
-
new PointF(cx + 1f, cy + 3.5f),
|
|
210
|
-
new PointF(cx + 8f, cy + 3.5f),
|
|
211
|
-
};
|
|
212
|
-
for (int pass = 3; pass >= 1; pass--)
|
|
213
|
-
{
|
|
214
|
-
using (SolidBrush shadow = new SolidBrush(Color.FromArgb((int)(alpha * 0.10), 0, 0, 0)))
|
|
215
|
-
using (Matrix m = new Matrix())
|
|
216
|
-
{
|
|
217
|
-
m.Translate(pass * 0.8f, pass * 0.9f);
|
|
218
|
-
GraphicsPath shifted = new GraphicsPath();
|
|
219
|
-
shifted.AddPolygon(arrow);
|
|
220
|
-
shifted.Transform(m);
|
|
221
|
-
g.FillPath(shadow, shifted);
|
|
222
|
-
shifted.Dispose();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
using (SolidBrush fill = new SolidBrush(Color.FromArgb(alpha, 255, 255, 255)))
|
|
226
|
-
g.FillPolygon(fill, arrow);
|
|
227
|
-
using (Pen outline = new Pen(Color.FromArgb((int)(alpha * 0.85), 24, 32, 48), 1.5f))
|
|
228
|
-
{
|
|
229
|
-
outline.LineJoin = LineJoin.Round;
|
|
230
|
-
g.DrawPolygon(outline, arrow);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (!Visible) Show();
|
|
234
|
-
Native.ApplyLayered(this, surface);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/// The status pill: auto-width glass chip with a status dot, the message, an Esc keycap
|
|
240
|
-
/// and a muted hint. It floats in the taskbar margin so it never covers app content.
|
|
241
|
-
public sealed class ChipForm : Form
|
|
242
|
-
{
|
|
243
|
-
const int PadX = 16;
|
|
244
|
-
const int Gap = 10;
|
|
245
|
-
const int Height0 = 36;
|
|
246
|
-
|
|
247
|
-
public ChipForm()
|
|
248
|
-
{
|
|
249
|
-
FormBorderStyle = FormBorderStyle.None;
|
|
250
|
-
ShowInTaskbar = false;
|
|
251
|
-
TopMost = true;
|
|
252
|
-
StartPosition = FormStartPosition.Manual;
|
|
253
|
-
Size = new Size(340, Height0);
|
|
254
|
-
BackColor = Color.Black;
|
|
255
|
-
SetStyle(ControlStyles.Opaque, true);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
protected override bool ShowWithoutActivation { get { return true; } }
|
|
259
|
-
protected override void OnPaint(PaintEventArgs e) { }
|
|
260
|
-
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
261
|
-
|
|
262
|
-
protected override CreateParams CreateParams
|
|
263
|
-
{
|
|
264
|
-
get
|
|
265
|
-
{
|
|
266
|
-
CreateParams cp = base.CreateParams;
|
|
267
|
-
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
268
|
-
return cp;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
public void Render(double phase)
|
|
273
|
-
{
|
|
274
|
-
using (Font strong = new Font("Microsoft YaHei UI", 12.5f, FontStyle.Regular, GraphicsUnit.Pixel))
|
|
275
|
-
using (Font weak = new Font("Microsoft YaHei UI", 11.5f, FontStyle.Regular, GraphicsUnit.Pixel))
|
|
276
|
-
using (Font key = new Font("Segoe UI", 10f, FontStyle.Regular, GraphicsUnit.Pixel))
|
|
277
|
-
{
|
|
278
|
-
const string message = "DSH 正在使用您的电脑";
|
|
279
|
-
const string tail = "取消";
|
|
280
|
-
SizeF mSize, tSize, kSize;
|
|
281
|
-
using (Bitmap probe = new Bitmap(1, 1))
|
|
282
|
-
using (Graphics pg = Graphics.FromImage(probe))
|
|
283
|
-
{
|
|
284
|
-
mSize = pg.MeasureString(message, strong);
|
|
285
|
-
tSize = pg.MeasureString(tail, weak);
|
|
286
|
-
kSize = pg.MeasureString("Esc", key);
|
|
287
|
-
}
|
|
288
|
-
int keyW = (int)kSize.Width + 12;
|
|
289
|
-
int keyH = 18;
|
|
290
|
-
int dotW = 14;
|
|
291
|
-
int width = PadX * 2 + dotW + Gap + (int)mSize.Width + Gap + keyW + 6 + (int)tSize.Width;
|
|
292
|
-
if (Width != width) Width = width;
|
|
293
|
-
|
|
294
|
-
using (Bitmap surface = new Bitmap(Width, Height0, PixelFormat.Format32bppArgb))
|
|
295
|
-
{
|
|
296
|
-
using (Graphics g = Graphics.FromImage(surface))
|
|
297
|
-
{
|
|
298
|
-
g.Clear(Color.Transparent);
|
|
299
|
-
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
300
|
-
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
|
301
|
-
|
|
302
|
-
RectangleF rect = new RectangleF(0.5f, 0.5f, Width - 1f, Height0 - 1f);
|
|
303
|
-
using (GraphicsPath path = Rounded(rect, Height0 / 2f))
|
|
304
|
-
using (SolidBrush body = new SolidBrush(Color.FromArgb(196, 11, 14, 22)))
|
|
305
|
-
using (Pen border = new Pen(Color.FromArgb(60, 255, 255, 255), 1f))
|
|
306
|
-
{
|
|
307
|
-
g.FillPath(body, path);
|
|
308
|
-
g.DrawPath(border, path);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
float x = PadX;
|
|
312
|
-
float midY = Height0 / 2f;
|
|
313
|
-
|
|
314
|
-
// status dot with a soft ring
|
|
315
|
-
int pulseA = 110 + (int)(110 * phase);
|
|
316
|
-
using (SolidBrush ring = new SolidBrush(Color.FromArgb((int)(pulseA * 0.30), Config.Accent)))
|
|
317
|
-
g.FillEllipse(ring, x - 4f, midY - 4f, 8f, 8f);
|
|
318
|
-
using (SolidBrush dot = new SolidBrush(Color.FromArgb(255, Config.Accent)))
|
|
319
|
-
g.FillEllipse(dot, x - 1.5f, midY - 1.5f, 3f, 3f);
|
|
320
|
-
x += dotW + Gap;
|
|
321
|
-
|
|
322
|
-
using (SolidBrush textStrong = new SolidBrush(Color.FromArgb(243, 241, 245, 252)))
|
|
323
|
-
g.DrawString(message, strong, textStrong, x, midY - mSize.Height / 2f + 1f);
|
|
324
|
-
x += mSize.Width + Gap;
|
|
325
|
-
|
|
326
|
-
// Esc keycap
|
|
327
|
-
RectangleF cap = new RectangleF(x, midY - keyH / 2f, keyW, keyH);
|
|
328
|
-
using (GraphicsPath capPath = Rounded(cap, 5f))
|
|
329
|
-
using (SolidBrush capFill = new SolidBrush(Color.FromArgb(46, 255, 255, 255)))
|
|
330
|
-
using (Pen capBorder = new Pen(Color.FromArgb(70, 255, 255, 255), 1f))
|
|
331
|
-
{
|
|
332
|
-
g.FillPath(capFill, capPath);
|
|
333
|
-
g.DrawPath(capBorder, capPath);
|
|
334
|
-
}
|
|
335
|
-
using (StringFormat center = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
|
|
336
|
-
using (SolidBrush capText = new SolidBrush(Color.FromArgb(225, 226, 232, 244)))
|
|
337
|
-
g.DrawString("Esc", key, capText, cap, center);
|
|
338
|
-
x += keyW + 6;
|
|
339
|
-
|
|
340
|
-
using (SolidBrush hint = new SolidBrush(Color.FromArgb(190, 170, 180, 200)))
|
|
341
|
-
g.DrawString(tail, weak, hint, x, midY - tSize.Height / 2f + 1f);
|
|
342
|
-
}
|
|
343
|
-
if (!Visible) Show();
|
|
344
|
-
Native.ApplyLayered(this, surface);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
static GraphicsPath Rounded(RectangleF r, float radius)
|
|
350
|
-
{
|
|
351
|
-
GraphicsPath path = new GraphicsPath();
|
|
352
|
-
float d = radius * 2f;
|
|
353
|
-
path.AddArc(r.X, r.Y, d, d, 180, 90);
|
|
354
|
-
path.AddArc(r.Right - d, r.Y, d, d, 270, 90);
|
|
355
|
-
path.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90);
|
|
356
|
-
path.AddArc(r.X, r.Bottom - d, d, d, 90, 90);
|
|
357
|
-
path.CloseFigure();
|
|
358
|
-
return path;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/// One edge of the frame: a soft outward glow plus a crisp 2px core line.
|
|
363
|
-
public sealed class StripForm : Form
|
|
364
|
-
{
|
|
365
|
-
public StripForm()
|
|
366
|
-
{
|
|
367
|
-
FormBorderStyle = FormBorderStyle.None;
|
|
368
|
-
ShowInTaskbar = false;
|
|
369
|
-
TopMost = true;
|
|
370
|
-
StartPosition = FormStartPosition.Manual;
|
|
371
|
-
BackColor = Color.Black;
|
|
372
|
-
SetStyle(ControlStyles.Opaque, true);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
protected override bool ShowWithoutActivation { get { return true; } }
|
|
376
|
-
protected override void OnPaint(PaintEventArgs e) { }
|
|
377
|
-
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
378
|
-
|
|
379
|
-
protected override CreateParams CreateParams
|
|
380
|
-
{
|
|
381
|
-
get
|
|
382
|
-
{
|
|
383
|
-
CreateParams cp = base.CreateParams;
|
|
384
|
-
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
385
|
-
return cp;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/// A crisp hairline with two faint halo strokes. Solid colours only: GDI+ gradient
|
|
390
|
-
/// brushes with transparent endpoints can composite as opaque black once the bitmap
|
|
391
|
-
/// goes through GetHbitmap/UpdateLayeredWindow.
|
|
392
|
-
public void RenderEdge(int w, int h, bool vertical, bool flip, int coreAlpha, int glowAlpha)
|
|
393
|
-
{
|
|
394
|
-
if (w <= 0 || h <= 0) return;
|
|
395
|
-
if (Width != w || Height != h) { Width = w; Height = h; }
|
|
396
|
-
using (Bitmap surface = new Bitmap(w, h, PixelFormat.Format32bppArgb))
|
|
397
|
-
{
|
|
398
|
-
using (Graphics g = Graphics.FromImage(surface))
|
|
399
|
-
{
|
|
400
|
-
g.Clear(Color.Transparent);
|
|
401
|
-
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
402
|
-
Color accent = Config.Accent;
|
|
403
|
-
|
|
404
|
-
// halo: widest + faintest first, then tighten
|
|
405
|
-
for (int i = 3; i >= 1; i--)
|
|
406
|
-
{
|
|
407
|
-
float width = i * 3.2f;
|
|
408
|
-
int alpha = Math.Max(6, glowAlpha / (i + 1));
|
|
409
|
-
using (Pen halo = new Pen(Color.FromArgb(alpha, accent), width))
|
|
410
|
-
{
|
|
411
|
-
halo.Alignment = PenAlignment.Center;
|
|
412
|
-
if (vertical)
|
|
413
|
-
{
|
|
414
|
-
float y = flip ? h - 1.5f : 1.5f;
|
|
415
|
-
g.DrawLine(halo, 0, y, w, y);
|
|
416
|
-
}
|
|
417
|
-
else
|
|
418
|
-
{
|
|
419
|
-
float x = flip ? w - 1.5f : 1.5f;
|
|
420
|
-
g.DrawLine(halo, x, 0, x, h);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// core hairline
|
|
426
|
-
using (Pen core = new Pen(Color.FromArgb(coreAlpha, accent), 2f))
|
|
427
|
-
{
|
|
428
|
-
core.Alignment = PenAlignment.Center;
|
|
429
|
-
if (vertical)
|
|
430
|
-
{
|
|
431
|
-
float y = flip ? h - 1.5f : 1.5f;
|
|
432
|
-
g.DrawLine(core, 0, y, w, y);
|
|
433
|
-
}
|
|
434
|
-
else
|
|
435
|
-
{
|
|
436
|
-
float x = flip ? w - 1.5f : 1.5f;
|
|
437
|
-
g.DrawLine(core, x, 0, x, h);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
if (!Visible) Show();
|
|
442
|
-
Native.ApplyLayered(this, surface);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/// One rounded corner: the glow and core follow a quarter arc so the frame reads as a
|
|
448
|
-
/// single rounded rectangle instead of four straight strips.
|
|
449
|
-
public sealed class CornerForm : Form
|
|
450
|
-
{
|
|
451
|
-
public CornerForm()
|
|
452
|
-
{
|
|
453
|
-
FormBorderStyle = FormBorderStyle.None;
|
|
454
|
-
ShowInTaskbar = false;
|
|
455
|
-
TopMost = true;
|
|
456
|
-
StartPosition = FormStartPosition.Manual;
|
|
457
|
-
BackColor = Color.Black;
|
|
458
|
-
SetStyle(ControlStyles.Opaque, true);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
protected override bool ShowWithoutActivation { get { return true; } }
|
|
462
|
-
protected override void OnPaint(PaintEventArgs e) { }
|
|
463
|
-
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
464
|
-
|
|
465
|
-
protected override CreateParams CreateParams
|
|
466
|
-
{
|
|
467
|
-
get
|
|
468
|
-
{
|
|
469
|
-
CreateParams cp = base.CreateParams;
|
|
470
|
-
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
471
|
-
return cp;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
public void RenderCorner(int size, int radius, int quadrant, int coreAlpha, int glowAlpha)
|
|
476
|
-
{
|
|
477
|
-
if (Width != size || Height != size) { Width = size; Height = size; }
|
|
478
|
-
using (Bitmap surface = new Bitmap(size, size, PixelFormat.Format32bppArgb))
|
|
479
|
-
{
|
|
480
|
-
using (Graphics g = Graphics.FromImage(surface))
|
|
481
|
-
{
|
|
482
|
-
g.Clear(Color.Transparent);
|
|
483
|
-
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
484
|
-
float r = radius;
|
|
485
|
-
float inset = 1.4f;
|
|
486
|
-
RectangleF box;
|
|
487
|
-
float startAngle;
|
|
488
|
-
switch (quadrant)
|
|
489
|
-
{
|
|
490
|
-
case 0: box = new RectangleF(inset, inset, r * 2, r * 2); startAngle = 180; break;
|
|
491
|
-
case 1: box = new RectangleF(size - r * 2 - inset, inset, r * 2, r * 2); startAngle = 270; break;
|
|
492
|
-
case 2: box = new RectangleF(size - r * 2 - inset, size - r * 2 - inset, r * 2, r * 2); startAngle = 0; break;
|
|
493
|
-
default: box = new RectangleF(inset, size - r * 2 - inset, r * 2, r * 2); startAngle = 90; break;
|
|
494
|
-
}
|
|
495
|
-
// tight glow: two soft arcs
|
|
496
|
-
using (Pen wide = new Pen(Color.FromArgb(Math.Max(6, glowAlpha / 2), Config.Accent), 9f)) { wide.Alignment = PenAlignment.Inset; g.DrawArc(wide, box, startAngle, 90); }
|
|
497
|
-
using (Pen narrow = new Pen(Color.FromArgb(Math.Max(8, glowAlpha), Config.Accent), 4f)) { narrow.Alignment = PenAlignment.Inset; g.DrawArc(narrow, box, startAngle, 90); }
|
|
498
|
-
using (Pen core = new Pen(Color.FromArgb(coreAlpha, Config.Accent), 1.6f)) { core.Alignment = PenAlignment.Inset; g.DrawArc(core, box, startAngle, 90); }
|
|
499
|
-
}
|
|
500
|
-
if (!Visible) Show();
|
|
501
|
-
Native.ApplyLayered(this, surface);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
internal sealed class Controller : ApplicationContext
|
|
507
|
-
{
|
|
508
|
-
readonly System.Windows.Forms.Timer _timer = new System.Windows.Forms.Timer();
|
|
509
|
-
readonly StripForm _top = new StripForm();
|
|
510
|
-
readonly StripForm _bottom = new StripForm();
|
|
511
|
-
readonly StripForm _left = new StripForm();
|
|
512
|
-
readonly StripForm _right = new StripForm();
|
|
513
|
-
readonly CornerForm _c0 = new CornerForm(), _c1 = new CornerForm(), _c2 = new CornerForm(), _c3 = new CornerForm();
|
|
514
|
-
readonly ChipForm _chip = new ChipForm();
|
|
515
|
-
readonly CursorForm _cursor = new CursorForm();
|
|
516
|
-
readonly int _ttlMs;
|
|
517
|
-
readonly DateTime _deadline;
|
|
518
|
-
int _tick;
|
|
519
|
-
LowLevelKeyboardProc _hookProc;
|
|
520
|
-
IntPtr _hook;
|
|
521
|
-
|
|
522
|
-
[DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
|
523
|
-
[DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
|
524
|
-
[DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
|
525
|
-
[DllImport("user32.dll")] static extern bool GetCursorPos(out PointNative pt);
|
|
526
|
-
[StructLayout(LayoutKind.Sequential)] struct PointNative { public int X, Y; }
|
|
527
|
-
delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
|
|
528
|
-
|
|
529
|
-
const int Radius = 18; // corner radius
|
|
530
|
-
const int Glow = 16; // band the halo fades inside
|
|
531
|
-
|
|
532
|
-
public Controller(int ttlMs)
|
|
533
|
-
{
|
|
534
|
-
_ttlMs = ttlMs;
|
|
535
|
-
_deadline = DateTime.UtcNow.AddMilliseconds(ttlMs);
|
|
536
|
-
_hookProc = Hook;
|
|
537
|
-
_hook = SetWindowsHookEx(13, _hookProc, IntPtr.Zero, 0);
|
|
538
|
-
_timer.Interval = 45;
|
|
539
|
-
_timer.Tick += (s, e) => Tick();
|
|
540
|
-
_timer.Start();
|
|
541
|
-
Tick();
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
IntPtr Hook(int nCode, IntPtr wParam, IntPtr lParam)
|
|
545
|
-
{
|
|
546
|
-
if (nCode >= 0 && (wParam == (IntPtr)0x0100 || wParam == (IntPtr)0x0104))
|
|
547
|
-
{
|
|
548
|
-
int vk = Marshal.ReadInt32(lParam);
|
|
549
|
-
if (vk == 0x1B)
|
|
550
|
-
{
|
|
551
|
-
try { if (Config.CancelFile.Length > 0) File.WriteAllText(Config.CancelFile, "cancelled " + DateTime.Now.ToString("o")); } catch { }
|
|
552
|
-
try { ExitThreadCore(); } catch { }
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return CallNextHookEx(_hook, nCode, wParam, lParam);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
void Tick()
|
|
559
|
-
{
|
|
560
|
-
_tick++;
|
|
561
|
-
if (Config.OwnerPid > 0)
|
|
562
|
-
{
|
|
563
|
-
try { if (System.Diagnostics.Process.GetProcessById(Config.OwnerPid).HasExited) { ExitThreadCore(); return; } }
|
|
564
|
-
catch { ExitThreadCore(); return; }
|
|
565
|
-
}
|
|
566
|
-
if (DateTime.UtcNow > _deadline) { ExitThreadCore(); return; }
|
|
567
|
-
|
|
568
|
-
Rectangle screen = SystemInformation.VirtualScreen;
|
|
569
|
-
// slow 3.2s breath: the frame gets a little brighter and a touch wider
|
|
570
|
-
double phase = (Math.Sin(_tick / 26.0 - 1.57) + 1.0) / 2.0;
|
|
571
|
-
int coreAlpha = 200 + (int)Math.Round(55 * phase); // clearly visible, still a hairline
|
|
572
|
-
int glowAlpha = 42 + (int)Math.Round(26 * phase);
|
|
573
|
-
int thickness = Glow;
|
|
574
|
-
|
|
575
|
-
int x0 = screen.Left, y0 = screen.Top, w = screen.Width, h = screen.Height;
|
|
576
|
-
|
|
577
|
-
_top.RenderEdge(w - Radius * 2, thickness, true, false, coreAlpha, glowAlpha);
|
|
578
|
-
_top.Location = new Point(x0 + Radius, y0);
|
|
579
|
-
_bottom.RenderEdge(w - Radius * 2, thickness, true, true, coreAlpha, glowAlpha);
|
|
580
|
-
_bottom.Location = new Point(x0 + Radius, y0 + h - thickness);
|
|
581
|
-
_left.RenderEdge(thickness, h - Radius * 2, false, false, coreAlpha, glowAlpha);
|
|
582
|
-
_left.Location = new Point(x0, y0 + Radius);
|
|
583
|
-
_right.RenderEdge(thickness, h - Radius * 2, false, true, coreAlpha, glowAlpha);
|
|
584
|
-
_right.Location = new Point(x0 + w - thickness, y0 + Radius);
|
|
585
|
-
|
|
586
|
-
int cs = Radius + Glow;
|
|
587
|
-
_c0.RenderCorner(cs, Radius, 0, coreAlpha, glowAlpha); _c0.Location = new Point(x0, y0);
|
|
588
|
-
_c1.RenderCorner(cs, Radius, 1, coreAlpha, glowAlpha); _c1.Location = new Point(x0 + w - cs, y0);
|
|
589
|
-
_c2.RenderCorner(cs, Radius, 2, coreAlpha, glowAlpha); _c2.Location = new Point(x0 + w - cs, y0 + h - cs);
|
|
590
|
-
_c3.RenderCorner(cs, Radius, 3, coreAlpha, glowAlpha); _c3.Location = new Point(x0, y0 + h - cs);
|
|
591
|
-
|
|
592
|
-
if (_tick % 25 == 0)
|
|
593
|
-
{
|
|
594
|
-
Native.Raise(_top); Native.Raise(_bottom); Native.Raise(_left); Native.Raise(_right);
|
|
595
|
-
Native.Raise(_c0); Native.Raise(_c1); Native.Raise(_c2); Native.Raise(_c3); Native.Raise(_chip);
|
|
596
|
-
Native.Raise(_cursor);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
_chip.Render(phase);
|
|
600
|
-
_chip.Location = new Point(x0 + (w - _chip.Width) / 2, y0 + h - 74);
|
|
601
|
-
|
|
602
|
-
// virtual cursor stands in for the real pointer while the overlay is up
|
|
603
|
-
try
|
|
604
|
-
{
|
|
605
|
-
if (Config.CursorFile.Length > 0 && File.Exists(Config.CursorFile))
|
|
606
|
-
{
|
|
607
|
-
string raw = File.ReadAllText(Config.CursorFile).Trim();
|
|
608
|
-
string[] parts = raw.Split(',');
|
|
609
|
-
if (parts.Length >= 3)
|
|
610
|
-
{
|
|
611
|
-
DateTime stamp = new DateTime(long.Parse(parts[2]));
|
|
612
|
-
if (stamp > _cursor.Last)
|
|
613
|
-
{
|
|
614
|
-
_cursor.Last = stamp;
|
|
615
|
-
_cursor.Aim(float.Parse(parts[0], System.Globalization.CultureInfo.InvariantCulture),
|
|
616
|
-
float.Parse(parts[1], System.Globalization.CultureInfo.InvariantCulture));
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
catch { }
|
|
622
|
-
|
|
623
|
-
// Windows restores the real cursors on resume / display change; put the blank
|
|
624
|
-
// ones back while the indicator is up (Codex: "schedule system cursor re-suppression").
|
|
625
|
-
if (Config.SuppressCursor && _tick % 40 == 0) Native.SuppressSystemCursors(true);
|
|
626
|
-
|
|
627
|
-
if (!_cursor.Live)
|
|
628
|
-
{
|
|
629
|
-
PointNative p;
|
|
630
|
-
if (GetCursorPos(out p)) { _cursor.Position = new PointF(p.X, p.Y); _cursor.Target = _cursor.Position; }
|
|
631
|
-
_cursor.Live = true;
|
|
632
|
-
}
|
|
633
|
-
_cursor.Tick(0);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
protected override void ExitThreadCore()
|
|
637
|
-
{
|
|
638
|
-
try { _timer.Stop(); } catch { }
|
|
639
|
-
try { if (_hook != IntPtr.Zero) UnhookWindowsHookEx(_hook); } catch { }
|
|
640
|
-
try
|
|
641
|
-
{
|
|
642
|
-
_top.Close(); _bottom.Close(); _left.Close(); _right.Close();
|
|
643
|
-
_c0.Close(); _c1.Close(); _c2.Close(); _c3.Close();
|
|
644
|
-
_chip.Close(); _cursor.Close();
|
|
645
|
-
}
|
|
646
|
-
catch { }
|
|
647
|
-
Native.SuppressSystemCursors(false);
|
|
648
|
-
base.ExitThreadCore();
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
internal static class Program
|
|
653
|
-
{
|
|
654
|
-
[DllImport("user32.dll")] private static extern bool SetProcessDpiAwarenessContext(IntPtr ctx);
|
|
655
|
-
[DllImport("user32.dll")] private static extern bool SetProcessDPIAware();
|
|
656
|
-
|
|
657
|
-
[STAThread]
|
|
658
|
-
private static void Main(string[] args)
|
|
659
|
-
{
|
|
660
|
-
// Per-monitor-v2: without this the process is DPI-virtualized, so a 1707x1067
|
|
661
|
-
// logical screen is scaled onto a 2560x1600 physical one - the frame gets blurry,
|
|
662
|
-
// sized wrong, and action coordinates land in the wrong place.
|
|
663
|
-
try { SetProcessDpiAwarenessContext(new IntPtr(-4)); } catch { }
|
|
664
|
-
try { SetProcessDPIAware(); } catch { }
|
|
665
|
-
|
|
666
|
-
// Guard mode: wait for the owner, then restore the cursors. This must run before
|
|
667
|
-
// anything else - a guard that reached the suppression block would hide the
|
|
668
|
-
// cursors again and spawn another guard, and the chain would never stop.
|
|
669
|
-
int guardIndex = Array.IndexOf(args, "--cursor-guard");
|
|
670
|
-
if (guardIndex >= 0 && args.Length > guardIndex + 1)
|
|
671
|
-
{
|
|
672
|
-
int ownerPid;
|
|
673
|
-
if (int.TryParse(args[guardIndex + 1], out ownerPid) && ownerPid != System.Diagnostics.Process.GetCurrentProcess().Id)
|
|
674
|
-
{
|
|
675
|
-
try
|
|
676
|
-
{
|
|
677
|
-
System.Diagnostics.Process owner = System.Diagnostics.Process.GetProcessById(ownerPid);
|
|
678
|
-
owner.WaitForExit(600000); // hard cap: a guard never lives forever
|
|
679
|
-
}
|
|
680
|
-
catch { /* owner already gone */ }
|
|
681
|
-
// Only clean up our own owner's suppression: a newer overlay may have
|
|
682
|
-
// taken over the cursors in the meantime, and restoring them here would
|
|
683
|
-
// make the real pointer reappear under a live indicator.
|
|
684
|
-
bool ours = false;
|
|
685
|
-
try
|
|
686
|
-
{
|
|
687
|
-
string flagPath = Native.CursorFlagPath();
|
|
688
|
-
ours = File.Exists(flagPath) && File.ReadAllText(flagPath).Trim() == ownerPid.ToString();
|
|
689
|
-
}
|
|
690
|
-
catch { }
|
|
691
|
-
if (ours) Native.SuppressSystemCursors(false);
|
|
692
|
-
}
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
int ttlMs = 20000;
|
|
697
|
-
for (int i = 0; i < args.Length - 1; i++)
|
|
698
|
-
{
|
|
699
|
-
string a = args[i];
|
|
700
|
-
if (a == "--cancel-file") Config.CancelFile = args[i + 1];
|
|
701
|
-
else if (a == "--cursor-file") Config.CursorFile = args[i + 1];
|
|
702
|
-
else if (a == "--accent") Config.AccentHex = args[i + 1];
|
|
703
|
-
else if (a == "--text") Config.Title = args[i + 1];
|
|
704
|
-
else if (a == "--hint") Config.Hint = args[i + 1];
|
|
705
|
-
else if (a == "--ttl-ms") int.TryParse(args[i + 1], out ttlMs);
|
|
706
|
-
else if (a == "--owner-pid") int.TryParse(args[i + 1], out Config.OwnerPid);
|
|
707
|
-
}
|
|
708
|
-
Config.SuppressCursor = true; // Codex default
|
|
709
|
-
for (int i = 0; i < args.Length; i++) if (args[i] == "--no-suppress-cursor") Config.SuppressCursor = false;
|
|
710
|
-
try { Config.Accent = ColorTranslator.FromHtml(Config.AccentHex); } catch { }
|
|
711
|
-
|
|
712
|
-
if (Config.SuppressCursor)
|
|
713
|
-
{
|
|
714
|
-
Native.SuppressSystemCursors(true);
|
|
715
|
-
AppDomain.CurrentDomain.ProcessExit += (s, e) => Native.SuppressSystemCursors(false);
|
|
716
|
-
try
|
|
717
|
-
{
|
|
718
|
-
System.Diagnostics.ProcessStartInfo guard = new System.Diagnostics.ProcessStartInfo(
|
|
719
|
-
System.Reflection.Assembly.GetExecutingAssembly().Location,
|
|
720
|
-
"--cursor-guard " + System.Diagnostics.Process.GetCurrentProcess().Id);
|
|
721
|
-
guard.UseShellExecute = false;
|
|
722
|
-
guard.CreateNoWindow = true;
|
|
723
|
-
System.Diagnostics.Process.Start(guard);
|
|
724
|
-
}
|
|
725
|
-
catch { /* the helper watchdog still covers us */ }
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
Application.EnableVisualStyles();
|
|
729
|
-
Application.Run(new Controller(ttlMs));
|
|
730
|
-
Native.SuppressSystemCursors(false);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
1
|
+
using System;
|
|
2
|
+
using System.Drawing;
|
|
3
|
+
using System.Drawing.Drawing2D;
|
|
4
|
+
using System.Drawing.Imaging;
|
|
5
|
+
using System.IO;
|
|
6
|
+
using System.Runtime.InteropServices;
|
|
7
|
+
using System.Windows.Forms;
|
|
8
|
+
|
|
9
|
+
// "DSH is using your computer" indicator, modelled on Codex's overlays:
|
|
10
|
+
// * Display Overlay - a breathing accent border around the whole virtual screen
|
|
11
|
+
// (Codex: "border size breath" / "border offset breath")
|
|
12
|
+
// * Cursor Overlay - a virtual cursor that glides to every pointer action, with
|
|
13
|
+
// optional suppression of the real system cursor
|
|
14
|
+
// * Esc - kill switch; writes a cancel file the helper checks before
|
|
15
|
+
// it injects anything
|
|
16
|
+
// Everything is layered, per-pixel alpha, click-through and never takes focus.
|
|
17
|
+
public sealed class Config
|
|
18
|
+
{
|
|
19
|
+
public static string CancelFile = "";
|
|
20
|
+
public static string CursorFile = "";
|
|
21
|
+
public static string AccentHex = "#3f7fd8";
|
|
22
|
+
public static string Title = "DSH 正在使用您的电脑";
|
|
23
|
+
public static string Hint = "Esc 取消";
|
|
24
|
+
public static bool SuppressCursor = false;
|
|
25
|
+
public static int OwnerPid = 0;
|
|
26
|
+
public static int Corner1 = 0; // unused placeholder kept for arg compatibility
|
|
27
|
+
public static Color Accent = Color.FromArgb(63, 127, 216);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static class Native
|
|
31
|
+
{
|
|
32
|
+
[StructLayout(LayoutKind.Sequential)] public struct POINT { public int X, Y; }
|
|
33
|
+
[StructLayout(LayoutKind.Sequential)] public struct SIZE { public int CX, CY; }
|
|
34
|
+
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
35
|
+
public struct BLENDFUNCTION { public byte BlendOp; public byte BlendFlags; public byte SourceConstantAlpha; public byte AlphaFormat; }
|
|
36
|
+
|
|
37
|
+
[DllImport("user32.dll")] public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref POINT pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pptSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags);
|
|
38
|
+
[DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hWnd);
|
|
39
|
+
[DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
|
40
|
+
[DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint flags);
|
|
41
|
+
[DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
|
|
42
|
+
[DllImport("gdi32.dll")] public static extern bool DeleteDC(IntPtr hdc);
|
|
43
|
+
[DllImport("gdi32.dll")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr h);
|
|
44
|
+
[DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr h);
|
|
45
|
+
[DllImport("user32.dll")] public static extern IntPtr SetSystemCursor(IntPtr hcur, uint id);
|
|
46
|
+
[DllImport("user32.dll")] public static extern bool SystemParametersInfo(uint action, uint param, IntPtr pv, uint winIni);
|
|
47
|
+
[DllImport("user32.dll")] public static extern IntPtr CreateCursor(IntPtr inst, int x, int y, int w, int h, byte[] andMask, byte[] xorMask);
|
|
48
|
+
[DllImport("user32.dll")] public static extern IntPtr CopyIcon(IntPtr hIcon);
|
|
49
|
+
[DllImport("user32.dll")] public static extern bool DestroyCursor(IntPtr hCursor);
|
|
50
|
+
|
|
51
|
+
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
|
52
|
+
public const uint SWP_NOSIZE = 0x0001, SWP_NOMOVE = 0x0002, SWP_NOACTIVATE = 0x0010, SWP_SHOWWINDOW = 0x0040;
|
|
53
|
+
public const int ULW_ALPHA = 2;
|
|
54
|
+
public const uint SPI_SETCURSORS = 0x0057;
|
|
55
|
+
|
|
56
|
+
public static void ApplyLayered(Form form, Bitmap surface)
|
|
57
|
+
{
|
|
58
|
+
IntPtr screenDc = GetDC(IntPtr.Zero);
|
|
59
|
+
IntPtr memDc = CreateCompatibleDC(screenDc);
|
|
60
|
+
IntPtr hBitmap = surface.GetHbitmap(Color.FromArgb(0));
|
|
61
|
+
IntPtr oldBitmap = SelectObject(memDc, hBitmap);
|
|
62
|
+
try
|
|
63
|
+
{
|
|
64
|
+
POINT dst = new POINT { X = form.Left, Y = form.Top };
|
|
65
|
+
POINT src = new POINT { X = 0, Y = 0 };
|
|
66
|
+
SIZE size = new SIZE { CX = surface.Width, CY = surface.Height };
|
|
67
|
+
BLENDFUNCTION blend = new BLENDFUNCTION { BlendOp = 0, BlendFlags = 0, SourceConstantAlpha = 255, AlphaFormat = 1 };
|
|
68
|
+
UpdateLayeredWindow(form.Handle, screenDc, ref dst, ref size, memDc, ref src, 0, ref blend, ULW_ALPHA);
|
|
69
|
+
}
|
|
70
|
+
finally
|
|
71
|
+
{
|
|
72
|
+
SelectObject(memDc, oldBitmap);
|
|
73
|
+
DeleteObject(hBitmap);
|
|
74
|
+
DeleteDC(memDc);
|
|
75
|
+
ReleaseDC(IntPtr.Zero, screenDc);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public static void Raise(Form form)
|
|
80
|
+
{
|
|
81
|
+
try { SetWindowPos(form.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW); } catch { }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Path of the "cursors are hidden" flag. It carries our PID so a long-lived
|
|
85
|
+
/// supervisor can tell "the overlay is still hiding them" from "the overlay died
|
|
86
|
+
/// and left the desktop without a pointer".
|
|
87
|
+
public static string CursorFlagPath()
|
|
88
|
+
{
|
|
89
|
+
string dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "dsh-computer-use");
|
|
90
|
+
return Path.Combine(dir, "cursor-hidden.flag");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static IntPtr _blankCursor;
|
|
94
|
+
|
|
95
|
+
public static void SuppressSystemCursors(bool suppress)
|
|
96
|
+
{
|
|
97
|
+
try
|
|
98
|
+
{
|
|
99
|
+
if (!suppress)
|
|
100
|
+
{
|
|
101
|
+
SystemParametersInfo(SPI_SETCURSORS, 0, IntPtr.Zero, 0);
|
|
102
|
+
try { File.Delete(CursorFlagPath()); } catch { }
|
|
103
|
+
if (_blankCursor != IntPtr.Zero) { try { DestroyCursor(_blankCursor); } catch { } _blankCursor = IntPtr.Zero; }
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Cache the blank cursor: Windows resets the cursors on resume, display
|
|
107
|
+
// change and some logins, so the controller calls this again periodically
|
|
108
|
+
// (Codex calls the same "system cursor re-suppression").
|
|
109
|
+
if (_blankCursor == IntPtr.Zero)
|
|
110
|
+
{
|
|
111
|
+
byte[] andMask = new byte[128];
|
|
112
|
+
byte[] xorMask = new byte[128];
|
|
113
|
+
for (int i = 0; i < andMask.Length; i++) andMask[i] = 0xFF;
|
|
114
|
+
_blankCursor = CreateCursor(IntPtr.Zero, 0, 0, 32, 32, andMask, xorMask);
|
|
115
|
+
}
|
|
116
|
+
IntPtr blank = _blankCursor;
|
|
117
|
+
if (blank == IntPtr.Zero) return;
|
|
118
|
+
uint[] ids = new uint[] { 32512, 32513, 32514, 32515, 32516, 32631, 32640, 32641, 32642, 32643, 32644, 32645, 32646, 32648, 32649, 32650, 32651, 32671 };
|
|
119
|
+
foreach (uint id in ids) SetSystemCursor(CopyIcon(blank), id);
|
|
120
|
+
try
|
|
121
|
+
{
|
|
122
|
+
Directory.CreateDirectory(Path.GetDirectoryName(CursorFlagPath()));
|
|
123
|
+
File.WriteAllText(CursorFlagPath(), System.Diagnostics.Process.GetCurrentProcess().Id.ToString());
|
|
124
|
+
}
|
|
125
|
+
catch { }
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public sealed class CursorForm : Form
|
|
132
|
+
{
|
|
133
|
+
public PointF Position = new PointF(-2000, -2000);
|
|
134
|
+
public PointF Target = new PointF(-2000, -2000);
|
|
135
|
+
public DateTime Last = DateTime.MinValue;
|
|
136
|
+
public bool Live = false;
|
|
137
|
+
float pulse = 0f;
|
|
138
|
+
const int Box = 96;
|
|
139
|
+
|
|
140
|
+
public CursorForm()
|
|
141
|
+
{
|
|
142
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
143
|
+
ShowInTaskbar = false;
|
|
144
|
+
TopMost = true;
|
|
145
|
+
StartPosition = FormStartPosition.Manual;
|
|
146
|
+
Size = new Size(Box, Box);
|
|
147
|
+
BackColor = Color.Black;
|
|
148
|
+
SetStyle(ControlStyles.Opaque, true);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
protected override bool ShowWithoutActivation { get { return true; } }
|
|
152
|
+
protected override void OnPaint(PaintEventArgs e) { }
|
|
153
|
+
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
154
|
+
|
|
155
|
+
protected override CreateParams CreateParams
|
|
156
|
+
{
|
|
157
|
+
get
|
|
158
|
+
{
|
|
159
|
+
CreateParams cp = base.CreateParams;
|
|
160
|
+
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
161
|
+
return cp;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public void Aim(float x, float y)
|
|
166
|
+
{
|
|
167
|
+
Target = new PointF(x, y);
|
|
168
|
+
if (!Live) { Position = Target; Live = true; }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public void Tick(double idleMs)
|
|
172
|
+
{
|
|
173
|
+
Position = new PointF(
|
|
174
|
+
Position.X + (Target.X - Position.X) * 0.35f,
|
|
175
|
+
Position.Y + (Target.Y - Position.Y) * 0.35f);
|
|
176
|
+
pulse = (float)((Math.Sin(DateTime.UtcNow.Ticks / 900000.0) + 1.0) / 2.0);
|
|
177
|
+
double fade = 1.0; // stays visible for the whole overlay lifetime
|
|
178
|
+
Left = (int)Position.X - Box / 2;
|
|
179
|
+
Top = (int)Position.Y - Box / 2;
|
|
180
|
+
|
|
181
|
+
using (Bitmap surface = new Bitmap(Box, Box, PixelFormat.Format32bppArgb))
|
|
182
|
+
{
|
|
183
|
+
using (Graphics g = Graphics.FromImage(surface))
|
|
184
|
+
{
|
|
185
|
+
g.Clear(Color.Transparent);
|
|
186
|
+
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
187
|
+
int alpha = (int)(255 * fade);
|
|
188
|
+
float cx = Box / 2f, cy = Box / 2f;
|
|
189
|
+
|
|
190
|
+
// very soft halo so the pointer reads on busy backgrounds
|
|
191
|
+
using (GraphicsPath haloPath = new GraphicsPath())
|
|
192
|
+
{
|
|
193
|
+
haloPath.AddEllipse(cx - 26f, cy - 26f, 52f, 52f);
|
|
194
|
+
using (PathGradientBrush halo = new PathGradientBrush(haloPath))
|
|
195
|
+
{
|
|
196
|
+
halo.CenterColor = Color.FromArgb((int)(alpha * 0.20), Config.Accent);
|
|
197
|
+
halo.SurroundColors = new Color[] { Color.FromArgb(0, Config.Accent) };
|
|
198
|
+
g.FillPath(halo, haloPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// classic pointer, drawn with a dark outline and a soft drop shadow
|
|
203
|
+
PointF[] arrow = new PointF[] {
|
|
204
|
+
new PointF(cx - 9f, cy - 17f),
|
|
205
|
+
new PointF(cx - 9f, cy + 11f),
|
|
206
|
+
new PointF(cx - 3.5f, cy + 5.5f),
|
|
207
|
+
new PointF(cx + 0.5f, cy + 15f),
|
|
208
|
+
new PointF(cx + 5f, cy + 13f),
|
|
209
|
+
new PointF(cx + 1f, cy + 3.5f),
|
|
210
|
+
new PointF(cx + 8f, cy + 3.5f),
|
|
211
|
+
};
|
|
212
|
+
for (int pass = 3; pass >= 1; pass--)
|
|
213
|
+
{
|
|
214
|
+
using (SolidBrush shadow = new SolidBrush(Color.FromArgb((int)(alpha * 0.10), 0, 0, 0)))
|
|
215
|
+
using (Matrix m = new Matrix())
|
|
216
|
+
{
|
|
217
|
+
m.Translate(pass * 0.8f, pass * 0.9f);
|
|
218
|
+
GraphicsPath shifted = new GraphicsPath();
|
|
219
|
+
shifted.AddPolygon(arrow);
|
|
220
|
+
shifted.Transform(m);
|
|
221
|
+
g.FillPath(shadow, shifted);
|
|
222
|
+
shifted.Dispose();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
using (SolidBrush fill = new SolidBrush(Color.FromArgb(alpha, 255, 255, 255)))
|
|
226
|
+
g.FillPolygon(fill, arrow);
|
|
227
|
+
using (Pen outline = new Pen(Color.FromArgb((int)(alpha * 0.85), 24, 32, 48), 1.5f))
|
|
228
|
+
{
|
|
229
|
+
outline.LineJoin = LineJoin.Round;
|
|
230
|
+
g.DrawPolygon(outline, arrow);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!Visible) Show();
|
|
234
|
+
Native.ApplyLayered(this, surface);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// The status pill: auto-width glass chip with a status dot, the message, an Esc keycap
|
|
240
|
+
/// and a muted hint. It floats in the taskbar margin so it never covers app content.
|
|
241
|
+
public sealed class ChipForm : Form
|
|
242
|
+
{
|
|
243
|
+
const int PadX = 16;
|
|
244
|
+
const int Gap = 10;
|
|
245
|
+
const int Height0 = 36;
|
|
246
|
+
|
|
247
|
+
public ChipForm()
|
|
248
|
+
{
|
|
249
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
250
|
+
ShowInTaskbar = false;
|
|
251
|
+
TopMost = true;
|
|
252
|
+
StartPosition = FormStartPosition.Manual;
|
|
253
|
+
Size = new Size(340, Height0);
|
|
254
|
+
BackColor = Color.Black;
|
|
255
|
+
SetStyle(ControlStyles.Opaque, true);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
protected override bool ShowWithoutActivation { get { return true; } }
|
|
259
|
+
protected override void OnPaint(PaintEventArgs e) { }
|
|
260
|
+
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
261
|
+
|
|
262
|
+
protected override CreateParams CreateParams
|
|
263
|
+
{
|
|
264
|
+
get
|
|
265
|
+
{
|
|
266
|
+
CreateParams cp = base.CreateParams;
|
|
267
|
+
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
268
|
+
return cp;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public void Render(double phase)
|
|
273
|
+
{
|
|
274
|
+
using (Font strong = new Font("Microsoft YaHei UI", 12.5f, FontStyle.Regular, GraphicsUnit.Pixel))
|
|
275
|
+
using (Font weak = new Font("Microsoft YaHei UI", 11.5f, FontStyle.Regular, GraphicsUnit.Pixel))
|
|
276
|
+
using (Font key = new Font("Segoe UI", 10f, FontStyle.Regular, GraphicsUnit.Pixel))
|
|
277
|
+
{
|
|
278
|
+
const string message = "DSH 正在使用您的电脑";
|
|
279
|
+
const string tail = "取消";
|
|
280
|
+
SizeF mSize, tSize, kSize;
|
|
281
|
+
using (Bitmap probe = new Bitmap(1, 1))
|
|
282
|
+
using (Graphics pg = Graphics.FromImage(probe))
|
|
283
|
+
{
|
|
284
|
+
mSize = pg.MeasureString(message, strong);
|
|
285
|
+
tSize = pg.MeasureString(tail, weak);
|
|
286
|
+
kSize = pg.MeasureString("Esc", key);
|
|
287
|
+
}
|
|
288
|
+
int keyW = (int)kSize.Width + 12;
|
|
289
|
+
int keyH = 18;
|
|
290
|
+
int dotW = 14;
|
|
291
|
+
int width = PadX * 2 + dotW + Gap + (int)mSize.Width + Gap + keyW + 6 + (int)tSize.Width;
|
|
292
|
+
if (Width != width) Width = width;
|
|
293
|
+
|
|
294
|
+
using (Bitmap surface = new Bitmap(Width, Height0, PixelFormat.Format32bppArgb))
|
|
295
|
+
{
|
|
296
|
+
using (Graphics g = Graphics.FromImage(surface))
|
|
297
|
+
{
|
|
298
|
+
g.Clear(Color.Transparent);
|
|
299
|
+
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
300
|
+
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
|
301
|
+
|
|
302
|
+
RectangleF rect = new RectangleF(0.5f, 0.5f, Width - 1f, Height0 - 1f);
|
|
303
|
+
using (GraphicsPath path = Rounded(rect, Height0 / 2f))
|
|
304
|
+
using (SolidBrush body = new SolidBrush(Color.FromArgb(196, 11, 14, 22)))
|
|
305
|
+
using (Pen border = new Pen(Color.FromArgb(60, 255, 255, 255), 1f))
|
|
306
|
+
{
|
|
307
|
+
g.FillPath(body, path);
|
|
308
|
+
g.DrawPath(border, path);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
float x = PadX;
|
|
312
|
+
float midY = Height0 / 2f;
|
|
313
|
+
|
|
314
|
+
// status dot with a soft ring
|
|
315
|
+
int pulseA = 110 + (int)(110 * phase);
|
|
316
|
+
using (SolidBrush ring = new SolidBrush(Color.FromArgb((int)(pulseA * 0.30), Config.Accent)))
|
|
317
|
+
g.FillEllipse(ring, x - 4f, midY - 4f, 8f, 8f);
|
|
318
|
+
using (SolidBrush dot = new SolidBrush(Color.FromArgb(255, Config.Accent)))
|
|
319
|
+
g.FillEllipse(dot, x - 1.5f, midY - 1.5f, 3f, 3f);
|
|
320
|
+
x += dotW + Gap;
|
|
321
|
+
|
|
322
|
+
using (SolidBrush textStrong = new SolidBrush(Color.FromArgb(243, 241, 245, 252)))
|
|
323
|
+
g.DrawString(message, strong, textStrong, x, midY - mSize.Height / 2f + 1f);
|
|
324
|
+
x += mSize.Width + Gap;
|
|
325
|
+
|
|
326
|
+
// Esc keycap
|
|
327
|
+
RectangleF cap = new RectangleF(x, midY - keyH / 2f, keyW, keyH);
|
|
328
|
+
using (GraphicsPath capPath = Rounded(cap, 5f))
|
|
329
|
+
using (SolidBrush capFill = new SolidBrush(Color.FromArgb(46, 255, 255, 255)))
|
|
330
|
+
using (Pen capBorder = new Pen(Color.FromArgb(70, 255, 255, 255), 1f))
|
|
331
|
+
{
|
|
332
|
+
g.FillPath(capFill, capPath);
|
|
333
|
+
g.DrawPath(capBorder, capPath);
|
|
334
|
+
}
|
|
335
|
+
using (StringFormat center = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
|
|
336
|
+
using (SolidBrush capText = new SolidBrush(Color.FromArgb(225, 226, 232, 244)))
|
|
337
|
+
g.DrawString("Esc", key, capText, cap, center);
|
|
338
|
+
x += keyW + 6;
|
|
339
|
+
|
|
340
|
+
using (SolidBrush hint = new SolidBrush(Color.FromArgb(190, 170, 180, 200)))
|
|
341
|
+
g.DrawString(tail, weak, hint, x, midY - tSize.Height / 2f + 1f);
|
|
342
|
+
}
|
|
343
|
+
if (!Visible) Show();
|
|
344
|
+
Native.ApplyLayered(this, surface);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static GraphicsPath Rounded(RectangleF r, float radius)
|
|
350
|
+
{
|
|
351
|
+
GraphicsPath path = new GraphicsPath();
|
|
352
|
+
float d = radius * 2f;
|
|
353
|
+
path.AddArc(r.X, r.Y, d, d, 180, 90);
|
|
354
|
+
path.AddArc(r.Right - d, r.Y, d, d, 270, 90);
|
|
355
|
+
path.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90);
|
|
356
|
+
path.AddArc(r.X, r.Bottom - d, d, d, 90, 90);
|
|
357
|
+
path.CloseFigure();
|
|
358
|
+
return path;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/// One edge of the frame: a soft outward glow plus a crisp 2px core line.
|
|
363
|
+
public sealed class StripForm : Form
|
|
364
|
+
{
|
|
365
|
+
public StripForm()
|
|
366
|
+
{
|
|
367
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
368
|
+
ShowInTaskbar = false;
|
|
369
|
+
TopMost = true;
|
|
370
|
+
StartPosition = FormStartPosition.Manual;
|
|
371
|
+
BackColor = Color.Black;
|
|
372
|
+
SetStyle(ControlStyles.Opaque, true);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
protected override bool ShowWithoutActivation { get { return true; } }
|
|
376
|
+
protected override void OnPaint(PaintEventArgs e) { }
|
|
377
|
+
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
378
|
+
|
|
379
|
+
protected override CreateParams CreateParams
|
|
380
|
+
{
|
|
381
|
+
get
|
|
382
|
+
{
|
|
383
|
+
CreateParams cp = base.CreateParams;
|
|
384
|
+
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
385
|
+
return cp;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// A crisp hairline with two faint halo strokes. Solid colours only: GDI+ gradient
|
|
390
|
+
/// brushes with transparent endpoints can composite as opaque black once the bitmap
|
|
391
|
+
/// goes through GetHbitmap/UpdateLayeredWindow.
|
|
392
|
+
public void RenderEdge(int w, int h, bool vertical, bool flip, int coreAlpha, int glowAlpha)
|
|
393
|
+
{
|
|
394
|
+
if (w <= 0 || h <= 0) return;
|
|
395
|
+
if (Width != w || Height != h) { Width = w; Height = h; }
|
|
396
|
+
using (Bitmap surface = new Bitmap(w, h, PixelFormat.Format32bppArgb))
|
|
397
|
+
{
|
|
398
|
+
using (Graphics g = Graphics.FromImage(surface))
|
|
399
|
+
{
|
|
400
|
+
g.Clear(Color.Transparent);
|
|
401
|
+
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
402
|
+
Color accent = Config.Accent;
|
|
403
|
+
|
|
404
|
+
// halo: widest + faintest first, then tighten
|
|
405
|
+
for (int i = 3; i >= 1; i--)
|
|
406
|
+
{
|
|
407
|
+
float width = i * 3.2f;
|
|
408
|
+
int alpha = Math.Max(6, glowAlpha / (i + 1));
|
|
409
|
+
using (Pen halo = new Pen(Color.FromArgb(alpha, accent), width))
|
|
410
|
+
{
|
|
411
|
+
halo.Alignment = PenAlignment.Center;
|
|
412
|
+
if (vertical)
|
|
413
|
+
{
|
|
414
|
+
float y = flip ? h - 1.5f : 1.5f;
|
|
415
|
+
g.DrawLine(halo, 0, y, w, y);
|
|
416
|
+
}
|
|
417
|
+
else
|
|
418
|
+
{
|
|
419
|
+
float x = flip ? w - 1.5f : 1.5f;
|
|
420
|
+
g.DrawLine(halo, x, 0, x, h);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// core hairline
|
|
426
|
+
using (Pen core = new Pen(Color.FromArgb(coreAlpha, accent), 2f))
|
|
427
|
+
{
|
|
428
|
+
core.Alignment = PenAlignment.Center;
|
|
429
|
+
if (vertical)
|
|
430
|
+
{
|
|
431
|
+
float y = flip ? h - 1.5f : 1.5f;
|
|
432
|
+
g.DrawLine(core, 0, y, w, y);
|
|
433
|
+
}
|
|
434
|
+
else
|
|
435
|
+
{
|
|
436
|
+
float x = flip ? w - 1.5f : 1.5f;
|
|
437
|
+
g.DrawLine(core, x, 0, x, h);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (!Visible) Show();
|
|
442
|
+
Native.ApplyLayered(this, surface);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/// One rounded corner: the glow and core follow a quarter arc so the frame reads as a
|
|
448
|
+
/// single rounded rectangle instead of four straight strips.
|
|
449
|
+
public sealed class CornerForm : Form
|
|
450
|
+
{
|
|
451
|
+
public CornerForm()
|
|
452
|
+
{
|
|
453
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
454
|
+
ShowInTaskbar = false;
|
|
455
|
+
TopMost = true;
|
|
456
|
+
StartPosition = FormStartPosition.Manual;
|
|
457
|
+
BackColor = Color.Black;
|
|
458
|
+
SetStyle(ControlStyles.Opaque, true);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
protected override bool ShowWithoutActivation { get { return true; } }
|
|
462
|
+
protected override void OnPaint(PaintEventArgs e) { }
|
|
463
|
+
protected override void OnPaintBackground(PaintEventArgs e) { }
|
|
464
|
+
|
|
465
|
+
protected override CreateParams CreateParams
|
|
466
|
+
{
|
|
467
|
+
get
|
|
468
|
+
{
|
|
469
|
+
CreateParams cp = base.CreateParams;
|
|
470
|
+
cp.ExStyle |= 0x00080000 | 0x00000020 | 0x00000080 | 0x08000000 | 0x00000008;
|
|
471
|
+
return cp;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
public void RenderCorner(int size, int radius, int quadrant, int coreAlpha, int glowAlpha)
|
|
476
|
+
{
|
|
477
|
+
if (Width != size || Height != size) { Width = size; Height = size; }
|
|
478
|
+
using (Bitmap surface = new Bitmap(size, size, PixelFormat.Format32bppArgb))
|
|
479
|
+
{
|
|
480
|
+
using (Graphics g = Graphics.FromImage(surface))
|
|
481
|
+
{
|
|
482
|
+
g.Clear(Color.Transparent);
|
|
483
|
+
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
484
|
+
float r = radius;
|
|
485
|
+
float inset = 1.4f;
|
|
486
|
+
RectangleF box;
|
|
487
|
+
float startAngle;
|
|
488
|
+
switch (quadrant)
|
|
489
|
+
{
|
|
490
|
+
case 0: box = new RectangleF(inset, inset, r * 2, r * 2); startAngle = 180; break;
|
|
491
|
+
case 1: box = new RectangleF(size - r * 2 - inset, inset, r * 2, r * 2); startAngle = 270; break;
|
|
492
|
+
case 2: box = new RectangleF(size - r * 2 - inset, size - r * 2 - inset, r * 2, r * 2); startAngle = 0; break;
|
|
493
|
+
default: box = new RectangleF(inset, size - r * 2 - inset, r * 2, r * 2); startAngle = 90; break;
|
|
494
|
+
}
|
|
495
|
+
// tight glow: two soft arcs
|
|
496
|
+
using (Pen wide = new Pen(Color.FromArgb(Math.Max(6, glowAlpha / 2), Config.Accent), 9f)) { wide.Alignment = PenAlignment.Inset; g.DrawArc(wide, box, startAngle, 90); }
|
|
497
|
+
using (Pen narrow = new Pen(Color.FromArgb(Math.Max(8, glowAlpha), Config.Accent), 4f)) { narrow.Alignment = PenAlignment.Inset; g.DrawArc(narrow, box, startAngle, 90); }
|
|
498
|
+
using (Pen core = new Pen(Color.FromArgb(coreAlpha, Config.Accent), 1.6f)) { core.Alignment = PenAlignment.Inset; g.DrawArc(core, box, startAngle, 90); }
|
|
499
|
+
}
|
|
500
|
+
if (!Visible) Show();
|
|
501
|
+
Native.ApplyLayered(this, surface);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
internal sealed class Controller : ApplicationContext
|
|
507
|
+
{
|
|
508
|
+
readonly System.Windows.Forms.Timer _timer = new System.Windows.Forms.Timer();
|
|
509
|
+
readonly StripForm _top = new StripForm();
|
|
510
|
+
readonly StripForm _bottom = new StripForm();
|
|
511
|
+
readonly StripForm _left = new StripForm();
|
|
512
|
+
readonly StripForm _right = new StripForm();
|
|
513
|
+
readonly CornerForm _c0 = new CornerForm(), _c1 = new CornerForm(), _c2 = new CornerForm(), _c3 = new CornerForm();
|
|
514
|
+
readonly ChipForm _chip = new ChipForm();
|
|
515
|
+
readonly CursorForm _cursor = new CursorForm();
|
|
516
|
+
readonly int _ttlMs;
|
|
517
|
+
readonly DateTime _deadline;
|
|
518
|
+
int _tick;
|
|
519
|
+
LowLevelKeyboardProc _hookProc;
|
|
520
|
+
IntPtr _hook;
|
|
521
|
+
|
|
522
|
+
[DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
|
523
|
+
[DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
|
524
|
+
[DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
|
525
|
+
[DllImport("user32.dll")] static extern bool GetCursorPos(out PointNative pt);
|
|
526
|
+
[StructLayout(LayoutKind.Sequential)] struct PointNative { public int X, Y; }
|
|
527
|
+
delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
|
|
528
|
+
|
|
529
|
+
const int Radius = 18; // corner radius
|
|
530
|
+
const int Glow = 16; // band the halo fades inside
|
|
531
|
+
|
|
532
|
+
public Controller(int ttlMs)
|
|
533
|
+
{
|
|
534
|
+
_ttlMs = ttlMs;
|
|
535
|
+
_deadline = DateTime.UtcNow.AddMilliseconds(ttlMs);
|
|
536
|
+
_hookProc = Hook;
|
|
537
|
+
_hook = SetWindowsHookEx(13, _hookProc, IntPtr.Zero, 0);
|
|
538
|
+
_timer.Interval = 45;
|
|
539
|
+
_timer.Tick += (s, e) => Tick();
|
|
540
|
+
_timer.Start();
|
|
541
|
+
Tick();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
IntPtr Hook(int nCode, IntPtr wParam, IntPtr lParam)
|
|
545
|
+
{
|
|
546
|
+
if (nCode >= 0 && (wParam == (IntPtr)0x0100 || wParam == (IntPtr)0x0104))
|
|
547
|
+
{
|
|
548
|
+
int vk = Marshal.ReadInt32(lParam);
|
|
549
|
+
if (vk == 0x1B)
|
|
550
|
+
{
|
|
551
|
+
try { if (Config.CancelFile.Length > 0) File.WriteAllText(Config.CancelFile, "cancelled " + DateTime.Now.ToString("o")); } catch { }
|
|
552
|
+
try { ExitThreadCore(); } catch { }
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return CallNextHookEx(_hook, nCode, wParam, lParam);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
void Tick()
|
|
559
|
+
{
|
|
560
|
+
_tick++;
|
|
561
|
+
if (Config.OwnerPid > 0)
|
|
562
|
+
{
|
|
563
|
+
try { if (System.Diagnostics.Process.GetProcessById(Config.OwnerPid).HasExited) { ExitThreadCore(); return; } }
|
|
564
|
+
catch { ExitThreadCore(); return; }
|
|
565
|
+
}
|
|
566
|
+
if (DateTime.UtcNow > _deadline) { ExitThreadCore(); return; }
|
|
567
|
+
|
|
568
|
+
Rectangle screen = SystemInformation.VirtualScreen;
|
|
569
|
+
// slow 3.2s breath: the frame gets a little brighter and a touch wider
|
|
570
|
+
double phase = (Math.Sin(_tick / 26.0 - 1.57) + 1.0) / 2.0;
|
|
571
|
+
int coreAlpha = 200 + (int)Math.Round(55 * phase); // clearly visible, still a hairline
|
|
572
|
+
int glowAlpha = 42 + (int)Math.Round(26 * phase);
|
|
573
|
+
int thickness = Glow;
|
|
574
|
+
|
|
575
|
+
int x0 = screen.Left, y0 = screen.Top, w = screen.Width, h = screen.Height;
|
|
576
|
+
|
|
577
|
+
_top.RenderEdge(w - Radius * 2, thickness, true, false, coreAlpha, glowAlpha);
|
|
578
|
+
_top.Location = new Point(x0 + Radius, y0);
|
|
579
|
+
_bottom.RenderEdge(w - Radius * 2, thickness, true, true, coreAlpha, glowAlpha);
|
|
580
|
+
_bottom.Location = new Point(x0 + Radius, y0 + h - thickness);
|
|
581
|
+
_left.RenderEdge(thickness, h - Radius * 2, false, false, coreAlpha, glowAlpha);
|
|
582
|
+
_left.Location = new Point(x0, y0 + Radius);
|
|
583
|
+
_right.RenderEdge(thickness, h - Radius * 2, false, true, coreAlpha, glowAlpha);
|
|
584
|
+
_right.Location = new Point(x0 + w - thickness, y0 + Radius);
|
|
585
|
+
|
|
586
|
+
int cs = Radius + Glow;
|
|
587
|
+
_c0.RenderCorner(cs, Radius, 0, coreAlpha, glowAlpha); _c0.Location = new Point(x0, y0);
|
|
588
|
+
_c1.RenderCorner(cs, Radius, 1, coreAlpha, glowAlpha); _c1.Location = new Point(x0 + w - cs, y0);
|
|
589
|
+
_c2.RenderCorner(cs, Radius, 2, coreAlpha, glowAlpha); _c2.Location = new Point(x0 + w - cs, y0 + h - cs);
|
|
590
|
+
_c3.RenderCorner(cs, Radius, 3, coreAlpha, glowAlpha); _c3.Location = new Point(x0, y0 + h - cs);
|
|
591
|
+
|
|
592
|
+
if (_tick % 25 == 0)
|
|
593
|
+
{
|
|
594
|
+
Native.Raise(_top); Native.Raise(_bottom); Native.Raise(_left); Native.Raise(_right);
|
|
595
|
+
Native.Raise(_c0); Native.Raise(_c1); Native.Raise(_c2); Native.Raise(_c3); Native.Raise(_chip);
|
|
596
|
+
Native.Raise(_cursor);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
_chip.Render(phase);
|
|
600
|
+
_chip.Location = new Point(x0 + (w - _chip.Width) / 2, y0 + h - 74);
|
|
601
|
+
|
|
602
|
+
// virtual cursor stands in for the real pointer while the overlay is up
|
|
603
|
+
try
|
|
604
|
+
{
|
|
605
|
+
if (Config.CursorFile.Length > 0 && File.Exists(Config.CursorFile))
|
|
606
|
+
{
|
|
607
|
+
string raw = File.ReadAllText(Config.CursorFile).Trim();
|
|
608
|
+
string[] parts = raw.Split(',');
|
|
609
|
+
if (parts.Length >= 3)
|
|
610
|
+
{
|
|
611
|
+
DateTime stamp = new DateTime(long.Parse(parts[2]));
|
|
612
|
+
if (stamp > _cursor.Last)
|
|
613
|
+
{
|
|
614
|
+
_cursor.Last = stamp;
|
|
615
|
+
_cursor.Aim(float.Parse(parts[0], System.Globalization.CultureInfo.InvariantCulture),
|
|
616
|
+
float.Parse(parts[1], System.Globalization.CultureInfo.InvariantCulture));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
catch { }
|
|
622
|
+
|
|
623
|
+
// Windows restores the real cursors on resume / display change; put the blank
|
|
624
|
+
// ones back while the indicator is up (Codex: "schedule system cursor re-suppression").
|
|
625
|
+
if (Config.SuppressCursor && _tick % 40 == 0) Native.SuppressSystemCursors(true);
|
|
626
|
+
|
|
627
|
+
if (!_cursor.Live)
|
|
628
|
+
{
|
|
629
|
+
PointNative p;
|
|
630
|
+
if (GetCursorPos(out p)) { _cursor.Position = new PointF(p.X, p.Y); _cursor.Target = _cursor.Position; }
|
|
631
|
+
_cursor.Live = true;
|
|
632
|
+
}
|
|
633
|
+
_cursor.Tick(0);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
protected override void ExitThreadCore()
|
|
637
|
+
{
|
|
638
|
+
try { _timer.Stop(); } catch { }
|
|
639
|
+
try { if (_hook != IntPtr.Zero) UnhookWindowsHookEx(_hook); } catch { }
|
|
640
|
+
try
|
|
641
|
+
{
|
|
642
|
+
_top.Close(); _bottom.Close(); _left.Close(); _right.Close();
|
|
643
|
+
_c0.Close(); _c1.Close(); _c2.Close(); _c3.Close();
|
|
644
|
+
_chip.Close(); _cursor.Close();
|
|
645
|
+
}
|
|
646
|
+
catch { }
|
|
647
|
+
Native.SuppressSystemCursors(false);
|
|
648
|
+
base.ExitThreadCore();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
internal static class Program
|
|
653
|
+
{
|
|
654
|
+
[DllImport("user32.dll")] private static extern bool SetProcessDpiAwarenessContext(IntPtr ctx);
|
|
655
|
+
[DllImport("user32.dll")] private static extern bool SetProcessDPIAware();
|
|
656
|
+
|
|
657
|
+
[STAThread]
|
|
658
|
+
private static void Main(string[] args)
|
|
659
|
+
{
|
|
660
|
+
// Per-monitor-v2: without this the process is DPI-virtualized, so a 1707x1067
|
|
661
|
+
// logical screen is scaled onto a 2560x1600 physical one - the frame gets blurry,
|
|
662
|
+
// sized wrong, and action coordinates land in the wrong place.
|
|
663
|
+
try { SetProcessDpiAwarenessContext(new IntPtr(-4)); } catch { }
|
|
664
|
+
try { SetProcessDPIAware(); } catch { }
|
|
665
|
+
|
|
666
|
+
// Guard mode: wait for the owner, then restore the cursors. This must run before
|
|
667
|
+
// anything else - a guard that reached the suppression block would hide the
|
|
668
|
+
// cursors again and spawn another guard, and the chain would never stop.
|
|
669
|
+
int guardIndex = Array.IndexOf(args, "--cursor-guard");
|
|
670
|
+
if (guardIndex >= 0 && args.Length > guardIndex + 1)
|
|
671
|
+
{
|
|
672
|
+
int ownerPid;
|
|
673
|
+
if (int.TryParse(args[guardIndex + 1], out ownerPid) && ownerPid != System.Diagnostics.Process.GetCurrentProcess().Id)
|
|
674
|
+
{
|
|
675
|
+
try
|
|
676
|
+
{
|
|
677
|
+
System.Diagnostics.Process owner = System.Diagnostics.Process.GetProcessById(ownerPid);
|
|
678
|
+
owner.WaitForExit(600000); // hard cap: a guard never lives forever
|
|
679
|
+
}
|
|
680
|
+
catch { /* owner already gone */ }
|
|
681
|
+
// Only clean up our own owner's suppression: a newer overlay may have
|
|
682
|
+
// taken over the cursors in the meantime, and restoring them here would
|
|
683
|
+
// make the real pointer reappear under a live indicator.
|
|
684
|
+
bool ours = false;
|
|
685
|
+
try
|
|
686
|
+
{
|
|
687
|
+
string flagPath = Native.CursorFlagPath();
|
|
688
|
+
ours = File.Exists(flagPath) && File.ReadAllText(flagPath).Trim() == ownerPid.ToString();
|
|
689
|
+
}
|
|
690
|
+
catch { }
|
|
691
|
+
if (ours) Native.SuppressSystemCursors(false);
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
int ttlMs = 20000;
|
|
697
|
+
for (int i = 0; i < args.Length - 1; i++)
|
|
698
|
+
{
|
|
699
|
+
string a = args[i];
|
|
700
|
+
if (a == "--cancel-file") Config.CancelFile = args[i + 1];
|
|
701
|
+
else if (a == "--cursor-file") Config.CursorFile = args[i + 1];
|
|
702
|
+
else if (a == "--accent") Config.AccentHex = args[i + 1];
|
|
703
|
+
else if (a == "--text") Config.Title = args[i + 1];
|
|
704
|
+
else if (a == "--hint") Config.Hint = args[i + 1];
|
|
705
|
+
else if (a == "--ttl-ms") int.TryParse(args[i + 1], out ttlMs);
|
|
706
|
+
else if (a == "--owner-pid") int.TryParse(args[i + 1], out Config.OwnerPid);
|
|
707
|
+
}
|
|
708
|
+
Config.SuppressCursor = true; // Codex default
|
|
709
|
+
for (int i = 0; i < args.Length; i++) if (args[i] == "--no-suppress-cursor") Config.SuppressCursor = false;
|
|
710
|
+
try { Config.Accent = ColorTranslator.FromHtml(Config.AccentHex); } catch { }
|
|
711
|
+
|
|
712
|
+
if (Config.SuppressCursor)
|
|
713
|
+
{
|
|
714
|
+
Native.SuppressSystemCursors(true);
|
|
715
|
+
AppDomain.CurrentDomain.ProcessExit += (s, e) => Native.SuppressSystemCursors(false);
|
|
716
|
+
try
|
|
717
|
+
{
|
|
718
|
+
System.Diagnostics.ProcessStartInfo guard = new System.Diagnostics.ProcessStartInfo(
|
|
719
|
+
System.Reflection.Assembly.GetExecutingAssembly().Location,
|
|
720
|
+
"--cursor-guard " + System.Diagnostics.Process.GetCurrentProcess().Id);
|
|
721
|
+
guard.UseShellExecute = false;
|
|
722
|
+
guard.CreateNoWindow = true;
|
|
723
|
+
System.Diagnostics.Process.Start(guard);
|
|
724
|
+
}
|
|
725
|
+
catch { /* the helper watchdog still covers us */ }
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
Application.EnableVisualStyles();
|
|
729
|
+
Application.Run(new Controller(ttlMs));
|
|
730
|
+
Native.SuppressSystemCursors(false);
|
|
731
|
+
}
|
|
732
|
+
}
|