-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmask.go
More file actions
121 lines (107 loc) · 3.15 KB
/
Copy pathmask.go
File metadata and controls
121 lines (107 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 didvc
//go:build windows
package main
// Mask is one overlay rectangle. Coordinates are in real desktop pixels
// (virtual-screen space, so they may be negative on multi-monitor setups).
type Mask struct {
X, Y, W, H int32
Opacity byte // 0..255 layered alpha
Passthrough bool // clicks fall through to the desktop behind
Color COLORREF
hwnd HWND
}
func (m *Mask) rect() RECT {
return RECT{Left: m.X, Top: m.Y, Right: m.X + m.W, Bottom: m.Y + m.H}
}
func (m *Mask) contains(x, y int32) bool {
return x >= m.X && x < m.X+m.W && y >= m.Y && y < m.Y+m.H
}
// normalize keeps width/height non-negative after a resize drag.
func (m *Mask) normalize() {
if m.W < 0 {
m.X += m.W
m.W = -m.W
}
if m.H < 0 {
m.Y += m.H
m.H = -m.H
}
}
const overlayClassName = "ScreenMaskOverlay"
// createOverlay builds the layered top-most window that actually covers the
// desktop for this mask, then syncs its position/opacity/passthrough.
func (a *App) createOverlay(m *Mask) {
exStyle := uint32(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE)
if m.Passthrough {
exStyle |= WS_EX_TRANSPARENT
}
hwnd := CreateWindowEx(
exStyle,
mustUTF16(overlayClassName),
mustUTF16(""),
WS_POPUP,
m.X, m.Y, m.W, m.H,
0, 0, a.hInstance,
)
m.hwnd = hwnd
a.applyOverlayColor(m)
SetLayeredWindowAttributes(hwnd, 0, m.Opacity, LWA_ALPHA)
SetWindowPos(hwnd, HWND_TOPMOST, m.X, m.Y, m.W, m.H, SWP_NOACTIVATE|SWP_SHOWWINDOW)
ShowWindow(hwnd, SW_SHOWNA)
}
// applyOverlayColor repaints the overlay client area with the mask color by
// setting a solid background brush stored in the window's user data slot and
// forcing a redraw. We paint on demand in the overlay WndProc instead.
func (a *App) applyOverlayColor(m *Mask) {
InvalidateRect(m.hwnd, true)
}
// syncOverlay pushes the mask's geometry/opacity/passthrough to its window.
func (a *App) syncOverlay(m *Mask) {
if m.hwnd == 0 {
return
}
SetWindowPos(m.hwnd, HWND_TOPMOST, m.X, m.Y, m.W, m.H, SWP_NOACTIVATE|SWP_SHOWWINDOW)
SetLayeredWindowAttributes(m.hwnd, 0, m.Opacity, LWA_ALPHA)
ex := GetWindowLongPtr(m.hwnd, GWL_EXSTYLE)
if m.Passthrough {
ex |= WS_EX_TRANSPARENT
} else {
ex &^= WS_EX_TRANSPARENT
}
SetWindowLongPtr(m.hwnd, GWL_EXSTYLE, ex)
InvalidateRect(m.hwnd, true)
}
func (a *App) destroyOverlay(m *Mask) {
if m.hwnd != 0 {
DestroyWindow(m.hwnd)
m.hwnd = 0
}
}
// overlayWndProc paints the mask color and otherwise defers to Windows.
func overlayWndProc(hwnd HWND, msg uint32, wp WPARAM, lp LPARAM) LRESULT {
switch msg {
case WM_ERASEBKGND:
return 1 // handled in WM_PAINT, avoid flicker
case WM_PAINT:
var ps PAINTSTRUCT
dc := BeginPaint(hwnd, &ps)
rc := GetClientRect(hwnd)
color := app.overlayColorFor(hwnd)
brush := CreateSolidBrush(color)
FillRectC(dc, &rc, brush)
DeleteObject(brush)
EndPaint(hwnd, &ps)
return 0
}
return DefWindowProc(hwnd, msg, wp, lp)
}
// overlayColorFor finds the mask owning this overlay window to know its color.
func (a *App) overlayColorFor(hwnd HWND) COLORREF {
for _, m := range a.masks {
if m.hwnd == hwnd {
return m.Color
}
}
return RGB(0, 0, 0)
}