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