github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/app/internal/window/os_windows.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package window 4 5 import ( 6 "errors" 7 "image" 8 "runtime" 9 "sync" 10 "time" 11 "unicode" 12 "unsafe" 13 14 syscall "golang.org/x/sys/windows" 15 16 "github.com/gop9/olt/gio/app/internal/windows" 17 18 "github.com/gop9/olt/gio/f32" 19 "github.com/gop9/olt/gio/io/key" 20 "github.com/gop9/olt/gio/io/pointer" 21 "github.com/gop9/olt/gio/io/system" 22 ) 23 24 var winMap = make(map[syscall.Handle]*window) 25 26 type window struct { 27 hwnd syscall.Handle 28 hdc syscall.Handle 29 w Callbacks 30 width int 31 height int 32 stage system.Stage 33 dead bool 34 pointerBtns pointer.Buttons 35 36 mu sync.Mutex 37 animating bool 38 } 39 40 const _WM_REDRAW = windows.WM_USER + 0 41 42 var onceMu sync.Mutex 43 var mainDone = make(chan struct{}) 44 45 func Main() { 46 <-mainDone 47 } 48 49 func NewWindow(window Callbacks, opts *Options) error { 50 onceMu.Lock() 51 defer onceMu.Unlock() 52 if len(winMap) > 0 { 53 return errors.New("multiple windows are not supported") 54 } 55 cerr := make(chan error) 56 go func() { 57 // Call win32 API from a single OS thread. 58 runtime.LockOSThread() 59 w, err := createNativeWindow(opts) 60 if err != nil { 61 cerr <- err 62 return 63 } 64 defer w.destroy() 65 cerr <- nil 66 winMap[w.hwnd] = w 67 defer delete(winMap, w.hwnd) 68 w.w = window 69 w.w.SetDriver(w) 70 defer w.w.Event(system.DestroyEvent{}) 71 windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT) 72 windows.SetForegroundWindow(w.hwnd) 73 windows.SetFocus(w.hwnd) 74 if err := w.loop(); err != nil { 75 panic(err) 76 } 77 close(mainDone) 78 }() 79 return <-cerr 80 } 81 82 func createNativeWindow(opts *Options) (*window, error) { 83 windows.SetProcessDPIAware() 84 cfg := configForDC() 85 hInst, err := windows.GetModuleHandle() 86 if err != nil { 87 return nil, err 88 } 89 curs, err := windows.LoadCursor(windows.IDC_ARROW) 90 if err != nil { 91 return nil, err 92 } 93 wcls := windows.WndClassEx{ 94 CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})), 95 Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC, 96 LpfnWndProc: syscall.NewCallback(windowProc), 97 HInstance: hInst, 98 HCursor: curs, 99 LpszClassName: syscall.StringToUTF16Ptr("GioWindow"), 100 } 101 cls, err := windows.RegisterClassEx(&wcls) 102 if err != nil { 103 return nil, err 104 } 105 wr := windows.Rect{ 106 Right: int32(cfg.Px(opts.Width)), 107 Bottom: int32(cfg.Px(opts.Height)), 108 } 109 dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW) 110 dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE) 111 windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle) 112 hwnd, err := windows.CreateWindowEx(dwExStyle, 113 cls, 114 opts.Title, 115 dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN, 116 windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, 117 wr.Right-wr.Left, 118 wr.Bottom-wr.Top, 119 0, 120 0, 121 hInst, 122 0) 123 if err != nil { 124 return nil, err 125 } 126 w := &window{ 127 hwnd: hwnd, 128 } 129 w.hdc, err = windows.GetDC(hwnd) 130 if err != nil { 131 return nil, err 132 } 133 return w, nil 134 } 135 136 func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { 137 w := winMap[hwnd] 138 switch msg { 139 case windows.WM_UNICHAR: 140 if wParam == windows.UNICODE_NOCHAR { 141 // Tell the system that we accept WM_UNICHAR messages. 142 return 1 143 } 144 fallthrough 145 case windows.WM_CHAR: 146 if r := rune(wParam); unicode.IsPrint(r) { 147 w.w.Event(key.EditEvent{Text: string(r)}) 148 } 149 // The message is processed. 150 return 1 151 case windows.WM_DPICHANGED: 152 // Let Windows know we're prepared for runtime DPI changes. 153 return 1 154 case windows.WM_ERASEBKGND: 155 // Avoid flickering between GPU content and background color. 156 return 1 157 case windows.WM_KEYDOWN, windows.WM_SYSKEYDOWN: 158 if n, ok := convertKeyCode(wParam); ok { 159 w.w.Event(key.Event{Name: n, Modifiers: getModifiers()}) 160 } 161 case windows.WM_LBUTTONDOWN: 162 w.pointerButton(pointer.ButtonLeft, true, lParam, getModifiers()) 163 case windows.WM_LBUTTONUP: 164 w.pointerButton(pointer.ButtonLeft, false, lParam, getModifiers()) 165 case windows.WM_RBUTTONDOWN: 166 w.pointerButton(pointer.ButtonRight, true, lParam, getModifiers()) 167 case windows.WM_RBUTTONUP: 168 w.pointerButton(pointer.ButtonRight, false, lParam, getModifiers()) 169 case windows.WM_MBUTTONDOWN: 170 w.pointerButton(pointer.ButtonMiddle, true, lParam, getModifiers()) 171 case windows.WM_MBUTTONUP: 172 w.pointerButton(pointer.ButtonMiddle, false, lParam, getModifiers()) 173 case windows.WM_CANCELMODE: 174 w.w.Event(pointer.Event{ 175 Type: pointer.Cancel, 176 }) 177 case windows.WM_SETFOCUS: 178 w.w.Event(key.FocusEvent{Focus: true}) 179 case windows.WM_KILLFOCUS: 180 w.w.Event(key.FocusEvent{Focus: false}) 181 case windows.WM_MOUSEMOVE: 182 x, y := coordsFromlParam(lParam) 183 p := f32.Point{X: float32(x), Y: float32(y)} 184 w.w.Event(pointer.Event{ 185 Type: pointer.Move, 186 Source: pointer.Mouse, 187 Position: p, 188 Time: windows.GetMessageTime(), 189 }) 190 case windows.WM_MOUSEWHEEL: 191 w.scrollEvent(wParam, lParam) 192 case windows.WM_DESTROY: 193 w.dead = true 194 case windows.WM_PAINT: 195 w.draw(true) 196 case windows.WM_SIZE: 197 switch wParam { 198 case windows.SIZE_MINIMIZED: 199 w.setStage(system.StagePaused) 200 case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED: 201 w.setStage(system.StageRunning) 202 } 203 } 204 return windows.DefWindowProc(hwnd, msg, wParam, lParam) 205 } 206 207 func getModifiers() key.Modifiers { 208 var kmods key.Modifiers 209 if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 { 210 kmods |= key.ModSuper 211 } 212 if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 { 213 kmods |= key.ModAlt 214 } 215 if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 { 216 kmods |= key.ModCtrl 217 } 218 if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 { 219 kmods |= key.ModShift 220 } 221 return kmods 222 } 223 224 func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) { 225 var typ pointer.Type 226 if press { 227 typ = pointer.Press 228 if w.pointerBtns == 0 { 229 windows.SetCapture(w.hwnd) 230 } 231 w.pointerBtns |= btn 232 } else { 233 typ = pointer.Release 234 w.pointerBtns &^= btn 235 if w.pointerBtns == 0 { 236 windows.ReleaseCapture() 237 } 238 } 239 x, y := coordsFromlParam(lParam) 240 p := f32.Point{X: float32(x), Y: float32(y)} 241 w.w.Event(pointer.Event{ 242 Type: typ, 243 Source: pointer.Mouse, 244 Position: p, 245 Buttons: w.pointerBtns, 246 Time: windows.GetMessageTime(), 247 Modifiers: kmods, 248 }) 249 } 250 251 func coordsFromlParam(lParam uintptr) (int, int) { 252 x := int(int16(lParam & 0xffff)) 253 y := int(int16((lParam >> 16) & 0xffff)) 254 return x, y 255 } 256 257 func (w *window) scrollEvent(wParam, lParam uintptr) { 258 x, y := coordsFromlParam(lParam) 259 // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast 260 // to other mouse events. 261 np := windows.Point{X: int32(x), Y: int32(y)} 262 windows.ScreenToClient(w.hwnd, &np) 263 p := f32.Point{X: float32(np.X), Y: float32(np.Y)} 264 dist := float32(int16(wParam >> 16)) 265 w.w.Event(pointer.Event{ 266 Type: pointer.Move, 267 Source: pointer.Mouse, 268 Position: p, 269 Scroll: f32.Point{Y: -dist}, 270 Time: windows.GetMessageTime(), 271 }) 272 } 273 274 // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/ 275 func (w *window) loop() error { 276 msg := new(windows.Msg) 277 for !w.dead { 278 w.mu.Lock() 279 anim := w.animating 280 w.mu.Unlock() 281 if anim && !windows.PeekMessage(msg, w.hwnd, 0, 0, windows.PM_NOREMOVE) { 282 w.draw(false) 283 continue 284 } 285 windows.GetMessage(msg, w.hwnd, 0, 0) 286 if msg.Message == windows.WM_QUIT { 287 windows.PostQuitMessage(msg.WParam) 288 break 289 } 290 windows.TranslateMessage(msg) 291 windows.DispatchMessage(msg) 292 } 293 return nil 294 } 295 296 func (w *window) SetAnimating(anim bool) { 297 w.mu.Lock() 298 w.animating = anim 299 w.mu.Unlock() 300 if anim { 301 w.postRedraw() 302 } 303 } 304 305 func (w *window) postRedraw() { 306 if err := windows.PostMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil { 307 panic(err) 308 } 309 } 310 311 func (w *window) setStage(s system.Stage) { 312 w.stage = s 313 w.w.Event(system.StageEvent{Stage: s}) 314 } 315 316 func (w *window) draw(sync bool) { 317 var r windows.Rect 318 windows.GetClientRect(w.hwnd, &r) 319 w.width = int(r.Right - r.Left) 320 w.height = int(r.Bottom - r.Top) 321 cfg := configForDC() 322 cfg.now = time.Now() 323 w.w.Event(FrameEvent{ 324 FrameEvent: system.FrameEvent{ 325 Size: image.Point{ 326 X: w.width, 327 Y: w.height, 328 }, 329 Config: &cfg, 330 }, 331 Sync: sync, 332 }) 333 } 334 335 func (w *window) destroy() { 336 if w.hdc != 0 { 337 windows.ReleaseDC(w.hdc) 338 w.hdc = 0 339 } 340 if w.hwnd != 0 { 341 windows.DestroyWindow(w.hwnd) 342 w.hwnd = 0 343 } 344 } 345 346 func (w *window) ShowTextInput(show bool) {} 347 348 func (w *window) HDC() syscall.Handle { 349 return w.hdc 350 } 351 352 func (w *window) HWND() (syscall.Handle, int, int) { 353 return w.hwnd, w.width, w.height 354 } 355 356 func convertKeyCode(code uintptr) (string, bool) { 357 if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' { 358 return string(code), true 359 } 360 var r string 361 switch code { 362 case windows.VK_ESCAPE: 363 r = key.NameEscape 364 case windows.VK_LEFT: 365 r = key.NameLeftArrow 366 case windows.VK_RIGHT: 367 r = key.NameRightArrow 368 case windows.VK_RETURN: 369 r = key.NameReturn 370 case windows.VK_UP: 371 r = key.NameUpArrow 372 case windows.VK_DOWN: 373 r = key.NameDownArrow 374 case windows.VK_HOME: 375 r = key.NameHome 376 case windows.VK_END: 377 r = key.NameEnd 378 case windows.VK_BACK: 379 r = key.NameDeleteBackward 380 case windows.VK_DELETE: 381 r = key.NameDeleteForward 382 case windows.VK_PRIOR: 383 r = key.NamePageUp 384 case windows.VK_NEXT: 385 r = key.NamePageDown 386 case windows.VK_F1: 387 r = "F1" 388 case windows.VK_F2: 389 r = "F2" 390 case windows.VK_F3: 391 r = "F3" 392 case windows.VK_F4: 393 r = "F4" 394 case windows.VK_F5: 395 r = "F5" 396 case windows.VK_F6: 397 r = "F6" 398 case windows.VK_F7: 399 r = "F7" 400 case windows.VK_F8: 401 r = "F8" 402 case windows.VK_F9: 403 r = "F9" 404 case windows.VK_F10: 405 r = "F10" 406 case windows.VK_F11: 407 r = "F11" 408 case windows.VK_F12: 409 r = "F12" 410 case windows.VK_TAB: 411 r = key.NameTab 412 case windows.VK_SPACE: 413 r = "Space" 414 case windows.VK_OEM_1: 415 r = ";" 416 case windows.VK_OEM_PLUS: 417 r = "+" 418 case windows.VK_OEM_COMMA: 419 r = "," 420 case windows.VK_OEM_MINUS: 421 r = "-" 422 case windows.VK_OEM_PERIOD: 423 r = "." 424 case windows.VK_OEM_2: 425 r = "/" 426 case windows.VK_OEM_3: 427 r = "`" 428 case windows.VK_OEM_4: 429 r = "[" 430 case windows.VK_OEM_5, windows.VK_OEM_102: 431 r = "\\" 432 case windows.VK_OEM_6: 433 r = "]" 434 case windows.VK_OEM_7: 435 r = "'" 436 default: 437 return "", false 438 } 439 return r, true 440 } 441 442 func configForDC() config { 443 dpi := windows.GetSystemDPI() 444 const inchPrDp = 1.0 / 96.0 445 ppdp := float32(dpi) * inchPrDp 446 return config{ 447 pxPerDp: ppdp, 448 pxPerSp: ppdp, 449 } 450 }