/
window.go
253 lines (214 loc) · 6.77 KB
/
window.go
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//go:build windows
package w32
import (
"fmt"
"github.com/samber/lo"
"log"
"strconv"
"strings"
"sync"
"syscall"
"unsafe"
)
const (
GCLP_HBRBACKGROUND int32 = -10
GCLP_HICON int32 = -14
)
func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) {
// -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11)
// Also shows the caption buttons if transparent ant translucent but they don't work.
// 0: Adds the default frame styling but no aero shadow, does not show the caption buttons.
// 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons
// are shown if transparent ant translucent.
var margins MARGINS
if extend {
margins = MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons
}
if err := dwmExtendFrameIntoClientArea(hwnd, &margins); err != nil {
log.Fatal(fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err))
}
}
func IsVisible(hwnd uintptr) bool {
ret, _, _ := procIsWindowVisible.Call(hwnd)
return ret != 0
}
func IsWindowFullScreen(hwnd uintptr) bool {
wRect := GetWindowRect(hwnd)
m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY)
var mi MONITORINFO
mi.CbSize = uint32(unsafe.Sizeof(mi))
if !GetMonitorInfo(m, &mi) {
return false
}
return wRect.Left == mi.RcMonitor.Left &&
wRect.Top == mi.RcMonitor.Top &&
wRect.Right == mi.RcMonitor.Right &&
wRect.Bottom == mi.RcMonitor.Bottom
}
func IsWindowMaximised(hwnd uintptr) bool {
style := uint32(getWindowLong(hwnd, GWL_STYLE))
return style&WS_MAXIMIZE != 0
}
func IsWindowMinimised(hwnd uintptr) bool {
style := uint32(getWindowLong(hwnd, GWL_STYLE))
return style&WS_MINIMIZE != 0
}
func RestoreWindow(hwnd uintptr) {
showWindow(hwnd, SW_RESTORE)
}
func ShowWindowMaximised(hwnd uintptr) {
showWindow(hwnd, SW_MAXIMIZE)
}
func ShowWindowMinimised(hwnd uintptr) {
showWindow(hwnd, SW_MINIMIZE)
}
func SetApplicationIcon(hwnd uintptr, icon HICON) {
setClassLongPtr(hwnd, GCLP_HICON, icon)
}
func SetBackgroundColour(hwnd uintptr, r, g, b uint8) {
col := uint32(r) | uint32(g)<<8 | uint32(b)<<16
hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col))
setClassLongPtr(hwnd, GCLP_HBRBACKGROUND, hbrush)
}
func IsWindowNormal(hwnd uintptr) bool {
return !IsWindowMaximised(hwnd) && !IsWindowMinimised(hwnd) && !IsWindowFullScreen(hwnd)
}
func setClassLongPtr(hwnd uintptr, param int32, val uintptr) bool {
proc := procSetClassLongPtr
if strconv.IntSize == 32 {
/*
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw
Note: To write code that is compatible with both 32-bit and 64-bit Windows, use SetClassLongPtr.
When compiling for 32-bit Windows, SetClassLongPtr is defined as a call to the SetClassLong function
=> We have to do this dynamically when directly calling the DLL procedures
*/
proc = procSetClassLong
}
ret, _, _ := proc.Call(
hwnd,
uintptr(param),
val,
)
return ret != 0
}
func getWindowLong(hwnd uintptr, index int) int32 {
ret, _, _ := procGetWindowLong.Call(
hwnd,
uintptr(index))
return int32(ret)
}
func showWindow(hwnd uintptr, cmdshow int) bool {
ret, _, _ := procShowWindow.Call(
hwnd,
uintptr(cmdshow))
return ret != 0
}
func stripNulls(str string) string {
// Split the string into substrings at each null character
substrings := strings.Split(str, "\x00")
// Join the substrings back into a single string
strippedStr := strings.Join(substrings, "")
return strippedStr
}
func MustStringToUTF16Ptr(input string) *uint16 {
input = stripNulls(input)
result, err := syscall.UTF16PtrFromString(input)
if err != nil {
Fatal(err.Error())
}
return result
}
func MustStringToUTF16uintptr(input string) uintptr {
input = stripNulls(input)
ret := lo.Must(syscall.UTF16PtrFromString(input))
return uintptr(unsafe.Pointer(ret))
}
func MustStringToUTF16(input string) []uint16 {
input = stripNulls(input)
return lo.Must(syscall.UTF16FromString(input))
}
func CenterWindow(hwnd HWND) {
windowInfo := getWindowInfo(hwnd)
frameless := windowInfo.IsPopup()
info := GetMonitorInfoForWindow(hwnd)
workRect := info.RcWork
screenMiddleW := workRect.Left + (workRect.Right-workRect.Left)/2
screenMiddleH := workRect.Top + (workRect.Bottom-workRect.Top)/2
var winRect *RECT
if !frameless {
winRect = GetWindowRect(hwnd)
} else {
winRect = GetClientRect(hwnd)
}
winWidth := winRect.Right - winRect.Left
winHeight := winRect.Bottom - winRect.Top
windowX := screenMiddleW - (winWidth / 2)
windowY := screenMiddleH - (winHeight / 2)
SetWindowPos(hwnd, HWND_TOP, int(windowX), int(windowY), int(winWidth), int(winHeight), SWP_NOSIZE)
}
func getWindowInfo(hwnd HWND) *WINDOWINFO {
var info WINDOWINFO
info.CbSize = uint32(unsafe.Sizeof(info))
GetWindowInfo(hwnd, &info)
return &info
}
func GetMonitorInfoForWindow(hwnd HWND) *MONITORINFO {
currentMonitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)
var info MONITORINFO
info.CbSize = uint32(unsafe.Sizeof(info))
GetMonitorInfo(currentMonitor, &info)
return &info
}
type WindowProc func(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr
var windowClasses = make(map[string]HINSTANCE)
var windowClassesLock sync.Mutex
func getWindowClass(name string) (HINSTANCE, bool) {
windowClassesLock.Lock()
defer windowClassesLock.Unlock()
result, exists := windowClasses[name]
return result, exists
}
func setWindowClass(name string, instance HINSTANCE) {
windowClassesLock.Lock()
defer windowClassesLock.Unlock()
windowClasses[name] = instance
}
func RegisterWindow(name string, proc WindowProc) (HINSTANCE, error) {
classInstance, exists := getWindowClass(name)
if exists {
return classInstance, nil
}
applicationInstance := GetModuleHandle("")
if applicationInstance == 0 {
return 0, fmt.Errorf("get module handle failed")
}
var wc WNDCLASSEX
wc.Size = uint32(unsafe.Sizeof(wc))
wc.WndProc = syscall.NewCallback(proc)
wc.Instance = applicationInstance
wc.Icon = LoadIconWithResourceID(0, uint16(IDI_APPLICATION))
wc.Cursor = LoadCursorWithResourceID(0, uint16(IDC_ARROW))
wc.Background = COLOR_BTNFACE + 1
wc.ClassName = MustStringToUTF16Ptr(name)
atom := RegisterClassEx(&wc)
if atom == 0 {
panic(syscall.GetLastError())
}
setWindowClass(name, applicationInstance)
return applicationInstance, nil
}
func FlashWindow(hwnd HWND, enabled bool) {
var flashInfo FLASHWINFO
flashInfo.CbSize = uint32(unsafe.Sizeof(flashInfo))
flashInfo.Hwnd = hwnd
if enabled {
flashInfo.DwFlags = FLASHW_ALL | FLASHW_TIMERNOFG
} else {
flashInfo.DwFlags = FLASHW_STOP
}
_, _, _ = procFlashWindowEx.Call(uintptr(unsafe.Pointer(&flashInfo)))
}
func EnumChildWindows(hwnd HWND, callback func(hwnd HWND, lparam LPARAM) LRESULT) LRESULT {
r, _, _ := procEnumChildWindows.Call(hwnd, syscall.NewCallback(callback), 0)
return r
}