github.com/utopiagio/gio@v0.0.8/app/os_windows.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package app 4 5 import ( 6 "errors" 7 "fmt" 8 "image" 9 "io" 10 "runtime" 11 "sort" 12 "strings" 13 "sync" 14 "time" 15 "unicode" 16 "unicode/utf8" 17 "unsafe" 18 19 syscall "golang.org/x/sys/windows" 20 21 "github.com/utopiagio/gio/app/internal/windows" 22 "github.com/utopiagio/gio/op" 23 "github.com/utopiagio/gio/unit" 24 gowindows "golang.org/x/sys/windows" 25 26 "github.com/utopiagio/gio/f32" 27 "github.com/utopiagio/gio/io/event" 28 "github.com/utopiagio/gio/io/key" 29 "github.com/utopiagio/gio/io/pointer" 30 "github.com/utopiagio/gio/io/system" 31 "github.com/utopiagio/gio/io/transfer" 32 ) 33 34 type Win32ViewEvent struct { 35 HWND uintptr 36 } 37 38 type window struct { 39 hwnd syscall.Handle 40 hdc syscall.Handle 41 w *callbacks 42 pointerBtns pointer.Buttons 43 44 // cursorIn tracks whether the cursor was inside the window according 45 // to the most recent WM_SETCURSOR. 46 cursorIn bool 47 cursor syscall.Handle 48 49 // placement saves the previous window position when in full screen mode. 50 placement *windows.WindowPlacement 51 52 animating bool 53 54 borderSize image.Point 55 config Config 56 loop *eventLoop 57 58 // invMu avoids the race between destroying the window and Invalidate. 59 invMu sync.Mutex 60 } 61 62 const _WM_WAKEUP = windows.WM_USER + iota 63 64 type gpuAPI struct { 65 priority int 66 initializer func(w *window) (context, error) 67 } 68 69 // drivers is the list of potential Context implementations. 70 var drivers []gpuAPI 71 72 // winMap maps win32 HWNDs to *windows. 73 var winMap sync.Map 74 75 // iconID is the ID of the icon in the resource file. 76 const iconID = 1 77 78 var resources struct { 79 once sync.Once 80 // handle is the module handle from GetModuleHandle. 81 handle syscall.Handle 82 // class is the Gio window class from RegisterClassEx. 83 class uint16 84 // cursor is the arrow cursor resource. 85 cursor syscall.Handle 86 } 87 88 func osMain() { 89 select {} 90 } 91 92 93 func newWindow(win *callbacks, options []Option) { 94 //fmt.Println("newWindow............") 95 done := make(chan struct{}) 96 go func() { 97 // GetMessage and PeekMessage can filter on a window HWND, but 98 // then thread-specific messages such as WM_QUIT are ignored. 99 // Instead lock the thread so window messages arrive through 100 // unfiltered GetMessage calls. 101 runtime.LockOSThread() 102 103 w := &window{ 104 w: win, 105 } 106 w.loop = newEventLoop(w.w, w.wakeup) 107 w.w.SetDriver(w) 108 err := w.init() 109 done <- struct{}{} 110 if err != nil { 111 w.ProcessEvent(DestroyEvent{Err: err}) 112 return 113 } 114 winMap.Store(w.hwnd, w) 115 defer winMap.Delete(w.hwnd) 116 w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)}) 117 w.Configure(options) 118 windows.SetForegroundWindow(w.hwnd) 119 windows.SetFocus(w.hwnd) 120 // Since the window class for the cursor is null, 121 // set it here to show the cursor. 122 w.SetCursor(pointer.CursorDefault) 123 w.runLoop() 124 }() 125 <-done 126 } 127 128 // initResources initializes the resources global. 129 func initResources() error { 130 windows.SetProcessDPIAware() 131 hInst, err := windows.GetModuleHandle() 132 if err != nil { 133 return err 134 } 135 resources.handle = hInst 136 c, err := windows.LoadCursor(windows.IDC_ARROW) 137 if err != nil { 138 return err 139 } 140 resources.cursor = c 141 icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED) 142 wcls := windows.WndClassEx{ 143 CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})), 144 Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC, 145 LpfnWndProc: syscall.NewCallback(windowProc), 146 HInstance: hInst, 147 HIcon: icon, 148 LpszClassName: syscall.StringToUTF16Ptr("GioWindow"), 149 } 150 cls, err := windows.RegisterClassEx(&wcls) 151 if err != nil { 152 return err 153 } 154 resources.class = cls 155 return nil 156 } 157 158 const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE 159 160 func (w *window) init() error { 161 var resErr error 162 resources.once.Do(func() { 163 resErr = initResources() 164 }) 165 if resErr != nil { 166 return resErr 167 } 168 const dwStyle = windows.WS_OVERLAPPEDWINDOW 169 170 hwnd, err := windows.CreateWindowEx( 171 dwExStyle, 172 resources.class, 173 "", 174 dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN, 175 windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, 176 windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, 177 0, 178 0, 179 resources.handle, 180 0) 181 if err != nil { 182 return err 183 } 184 w.hdc, err = windows.GetDC(hwnd) 185 if err != nil { 186 windows.DestroyWindow(hwnd) 187 return err 188 } 189 w.hwnd = hwnd 190 return nil 191 } 192 193 // update() handles changes done by the user, and updates the configuration. 194 // It reads the window style and size/position and updates w.config. 195 // If anything has changed it emits a ConfigEvent to notify the application. 196 func (w *window) update() { 197 w.borderSize = image.Pt( 198 windows.GetSystemMetrics(windows.SM_CXSIZEFRAME), 199 windows.GetSystemMetrics(windows.SM_CYSIZEFRAME), 200 ) 201 202 cr := windows.GetClientRect(w.hwnd) 203 //fmt.Println("w.GetClientRect=", cr.Left, ",", cr.Top, ",", cr.Right - cr.Left, ",", cr.Bottom - cr.Top, ",", ")") 204 w.config.Size = image.Point{ 205 X: int(cr.Right - cr.Left), 206 Y: int(cr.Bottom - cr.Top), 207 } 208 //fmt.Println("w.client.Size Width=", w.config.Size.X, "Height=", w.config.Size.Y, ")") 209 // ************************************************************************* 210 // RNW Added window position to update config after WM_MOVE message received 211 wr := windows.GetWindowRect(w.hwnd) 212 //fmt.Println("w.GetWindowRect=", wr.Left, ",", wr.Top, ",", wr.Right - wr.Left, ",", wr.Bottom - wr.Top, ",", ")") 213 w.config.Pos = image.Point{ 214 X: int(wr.Left), 215 Y: int(wr.Top), 216 } 217 //X := int(wr.Right - wr.Left) 218 //Y := int(wr.Bottom - wr.Top) 219 220 //fmt.Println("w.window.Pos X=", w.config.Pos.X, "Y=", w.config.Pos.Y, ")") 221 //fmt.Println("w.window.Size Width=", X, "Height=", Y, ")") 222 // ************************************************************************* 223 w.borderSize = image.Pt( 224 windows.GetSystemMetrics(windows.SM_CXSIZEFRAME), 225 windows.GetSystemMetrics(windows.SM_CYSIZEFRAME), 226 ) 227 //fmt.Println("w.borderSize X=", w.borderSize.X, "Y=", w.borderSize.Y, ")") 228 w.ProcessEvent(ConfigEvent{Config: w.config}) 229 } 230 231 func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { 232 win, exists := winMap.Load(hwnd) 233 if !exists { 234 return windows.DefWindowProc(hwnd, msg, wParam, lParam) 235 } 236 237 w := win.(*window) 238 239 switch msg { 240 case windows.WM_UNICHAR: 241 if wParam == windows.UNICODE_NOCHAR { 242 // Tell the system that we accept WM_UNICHAR messages. 243 return windows.TRUE 244 } 245 fallthrough 246 case windows.WM_CHAR: 247 if r := rune(wParam); unicode.IsPrint(r) { 248 w.w.EditorInsert(string(r)) 249 } 250 // The message is processed. 251 return windows.TRUE 252 case windows.WM_DPICHANGED: 253 // Let Windows know we're prepared for runtime DPI changes. 254 return windows.TRUE 255 case windows.WM_ERASEBKGND: 256 // Avoid flickering between GPU content and background color. 257 return windows.TRUE 258 case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP: 259 if n, ok := convertKeyCode(wParam); ok { 260 e := key.Event{ 261 Name: n, 262 Modifiers: getModifiers(), 263 State: key.Press, 264 } 265 if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP { 266 e.State = key.Release 267 } 268 269 w.ProcessEvent(e) 270 271 if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) { 272 // Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs 273 // such as cmd.exe and graphical debuggers also reserve F10. 274 return 0 275 } 276 } 277 case windows.WM_LBUTTONDOWN: 278 w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers()) 279 case windows.WM_LBUTTONUP: 280 w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers()) 281 case windows.WM_RBUTTONDOWN: 282 w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers()) 283 case windows.WM_RBUTTONUP: 284 w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers()) 285 case windows.WM_MBUTTONDOWN: 286 w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers()) 287 case windows.WM_MBUTTONUP: 288 w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers()) 289 case windows.WM_CANCELMODE: 290 w.ProcessEvent(pointer.Event{ 291 Kind: pointer.Cancel, 292 }) 293 case windows.WM_SETFOCUS: 294 w.config.Focused = true 295 w.ProcessEvent(ConfigEvent{Config: w.config}) 296 case windows.WM_KILLFOCUS: 297 w.config.Focused = false 298 w.ProcessEvent(ConfigEvent{Config: w.config}) 299 case windows.WM_NCHITTEST: 300 if w.config.Decorated { 301 // Let the system handle it. 302 break 303 } 304 x, y := coordsFromlParam(lParam) 305 np := windows.Point{X: int32(x), Y: int32(y)} 306 windows.ScreenToClient(w.hwnd, &np) 307 return w.hitTest(int(np.X), int(np.Y)) 308 case windows.WM_MOUSEMOVE: 309 x, y := coordsFromlParam(lParam) 310 p := f32.Point{X: float32(x), Y: float32(y)} 311 w.ProcessEvent(pointer.Event{ 312 Kind: pointer.Move, 313 Source: pointer.Mouse, 314 Position: p, 315 Buttons: w.pointerBtns, 316 Time: windows.GetMessageTime(), 317 Modifiers: getModifiers(), 318 }) 319 case windows.WM_MOUSEWHEEL: 320 w.scrollEvent(wParam, lParam, false, getModifiers()) 321 case windows.WM_MOUSEHWHEEL: 322 w.scrollEvent(wParam, lParam, true, getModifiers()) 323 case windows.WM_DESTROY: 324 w.ProcessEvent(Win32ViewEvent{}) 325 w.ProcessEvent(DestroyEvent{}) 326 if w.hdc != 0 { 327 windows.ReleaseDC(w.hdc) 328 w.hdc = 0 329 } 330 w.invMu.Lock() 331 // The system destroys the HWND for us. 332 w.hwnd = 0 333 w.invMu.Unlock() 334 windows.PostQuitMessage(0) 335 // ************************************************************************** 336 // ******** RNW Added WM_MOVE to os.windows::windowProc 26.02.2024 ********** 337 // Updates position of the window after user interaction. 338 case windows.WM_MOVE: 339 w.update() 340 w.draw(true) // DANGER! force window redraw in between move events 341 // ************************************************************************** 342 case windows.WM_NCCALCSIZE: 343 if w.config.Decorated { 344 // Let Windows handle decorations. 345 break 346 } 347 // No client areas; we draw decorations ourselves. 348 if wParam != 1 { 349 return 0 350 } 351 // lParam contains an NCCALCSIZE_PARAMS for us to adjust. 352 place := windows.GetWindowPlacement(w.hwnd) 353 if !place.IsMaximized() { 354 // Nothing do adjust. 355 return 0 356 } 357 // Adjust window position to avoid the extra padding in maximized 358 // state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543. 359 // Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows. 360 szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam)) 361 mi := windows.GetMonitorInfo(w.hwnd) 362 szp.Rgrc[0] = mi.WorkArea 363 return 0 364 case windows.WM_PAINT: 365 w.draw(true) 366 case windows.WM_SIZE: 367 w.update() 368 switch wParam { 369 case windows.SIZE_MINIMIZED: 370 w.config.Mode = Minimized 371 case windows.SIZE_MAXIMIZED: 372 w.config.Mode = Maximized 373 case windows.SIZE_RESTORED: 374 if w.config.Mode != Fullscreen { 375 w.config.Mode = Windowed 376 } 377 } 378 case windows.WM_GETMINMAXINFO: 379 mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam)) 380 var bw, bh int32 381 if w.config.Decorated { 382 r := windows.GetWindowRect(w.hwnd) 383 cr := windows.GetClientRect(w.hwnd) 384 bw = r.Right - r.Left - (cr.Right - cr.Left) 385 bh = r.Bottom - r.Top - (cr.Bottom - cr.Top) 386 } 387 if p := w.config.MinSize; p.X > 0 || p.Y > 0 { 388 mm.PtMinTrackSize = windows.Point{ 389 X: int32(p.X) + bw, 390 Y: int32(p.Y) + bh, 391 } 392 } 393 if p := w.config.MaxSize; p.X > 0 || p.Y > 0 { 394 mm.PtMaxTrackSize = windows.Point{ 395 X: int32(p.X) + bw, 396 Y: int32(p.Y) + bh, 397 } 398 } 399 return 0 400 case windows.WM_SETCURSOR: 401 w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT 402 if w.cursorIn { 403 windows.SetCursor(w.cursor) 404 return windows.TRUE 405 } 406 case _WM_WAKEUP: 407 w.loop.Wakeup() 408 w.loop.FlushEvents() 409 case windows.WM_IME_STARTCOMPOSITION: 410 imc := windows.ImmGetContext(w.hwnd) 411 if imc == 0 { 412 return windows.TRUE 413 } 414 defer windows.ImmReleaseContext(w.hwnd, imc) 415 sel := w.w.EditorState().Selection 416 caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent))) 417 icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5)) 418 windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y) 419 windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y) 420 case windows.WM_IME_COMPOSITION: 421 imc := windows.ImmGetContext(w.hwnd) 422 if imc == 0 { 423 return windows.TRUE 424 } 425 defer windows.ImmReleaseContext(w.hwnd, imc) 426 state := w.w.EditorState() 427 rng := state.compose 428 if rng.Start == -1 { 429 rng = state.Selection.Range 430 } 431 if rng.Start > rng.End { 432 rng.Start, rng.End = rng.End, rng.Start 433 } 434 var replacement string 435 switch { 436 case lParam&windows.GCS_RESULTSTR != 0: 437 replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR) 438 case lParam&windows.GCS_COMPSTR != 0: 439 replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR) 440 } 441 end := rng.Start + utf8.RuneCountInString(replacement) 442 w.w.EditorReplace(rng, replacement) 443 state = w.w.EditorState() 444 comp := key.Range{ 445 Start: rng.Start, 446 End: end, 447 } 448 if lParam&windows.GCS_DELTASTART != 0 { 449 start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART) 450 comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start) 451 } 452 w.w.SetComposingRegion(comp) 453 pos := end 454 if lParam&windows.GCS_CURSORPOS != 0 { 455 rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS) 456 pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel) 457 } 458 w.w.SetEditorSelection(key.Range{Start: pos, End: pos}) 459 return windows.TRUE 460 case windows.WM_IME_ENDCOMPOSITION: 461 w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) 462 return windows.TRUE 463 } 464 465 return windows.DefWindowProc(hwnd, msg, wParam, lParam) 466 } 467 468 func getModifiers() key.Modifiers { 469 var kmods key.Modifiers 470 if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 { 471 kmods |= key.ModSuper 472 } 473 if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 { 474 kmods |= key.ModAlt 475 } 476 if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 { 477 kmods |= key.ModCtrl 478 } 479 if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 { 480 kmods |= key.ModShift 481 } 482 return kmods 483 } 484 485 // hitTest returns the non-client area hit by the point, needed to 486 // process WM_NCHITTEST. 487 func (w *window) hitTest(x, y int) uintptr { 488 if w.config.Mode == Fullscreen { 489 return windows.HTCLIENT 490 } 491 if w.config.Mode != Windowed { 492 // Only windowed mode should allow resizing. 493 return windows.HTCLIENT 494 } 495 // Check for resize handle before system actions; otherwise it can be impossible to 496 // resize a custom-decorations window when the system move area is flush with the 497 // edge of the window. 498 top := y <= w.borderSize.Y 499 bottom := y >= w.config.Size.Y-w.borderSize.Y 500 left := x <= w.borderSize.X 501 right := x >= w.config.Size.X-w.borderSize.X 502 switch { 503 case top && left: 504 return windows.HTTOPLEFT 505 case top && right: 506 return windows.HTTOPRIGHT 507 case bottom && left: 508 return windows.HTBOTTOMLEFT 509 case bottom && right: 510 return windows.HTBOTTOMRIGHT 511 case top: 512 return windows.HTTOP 513 case bottom: 514 return windows.HTBOTTOM 515 case left: 516 return windows.HTLEFT 517 case right: 518 return windows.HTRIGHT 519 } 520 p := f32.Pt(float32(x), float32(y)) 521 if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove { 522 return windows.HTCAPTION 523 } 524 return windows.HTCLIENT 525 } 526 527 func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) { 528 if !w.config.Focused { 529 windows.SetFocus(w.hwnd) 530 } 531 532 var kind pointer.Kind 533 if press { 534 kind = pointer.Press 535 if w.pointerBtns == 0 { 536 windows.SetCapture(w.hwnd) 537 } 538 w.pointerBtns |= btn 539 } else { 540 kind = pointer.Release 541 w.pointerBtns &^= btn 542 if w.pointerBtns == 0 { 543 windows.ReleaseCapture() 544 } 545 } 546 x, y := coordsFromlParam(lParam) 547 p := f32.Point{X: float32(x), Y: float32(y)} 548 w.ProcessEvent(pointer.Event{ 549 Kind: kind, 550 Source: pointer.Mouse, 551 Position: p, 552 Buttons: w.pointerBtns, 553 Time: windows.GetMessageTime(), 554 Modifiers: kmods, 555 }) 556 } 557 558 func coordsFromlParam(lParam uintptr) (int, int) { 559 x := int(int16(lParam & 0xffff)) 560 y := int(int16((lParam >> 16) & 0xffff)) 561 return x, y 562 } 563 564 func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) { 565 x, y := coordsFromlParam(lParam) 566 // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast 567 // to other mouse events. 568 np := windows.Point{X: int32(x), Y: int32(y)} 569 windows.ScreenToClient(w.hwnd, &np) 570 p := f32.Point{X: float32(np.X), Y: float32(np.Y)} 571 dist := float32(int16(wParam >> 16)) 572 var sp f32.Point 573 if horizontal { 574 sp.X = dist 575 } else { 576 // support horizontal scroll (shift + mousewheel) 577 if kmods == key.ModShift { 578 sp.X = -dist 579 } else { 580 sp.Y = -dist 581 } 582 } 583 w.ProcessEvent(pointer.Event{ 584 Kind: pointer.Scroll, 585 Source: pointer.Mouse, 586 Position: p, 587 Buttons: w.pointerBtns, 588 Scroll: sp, 589 Modifiers: kmods, 590 Time: windows.GetMessageTime(), 591 }) 592 } 593 594 // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/ 595 func (w *window) runLoop() { 596 msg := new(windows.Msg) 597 loop: 598 for { 599 anim := w.animating 600 if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) { 601 w.draw(false) 602 continue 603 } 604 switch ret := windows.GetMessage(msg, 0, 0, 0); ret { 605 case -1: 606 panic(errors.New("GetMessage failed")) 607 case 0: 608 // WM_QUIT received. 609 break loop 610 } 611 windows.TranslateMessage(msg) 612 windows.DispatchMessage(msg) 613 } 614 } 615 616 func (w *window) EditorStateChanged(old, new editorState) { 617 imc := windows.ImmGetContext(w.hwnd) 618 if imc == 0 { 619 return 620 } 621 defer windows.ImmReleaseContext(w.hwnd, imc) 622 if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet { 623 windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0) 624 } 625 } 626 627 func (w *window) SetAnimating(anim bool) { 628 w.animating = anim 629 } 630 631 func (w *window) ProcessEvent(e event.Event) { 632 w.w.ProcessEvent(e) 633 w.loop.FlushEvents() 634 } 635 636 func (w *window) Event() event.Event { 637 return w.loop.Event() 638 } 639 640 func (w *window) Invalidate() { 641 w.loop.Invalidate() 642 } 643 644 func (w *window) Run(f func()) { 645 w.loop.Run(f) 646 } 647 648 func (w *window) Frame(frame *op.Ops) { 649 w.loop.Frame(frame) 650 } 651 652 func (w *window) wakeup() { 653 w.invMu.Lock() 654 defer w.invMu.Unlock() 655 if w.hwnd == 0 { 656 w.loop.Wakeup() 657 w.loop.FlushEvents() 658 return 659 } 660 if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil { 661 panic(err) 662 } 663 } 664 665 func (w *window) draw(sync bool) { 666 if w.config.Size.X == 0 || w.config.Size.Y == 0 { 667 return 668 } 669 dpi := windows.GetWindowDPI(w.hwnd) 670 cfg := configForDPI(dpi) 671 w.ProcessEvent(frameEvent{ 672 FrameEvent: FrameEvent{ 673 Now: time.Now(), 674 Size: w.config.Size, 675 Metric: cfg, 676 }, 677 Sync: sync, 678 }) 679 } 680 681 func (w *window) NewContext() (context, error) { 682 sort.Slice(drivers, func(i, j int) bool { 683 return drivers[i].priority < drivers[j].priority 684 }) 685 var errs []string 686 for _, b := range drivers { 687 ctx, err := b.initializer(w) 688 if err == nil { 689 return ctx, nil 690 } 691 errs = append(errs, err.Error()) 692 } 693 if len(errs) > 0 { 694 return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", ")) 695 } 696 return nil, errors.New("NewContext: no available GPU drivers") 697 } 698 699 func (w *window) ReadClipboard() { 700 w.readClipboard() 701 } 702 703 func (w *window) readClipboard() error { 704 if err := windows.OpenClipboard(w.hwnd); err != nil { 705 return err 706 } 707 defer windows.CloseClipboard() 708 mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT) 709 if err != nil { 710 return err 711 } 712 ptr, err := windows.GlobalLock(mem) 713 if err != nil { 714 return err 715 } 716 defer windows.GlobalUnlock(mem) 717 content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr))) 718 w.ProcessEvent(transfer.DataEvent{ 719 Type: "application/text", 720 Open: func() io.ReadCloser { 721 return io.NopCloser(strings.NewReader(content)) 722 }, 723 }) 724 return nil 725 } 726 727 func (w *window) Configure(options []Option) { 728 dpi := windows.GetSystemDPI() 729 metric := configForDPI(dpi) 730 w.config.apply(metric, options) 731 windows.SetWindowText(w.hwnd, w.config.Title) 732 733 style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE) 734 var showMode int32 735 var x, y, width, height int32 736 swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED) 737 winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW) 738 style &^= winStyle 739 switch w.config.Mode { 740 case Minimized: 741 style |= winStyle 742 swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE 743 showMode = windows.SW_SHOWMINIMIZED 744 745 case Maximized: 746 style |= winStyle 747 swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE 748 showMode = windows.SW_SHOWMAXIMIZED 749 case Windowed: 750 style |= winStyle 751 showMode = windows.SW_SHOWNORMAL 752 // ******************************************************************* 753 // RNW Added Get target position for client area position. 754 x = int32(w.config.Pos.X) // ******** RNW Added Pos (image.Point) to config 01.11.2023 ********* 755 y = int32(w.config.Pos.Y) // ******** RNW Added Pos (image.Point) to config 01.11.2023 ********* 756 // ******************************************************************* 757 758 // Get target for client area size. 759 width = int32(w.config.Size.X) 760 height = int32(w.config.Size.Y) 761 // Get the current window size and position. 762 wr := windows.GetWindowRect(w.hwnd) 763 // ******************************************************************* 764 // ******** RNW Added Pos (image.Point) to config 01.11.2023 ********* 765 if x < -9999 {x = wr.Left} //int32(-w.borderSize.X) {x = wr.Left} 766 if y < -9999 {y = wr.Top} 767 // ******************************************************************* 768 if w.config.Decorated { 769 // Compute client size and position. Note that the client size is 770 // equal to the window size when we are in control of decorations. 771 r := windows.Rect{ 772 Right: width, 773 Bottom: height, 774 } 775 windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle) 776 width = r.Right - r.Left 777 height = r.Bottom - r.Top 778 } 779 if !w.config.Decorated { 780 // Enable drop shadows when we draw decorations. 781 windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1}) 782 } 783 784 case Fullscreen: 785 swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE 786 mi := windows.GetMonitorInfo(w.hwnd) 787 x, y = mi.Monitor.Left, mi.Monitor.Top 788 width = mi.Monitor.Right - mi.Monitor.Left 789 height = mi.Monitor.Bottom - mi.Monitor.Top 790 showMode = windows.SW_SHOWMAXIMIZED 791 } 792 windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style) 793 windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle) 794 windows.ShowWindow(w.hwnd, showMode) 795 w.update() 796 } 797 798 func (w *window) WriteClipboard(mime string, s []byte) { 799 w.writeClipboard(string(s)) 800 } 801 802 func (w *window) writeClipboard(s string) error { 803 if err := windows.OpenClipboard(w.hwnd); err != nil { 804 return err 805 } 806 defer windows.CloseClipboard() 807 if err := windows.EmptyClipboard(); err != nil { 808 return err 809 } 810 u16, err := syscall.UTF16FromString(s) 811 if err != nil { 812 return err 813 } 814 n := len(u16) * int(unsafe.Sizeof(u16[0])) 815 mem, err := windows.GlobalAlloc(n) 816 if err != nil { 817 return err 818 } 819 ptr, err := windows.GlobalLock(mem) 820 if err != nil { 821 windows.GlobalFree(mem) 822 return err 823 } 824 u16v := unsafe.Slice((*uint16)(ptr), len(u16)) 825 copy(u16v, u16) 826 windows.GlobalUnlock(mem) 827 if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil { 828 windows.GlobalFree(mem) 829 return err 830 } 831 return nil 832 } 833 834 func (w *window) SetCursor(cursor pointer.Cursor) { 835 c, err := loadCursor(cursor) 836 if err != nil { 837 c = resources.cursor 838 } 839 w.cursor = c 840 if w.cursorIn { 841 windows.SetCursor(w.cursor) 842 } 843 } 844 845 // windowsCursor contains mapping from pointer.Cursor to an IDC. 846 var windowsCursor = [...]uint16{ 847 pointer.CursorDefault: windows.IDC_ARROW, 848 pointer.CursorNone: 0, 849 pointer.CursorText: windows.IDC_IBEAM, 850 pointer.CursorVerticalText: windows.IDC_IBEAM, 851 pointer.CursorPointer: windows.IDC_HAND, 852 pointer.CursorCrosshair: windows.IDC_CROSS, 853 pointer.CursorAllScroll: windows.IDC_SIZEALL, 854 pointer.CursorColResize: windows.IDC_SIZEWE, 855 pointer.CursorRowResize: windows.IDC_SIZENS, 856 pointer.CursorGrab: windows.IDC_SIZEALL, 857 pointer.CursorGrabbing: windows.IDC_SIZEALL, 858 pointer.CursorNotAllowed: windows.IDC_NO, 859 pointer.CursorWait: windows.IDC_WAIT, 860 pointer.CursorProgress: windows.IDC_APPSTARTING, 861 pointer.CursorNorthWestResize: windows.IDC_SIZENWSE, 862 pointer.CursorNorthEastResize: windows.IDC_SIZENESW, 863 pointer.CursorSouthWestResize: windows.IDC_SIZENESW, 864 pointer.CursorSouthEastResize: windows.IDC_SIZENWSE, 865 pointer.CursorNorthSouthResize: windows.IDC_SIZENS, 866 pointer.CursorEastWestResize: windows.IDC_SIZEWE, 867 pointer.CursorWestResize: windows.IDC_SIZEWE, 868 pointer.CursorEastResize: windows.IDC_SIZEWE, 869 pointer.CursorNorthResize: windows.IDC_SIZENS, 870 pointer.CursorSouthResize: windows.IDC_SIZENS, 871 pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW, 872 pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE, 873 } 874 875 func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) { 876 switch cursor { 877 case pointer.CursorDefault: 878 return resources.cursor, nil 879 case pointer.CursorNone: 880 return 0, nil 881 default: 882 return windows.LoadCursor(windowsCursor[cursor]) 883 } 884 } 885 886 func (w *window) ShowTextInput(show bool) {} 887 888 func (w *window) SetInputHint(_ key.InputHint) {} 889 890 func (w *window) HDC() syscall.Handle { 891 return w.hdc 892 } 893 894 func (w *window) HWND() (syscall.Handle, int, int) { 895 return w.hwnd, w.config.Size.X, w.config.Size.Y 896 } 897 898 func (w *window) Perform(acts system.Action) { 899 walkActions(acts, func(a system.Action) { 900 switch a { 901 case system.ActionCenter: 902 if w.config.Mode != Windowed { 903 break 904 } 905 r := windows.GetWindowRect(w.hwnd) 906 dx := r.Right - r.Left 907 dy := r.Bottom - r.Top 908 // Calculate center position on current monitor. 909 mi := windows.GetMonitorInfo(w.hwnd).Monitor 910 x := (mi.Right - mi.Left - dx) / 2 911 y := (mi.Bottom - mi.Top - dy) / 2 912 windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED) 913 case system.ActionRaise: 914 w.raise() 915 case system.ActionClose: 916 windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0) 917 } 918 }) 919 } 920 921 func (w *window) raise() { 922 windows.SetForegroundWindow(w.hwnd) 923 windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0, 924 windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW) 925 } 926 927 func convertKeyCode(code uintptr) (key.Name, bool) { 928 if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' { 929 return key.Name(rune(code)), true 930 } 931 var r key.Name 932 933 switch code { 934 case windows.VK_ESCAPE: 935 r = key.NameEscape 936 case windows.VK_LEFT: 937 r = key.NameLeftArrow 938 case windows.VK_RIGHT: 939 r = key.NameRightArrow 940 case windows.VK_RETURN: 941 r = key.NameReturn 942 case windows.VK_UP: 943 r = key.NameUpArrow 944 case windows.VK_DOWN: 945 r = key.NameDownArrow 946 case windows.VK_HOME: 947 r = key.NameHome 948 case windows.VK_END: 949 r = key.NameEnd 950 case windows.VK_BACK: 951 r = key.NameDeleteBackward 952 case windows.VK_DELETE: 953 r = key.NameDeleteForward 954 case windows.VK_PRIOR: 955 r = key.NamePageUp 956 case windows.VK_NEXT: 957 r = key.NamePageDown 958 case windows.VK_F1: 959 r = key.NameF1 960 case windows.VK_F2: 961 r = key.NameF2 962 case windows.VK_F3: 963 r = key.NameF3 964 case windows.VK_F4: 965 r = key.NameF4 966 case windows.VK_F5: 967 r = key.NameF5 968 case windows.VK_F6: 969 r = key.NameF6 970 case windows.VK_F7: 971 r = key.NameF7 972 case windows.VK_F8: 973 r = key.NameF8 974 case windows.VK_F9: 975 r = key.NameF9 976 case windows.VK_F10: 977 r = key.NameF10 978 case windows.VK_F11: 979 r = key.NameF11 980 case windows.VK_F12: 981 r = key.NameF12 982 case windows.VK_TAB: 983 r = key.NameTab 984 case windows.VK_SPACE: 985 r = key.NameSpace 986 case windows.VK_OEM_1: 987 r = ";" 988 case windows.VK_OEM_PLUS: 989 r = "+" 990 case windows.VK_OEM_COMMA: 991 r = "," 992 case windows.VK_OEM_MINUS: 993 r = "-" 994 case windows.VK_OEM_PERIOD: 995 r = "." 996 case windows.VK_OEM_2: 997 r = "/" 998 case windows.VK_OEM_3: 999 r = "`" 1000 case windows.VK_OEM_4: 1001 r = "[" 1002 case windows.VK_OEM_5, windows.VK_OEM_102: 1003 r = "\\" 1004 case windows.VK_OEM_6: 1005 r = "]" 1006 case windows.VK_OEM_7: 1007 r = "'" 1008 case windows.VK_CONTROL: 1009 r = key.NameCtrl 1010 case windows.VK_SHIFT: 1011 r = key.NameShift 1012 case windows.VK_MENU: 1013 r = key.NameAlt 1014 case windows.VK_LWIN, windows.VK_RWIN: 1015 r = key.NameSuper 1016 default: 1017 return "", false 1018 } 1019 return r, true 1020 } 1021 1022 func configForDPI(dpi int) unit.Metric { 1023 const inchPrDp = 1.0 / 96.0 1024 ppdp := float32(dpi) * inchPrDp 1025 return unit.Metric{ 1026 PxPerDp: ppdp, 1027 PxPerSp: ppdp, 1028 } 1029 } 1030 1031 func (Win32ViewEvent) implementsViewEvent() {} 1032 func (Win32ViewEvent) ImplementsEvent() {}