github.com/iDigitalFlame/xmt@v0.5.4/device/winapi/windows.go (about) 1 //go:build windows 2 // +build windows 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 package winapi 21 22 import ( 23 "sync" 24 "syscall" 25 "unsafe" 26 27 "github.com/iDigitalFlame/xmt/data" 28 ) 29 30 var winCb struct { 31 e []Window 32 sync.Mutex 33 } 34 var enumWindowsOnce struct { 35 _ [0]func() 36 sync.Once 37 f uintptr 38 } 39 40 type key struct { 41 // DO NOT REORDER 42 Key uint16 43 _ uint16 44 Flags uint32 45 _ uint32 46 _ uintptr 47 } 48 type input struct { 49 // DO NOT REORDER 50 Type uint32 51 Key key 52 _ uint64 53 } 54 55 // Window is a struct that represents a Windows Window. The handles are the same 56 // for the duration of the Window's existence. 57 type Window struct { 58 _ [0]func() 59 Name string 60 Flags uint8 61 Handle uintptr 62 X, Y int32 63 Width, Height int32 64 } 65 type windowInfo struct { 66 // DO NOT REORDER 67 Size uint32 68 Window rect 69 Client rect 70 Style uint32 71 ExStyle uint32 72 Status uint32 73 _, _ uint32 74 _, _ uint16 75 } 76 77 func initWindowsEnumFunc() { 78 enumWindowsOnce.f = syscall.NewCallback(enumWindowsCallback) 79 } 80 func sendText(s string) error { 81 var b [256]input 82 if len(s) < 64 { 83 return sendKeys(&b, s) 84 } 85 for i, e := 0, 0; i < len(s); { 86 if e = i + 64; e > len(s) { 87 e = len(s) 88 } 89 if err := sendKeys(&b, s[i:e]); err != nil { 90 return err 91 } 92 i = e 93 } 94 return nil 95 } 96 97 // CloseWindow is a helper function that sends the WM_DESTROY to the supplied 98 // Window handle. 99 // 100 // If the value of h is 0, this will target ALL FOUND WINDOWS. 101 func CloseWindow(h uintptr) error { 102 if h > 0 { 103 return closeWindow(h) 104 } 105 w, err := TopLevelWindows() 106 if err != nil { 107 return err 108 } 109 for i := range w { 110 closeWindow(w[i].Handle) 111 } 112 w = nil 113 return err 114 } 115 func closeWindow(h uintptr) error { 116 r, _, err := syscallN(funcSendNotifyMessage.address(), h, 0x0002, 0, 0) 117 if r == 0 { 118 return unboxError(err) 119 } 120 return nil 121 } 122 123 // IsMinimized returns true if the Window state was minimized at the time of 124 // discovery. 125 func (i Window) IsMinimized() bool { 126 return i.Flags&0x2 != 0 127 } 128 129 // IsMaximized returns true if the Window state was maximized at the time of 130 // discovery. 131 func (i Window) IsMaximized() bool { 132 return i.Flags&0x1 != 0 133 } 134 func keyCode(k byte) (uint16, bool) { 135 if k > 47 && k < 58 { 136 return uint16(0x30 + (k - 48)), false 137 } 138 if k > 64 && k < 91 { 139 return uint16(0x41 + (k - 65)), true 140 } 141 if k > 96 && k < 123 { 142 return uint16(0x41 + (k - 97)), false 143 } 144 switch k { 145 case 9: 146 return 0x09, false 147 case '\r', '\n': 148 return 0x0D, false 149 case '-': 150 return 0xBD, false 151 case '=': 152 return 0xBB, false 153 case ';': 154 return 0xBA, false 155 case '[': 156 return 0xDB, false 157 case ']': 158 return 0xDD, false 159 case '\\': 160 return 0xDC, false 161 case ',': 162 return 0xBC, false 163 case '.': 164 return 0xBE, false 165 case '`': 166 return 0xC0, false 167 case '/': 168 return 0xBF, false 169 case ' ': 170 return 0x20, false 171 case '\'': 172 return 0xDE, false 173 case '~': 174 return 0xC0, true 175 case '!': 176 return 0x31, true 177 case '@': 178 return 0x32, true 179 case '#': 180 return 0x33, true 181 case '$': 182 return 0x34, true 183 case '%': 184 return 0x35, true 185 case '^': 186 return 0x36, true 187 case '&': 188 return 0x37, true 189 case '*': 190 return 0x38, true 191 case '(': 192 return 0x39, true 193 case ')': 194 return 0x30, true 195 case '_': 196 return 0xBD, true 197 case '+': 198 return 0xBB, true 199 case '{': 200 return 0xDB, true 201 case '}': 202 return 0xDD, true 203 case '|': 204 return 0xDC, true 205 case ':': 206 return 0xBA, true 207 case '"': 208 return 0xDE, true 209 case '<': 210 return 0xBC, true 211 case '>': 212 return 0xBE, true 213 default: 214 } 215 return 0xBF, true 216 } 217 218 // TopLevelWindows returns a list of the current (non-dialog) Windows as a 219 // slice with their Name, Handle, Size and Position. 220 // 221 // The handles may be used for multiple functions and are valid until the window 222 // is closed. 223 func TopLevelWindows() ([]Window, error) { 224 winCb.Lock() 225 enumWindowsOnce.Do(initWindowsEnumFunc) 226 var ( 227 e []Window 228 r, _, err = syscallN(funcEnumWindows.address(), enumWindowsOnce.f, 0) 229 ) 230 e, winCb.e = winCb.e, nil 231 if winCb.Unlock(); r == 0 { 232 return nil, unboxError(err) 233 } 234 return e, nil 235 } 236 237 // SetForegroundWindow Windows API Call 238 // 239 // Brings the thread that created the specified window into the foreground and 240 // activates the window. Keyboard input is directed to the window, and various 241 // visual cues are changed for the user. The system assigns a slightly higher 242 // priority to the thread that created the foreground window than it does to 243 // other threads. 244 // 245 // This function is supplemented with the "SetFocus" function, as this will allow 246 // for requesting THEN setting the foreground window without user interaction. 247 // 248 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow 249 func SetForegroundWindow(h uintptr) error { 250 // Set it first before asking, that way the function below doesn't fail. 251 syscallN(funcSetFocus.address(), h) 252 r, _, err := syscallN(funcSetForegroundWindow.address(), h) 253 if r == 0 { 254 return unboxError(err) 255 } 256 return nil 257 } 258 259 // SendInput will attempt to set the window 'h' to the front (activate) and will 260 // perform input typing of the supplied string as input events. 261 // 262 // The window handle can be zero to ignore targeting a window. 263 func SendInput(h uintptr, s string) error { 264 if h > 0 { 265 // NOTE(dij): This function call error is ignored as it has a fit it 266 // focus is requested and the user doesn't give it attention. 267 SetForegroundWindow(h) 268 } 269 if len(s) == 0 { 270 return nil 271 } 272 return sendText(s) 273 } 274 func sendKeys(b *[256]input, s string) error { 275 var ( 276 n int 277 k uint16 278 u bool 279 ) 280 for i := 0; i < len(s) && i < 64 && n < 256; i++ { 281 if k, u = keyCode(s[i]); u { 282 (*b)[n].Type, (*b)[n].Key.Key, (*b)[n].Key.Flags = 1, 0x10, 0 283 n++ 284 } 285 (*b)[n].Type, (*b)[n].Key.Key, (*b)[n].Key.Flags = 1, k, 0 286 (*b)[n+1].Type, (*b)[n+1].Key.Key, (*b)[n+1].Key.Flags = 1, k, 2 287 if n += 2; u || k == 0x20 { 288 (*b)[n].Type, (*b)[n].Key.Key, (*b)[n].Key.Flags = 1, 0x10, 2 289 n++ 290 } 291 } 292 if r, _, err := syscallN(funcSendInput.address(), uintptr(n), uintptr(unsafe.Pointer(b)), unsafe.Sizeof(b[0])); int(r) != n { 293 return unboxError(err) 294 } 295 return nil 296 } 297 func enumWindowsCallback(h, _ uintptr) uintptr { 298 n, _, _ := syscallN(funcGetWindowTextLength.address(), h) 299 if n == 0 { 300 return 1 301 } 302 i := windowInfo{Size: 60} 303 if r, _, _ := syscallN(funcGetWindowInfo.address(), h, uintptr(unsafe.Pointer(&i))); r == 0 { 304 return 1 305 } 306 // 0x80000000 - WS_POPUP 307 // 0x20000000 - WS_MINIMIZE 308 // 0x10000000 - WS_VISIBLE 309 // 0x00000400 - WS_EX_CONTEXTHELP 310 // 311 // Removes popup windows that were created hidden or minimized. Most of them 312 // are built-in system dialogs. 313 if (i.Style&0x80000000 != 0 && i.Style&0x10000000 == 0) || i.Style&0x10000000 == 0 || i.Style&0x00000400 != 0 { 314 return 1 315 } 316 v := make([]uint16, n+1) 317 if n, _, _ = syscallN(funcGetWindowText.address(), h, uintptr(unsafe.Pointer(&v[0])), n+1); n == 0 { 318 return 1 319 } 320 var t uint8 321 if i.Style&0x1000000 == 0 { 322 t |= 0x1 323 } 324 if i.Style&0x20000000 == 0 { 325 t |= 0x2 326 } 327 if i.Style&0x1E000000 == 0x1E000000 { 328 t |= 0x80 329 } 330 winCb.e = append(winCb.e, Window{ 331 X: i.Window.Left, 332 Y: i.Window.Top, 333 Name: UTF16ToString(v[:n]), 334 Flags: t, 335 Width: i.Window.Right - i.Window.Left, 336 Height: i.Window.Bottom - i.Window.Top, 337 Handle: h, 338 }) 339 return 1 340 } 341 342 // ShowWindow Windows API Call 343 // 344 // Sets the specified window's show state. 345 // 346 // The provided Sw* constants can be used to specify a show type. 347 // 348 // The resulting boolean is if the window was previously shown, or false if 349 // it was hidden. (This value is always false if 'AllWindows'/0 is passed 350 // as the handle.) 351 // 352 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow 353 // 354 // If the value of h is 0, this will target ALL FOUND WINDOWS. 355 func ShowWindow(h uintptr, t uint8) (bool, error) { 356 if h > 0 { 357 return showWindow(h, uint32(t)) 358 } 359 w, err := TopLevelWindows() 360 if err != nil { 361 return false, err 362 } 363 for i := range w { 364 showWindow(w[i].Handle, uint32(t)) 365 } 366 w = nil 367 return false, err 368 } 369 func showWindow(h uintptr, v uint32) (bool, error) { 370 r, _, err := syscallN(funcShowWindow.address(), h, uintptr(v)) 371 if err > 0 { 372 return r > 0, unboxError(err) 373 } 374 return r > 0, nil 375 } 376 377 // MarshalStream transforms this struct into a binary format and writes to the 378 // supplied data.Writer. 379 func (i Window) MarshalStream(w data.Writer) error { 380 if err := w.WriteUint64(uint64(i.Handle)); err != nil { 381 return err 382 } 383 if err := w.WriteString(i.Name); err != nil { 384 return err 385 } 386 if err := w.WriteUint8(i.Flags); err != nil { 387 return err 388 } 389 if err := w.WriteInt32(i.X); err != nil { 390 return err 391 } 392 if err := w.WriteInt32(i.Y); err != nil { 393 return err 394 } 395 if err := w.WriteInt32(i.Width); err != nil { 396 return err 397 } 398 return w.WriteInt32(i.Height) 399 } 400 401 // EnableWindow Windows API Call 402 // 403 // Enables or disables mouse and keyboard input to the specified window or 404 // control. When input is disabled, the window does not receive input such as 405 // mouse clicks and key presses. When input is enabled, the window receives 406 // all input. 407 // 408 // The resulting boolean is if the window was previously enabled, or false if 409 // it was disabled. (This value is always false if 'AllWindows'/0 is passed 410 // as the handle.) 411 // 412 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow 413 // 414 // If the value of h is 0, this will target ALL FOUND WINDOWS. 415 func EnableWindow(h uintptr, e bool) (bool, error) { 416 var v uint32 417 if e { 418 v = 1 419 } 420 if h > 0 { 421 return enableWindow(h, v) 422 } 423 w, err := TopLevelWindows() 424 if err != nil { 425 return false, err 426 } 427 for i := range w { 428 enableWindow(w[i].Handle, v) 429 } 430 w = nil 431 return false, err 432 } 433 434 // SetWindowTransparency will attempt to set the transparency of the window handle 435 // to 0-255, 0 being completely transparent and 255 being opaque. 436 // 437 // If the value of h is 0, this will target ALL FOUND WINDOWS. 438 func SetWindowTransparency(h uintptr, t uint8) error { 439 if h > 0 { 440 return setWindowTransparency(h, t) 441 } 442 w, err := TopLevelWindows() 443 if err != nil { 444 return err 445 } 446 for i := range w { 447 setWindowTransparency(w[i].Handle, t) 448 } 449 w = nil 450 return err 451 } 452 func setWindowTransparency(h uintptr, t uint8) error { 453 // layeredPtr (-20) - GWL_EXSTYLE 454 r, _, err := syscallN(funcGetWindowLongW.address(), h, layeredPtr) 455 if r == 0 && err > 0 { 456 return unboxError(err) 457 } 458 // 0x80000 - WS_EX_LAYERED 459 syscallN(funcSetWindowLongW.address(), h, layeredPtr, r|0x80000) 460 if r, _, err = syscallN(funcSetLayeredWindowAttributes.address(), h, 0, uintptr(t), 3); r == 0 { 461 return unboxError(err) 462 } 463 return nil 464 } 465 func enableWindow(h uintptr, v uint32) (bool, error) { 466 r, _, err := syscallN(funcEnableWindow.address(), h, uintptr(v)) 467 if err > 0 { 468 return r > 0, unboxError(err) 469 } 470 return r > 0, nil 471 } 472 473 // SetWindowPos Windows API Call 474 // 475 // Changes the size, position, and Z order of a child, pop-up, or top-level 476 // window. These windows are ordered according to their appearance on the screen. 477 // The topmost window receives the highest rank and is the first window in the 478 // Z order. 479 // 480 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos 481 // 482 // Use '-1' for both the 'x' and 'y' arguments to ignore changing the position and 483 // just change the size OR use '-1' for both the 'width' and 'height' arguments to 484 // only change the window position. 485 // 486 // This implementation does NOT change the active state of Z index of the window. 487 func SetWindowPos(h uintptr, x, y, width, height int32) error { 488 // 0x14 - SWP_NOZORDER | SWP_NOACTIVATE 489 f := uint32(0x14) 490 if width == -1 && height == -1 { 491 // 0x1 - SWP_NOSIZE 492 f |= 0x1 493 } else if x == -1 && y == -1 { 494 // 0x2 - SWP_NOMOVE 495 f |= 0x2 496 } 497 r, _, err := syscallN(funcSetWindowPos.address(), h, 0, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(f)) 498 if r == 0 { 499 return unboxError(err) 500 } 501 return nil 502 } 503 504 // MessageBox Windows API Call 505 // 506 // Displays a modal dialog box that contains a system icon, a set of buttons, 507 // and a brief application-specific message, such as status or error information. 508 // The message box returns an integer value that indicates which button the user 509 // clicked. 510 // 511 // If the handle 'h' is '-1', "CurrentProcess" or "^uintptr(0)", this will attempt 512 // to target the Desktop window, which will fall back to '0' if it fails. 513 // 514 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw 515 func MessageBox(h uintptr, text, title string, f uint32) (uint32, error) { 516 var ( 517 t, d *uint16 518 err error 519 ) 520 if len(title) > 0 { 521 if t, err = UTF16PtrFromString(title); err != nil { 522 return 0, err 523 } 524 } 525 if len(text) > 0 { 526 if d, err = UTF16PtrFromString(text); err != nil { 527 return 0, err 528 } 529 } 530 if h == invalid { // If handle is '-1', target the Desktop window. 531 if w, err := TopLevelWindows(); err == nil { 532 for i := range w { 533 if w[i].Flags&0x80 != 0 { 534 h = w[i].Handle 535 break 536 } 537 } 538 } 539 if h == invalid { 540 h = 0 // Fallback 541 } 542 } 543 r, _, err1 := syscallN(funcMessageBox.address(), h, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(t)), uintptr(f)) 544 if r == 0 { 545 return 0, unboxError(err1) 546 } 547 return uint32(r), nil 548 }