os_windows.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package wm
4
5 import (
6 "errors"
7 "fmt"
8 "image"
9 "reflect"
10 "runtime"
11 "sort"
12 "strings"
13 "sync"
14 "time"
15 "unicode"
16 "unsafe"
17
18 syscall "golang.org/x/sys/windows"
19
20 "github.com/p9c/p9/pkg/gel/gio/app/internal/windows"
21 "github.com/p9c/p9/pkg/gel/gio/unit"
22 gowindows "golang.org/x/sys/windows"
23
24 "github.com/p9c/p9/pkg/gel/gio/f32"
25 "github.com/p9c/p9/pkg/gel/gio/io/clipboard"
26 "github.com/p9c/p9/pkg/gel/gio/io/key"
27 "github.com/p9c/p9/pkg/gel/gio/io/pointer"
28 "github.com/p9c/p9/pkg/gel/gio/io/system"
29 )
30
31 type winConstraints struct {
32 minWidth, minHeight int32
33 maxWidth, maxHeight int32
34 }
35
36 type winDeltas struct {
37 width int32
38 height int32
39 }
40
41 type window struct {
42 hwnd syscall.Handle
43 hdc syscall.Handle
44 w Callbacks
45 width int
46 height int
47 stage system.Stage
48 pointerBtns pointer.Buttons
49
50 // cursorIn tracks whether the cursor was inside the window according
51 // to the most recent WM_SETCURSOR.
52 cursorIn bool
53 cursor syscall.Handle
54
55 // placement saves the previous window position when in full screen mode.
56 placement *windows.WindowPlacement
57
58 mu sync.Mutex
59 animating bool
60
61 minmax winConstraints
62 deltas winDeltas
63 opts *Options
64 }
65
66 const (
67 _WM_REDRAW = windows.WM_USER + iota
68 _WM_CURSOR
69 _WM_OPTION
70 )
71
72 type gpuAPI struct {
73 priority int
74 initializer func(w *window) (Context, error)
75 }
76
77 // drivers is the list of potential Context implementations.
78 var drivers []gpuAPI
79
80 // winMap maps win32 HWNDs to *windows.
81 var winMap sync.Map
82
83 // iconID is the ID of the icon in the resource file.
84 const iconID = 1
85
86 var resources struct {
87 once sync.Once
88 // handle is the module handle from GetModuleHandle.
89 handle syscall.Handle
90 // class is the Gio window class from RegisterClassEx.
91 class uint16
92 // cursor is the arrow cursor resource.
93 cursor syscall.Handle
94 }
95
96 func Main() {
97 select {}
98 }
99
100 func NewWindow(window Callbacks, opts *Options) error {
101 cerr := make(chan error)
102 go func() {
103 // GetMessage and PeekMessage can filter on a window HWND, but
104 // then thread-specific messages such as WM_QUIT are ignored.
105 // Instead lock the thread so window messages arrive through
106 // unfiltered GetMessage calls.
107 runtime.LockOSThread()
108 w, err := createNativeWindow(opts)
109 if err != nil {
110 cerr <- err
111 return
112 }
113 defer w.destroy()
114 cerr <- nil
115 winMap.Store(w.hwnd, w)
116 defer winMap.Delete(w.hwnd)
117 w.w = window
118 w.w.SetDriver(w)
119 defer w.w.Event(system.DestroyEvent{})
120 w.Option(opts)
121 windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT)
122 windows.SetForegroundWindow(w.hwnd)
123 windows.SetFocus(w.hwnd)
124 // Since the window class for the cursor is null,
125 // set it here to show the cursor.
126 w.SetCursor(pointer.CursorDefault)
127 if err := w.loop(); err != nil {
128 panic(err)
129 }
130 }()
131 return <-cerr
132 }
133
134 // initResources initializes the resources global.
135 func initResources() error {
136 windows.SetProcessDPIAware()
137 hInst, err := windows.GetModuleHandle()
138 if err != nil {
139 return err
140 }
141 resources.handle = hInst
142 c, err := windows.LoadCursor(windows.IDC_ARROW)
143 if err != nil {
144 return err
145 }
146 resources.cursor = c
147 icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
148 wcls := windows.WndClassEx{
149 CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})),
150 Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
151 LpfnWndProc: syscall.NewCallback(windowProc),
152 HInstance: hInst,
153 HIcon: icon,
154 LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
155 }
156 cls, err := windows.RegisterClassEx(&wcls)
157 if err != nil {
158 return err
159 }
160 resources.class = cls
161 return nil
162 }
163
164 func getWindowConstraints(cfg unit.Metric, opts *Options) winConstraints {
165 var minmax winConstraints
166 if o := opts.MinSize; o != nil {
167 minmax.minWidth = int32(cfg.Px(o.Width))
168 minmax.minHeight = int32(cfg.Px(o.Height))
169 }
170 if o := opts.MaxSize; o != nil {
171 minmax.maxWidth = int32(cfg.Px(o.Width))
172 minmax.maxHeight = int32(cfg.Px(o.Height))
173 }
174 return minmax
175 }
176
177 func createNativeWindow(opts *Options) (*window, error) {
178 var resErr error
179 resources.once.Do(func() {
180 resErr = initResources()
181 })
182 if resErr != nil {
183 return nil, resErr
184 }
185 dpi := windows.GetSystemDPI()
186 cfg := configForDPI(dpi)
187 dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
188 dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
189
190 hwnd, err := windows.CreateWindowEx(dwExStyle,
191 resources.class,
192 "",
193 dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
194 windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
195 windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
196 0,
197 0,
198 resources.handle,
199 0)
200 if err != nil {
201 return nil, err
202 }
203 w := &window{
204 hwnd: hwnd,
205 minmax: getWindowConstraints(cfg, opts),
206 opts: opts,
207 }
208 w.hdc, err = windows.GetDC(hwnd)
209 if err != nil {
210 return nil, err
211 }
212 return w, nil
213 }
214
215 func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
216 win, exists := winMap.Load(hwnd)
217 if !exists {
218 return windows.DefWindowProc(hwnd, msg, wParam, lParam)
219 }
220
221 w := win.(*window)
222
223 switch msg {
224 case windows.WM_UNICHAR:
225 if wParam == windows.UNICODE_NOCHAR {
226 // Tell the system that we accept WM_UNICHAR messages.
227 return windows.TRUE
228 }
229 fallthrough
230 case windows.WM_CHAR:
231 if r := rune(wParam); unicode.IsPrint(r) {
232 w.w.Event(key.EditEvent{Text: string(r)})
233 }
234 // The message is processed.
235 return windows.TRUE
236 case windows.WM_DPICHANGED:
237 // Let Windows know we're prepared for runtime DPI changes.
238 return windows.TRUE
239 case windows.WM_ERASEBKGND:
240 // Avoid flickering between GPU content and background color.
241 return windows.TRUE
242 case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
243 if n, ok := convertKeyCode(wParam); ok {
244 e := key.Event{
245 Name: n,
246 Modifiers: getModifiers(),
247 State: key.Press,
248 }
249 if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
250 e.State = key.Release
251 }
252
253 w.w.Event(e)
254
255 if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
256 // Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
257 // such as cmd.exe and graphical debuggers also reserve F10.
258 return 0
259 }
260 }
261 case windows.WM_LBUTTONDOWN:
262 w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
263 case windows.WM_LBUTTONUP:
264 w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
265 case windows.WM_RBUTTONDOWN:
266 w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
267 case windows.WM_RBUTTONUP:
268 w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
269 case windows.WM_MBUTTONDOWN:
270 w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
271 case windows.WM_MBUTTONUP:
272 w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
273 case windows.WM_CANCELMODE:
274 w.w.Event(pointer.Event{
275 Type: pointer.Cancel,
276 })
277 case windows.WM_SETFOCUS:
278 w.w.Event(key.FocusEvent{Focus: true})
279 case windows.WM_KILLFOCUS:
280 w.w.Event(key.FocusEvent{Focus: false})
281 case windows.WM_MOUSEMOVE:
282 x, y := coordsFromlParam(lParam)
283 p := f32.Point{X: float32(x), Y: float32(y)}
284 w.w.Event(pointer.Event{
285 Type: pointer.Move,
286 Source: pointer.Mouse,
287 Position: p,
288 Buttons: w.pointerBtns,
289 Time: windows.GetMessageTime(),
290 })
291 case windows.WM_MOUSEWHEEL:
292 w.scrollEvent(wParam, lParam, false)
293 case windows.WM_MOUSEHWHEEL:
294 w.scrollEvent(wParam, lParam, true)
295 case windows.WM_DESTROY:
296 windows.PostQuitMessage(0)
297 case windows.WM_PAINT:
298 w.draw(true)
299 case windows.WM_SIZE:
300 switch wParam {
301 case windows.SIZE_MINIMIZED:
302 w.setStage(system.StagePaused)
303 case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED:
304 w.setStage(system.StageRunning)
305 }
306 case windows.WM_GETMINMAXINFO:
307 mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
308 if w.minmax.minWidth > 0 || w.minmax.minHeight > 0 {
309 mm.PtMinTrackSize = windows.Point{
310 X: w.minmax.minWidth + w.deltas.width,
311 Y: w.minmax.minHeight + w.deltas.height,
312 }
313 }
314 if w.minmax.maxWidth > 0 || w.minmax.maxHeight > 0 {
315 mm.PtMaxTrackSize = windows.Point{
316 X: w.minmax.maxWidth + w.deltas.width,
317 Y: w.minmax.maxHeight + w.deltas.height,
318 }
319 }
320 case windows.WM_SETCURSOR:
321 w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
322 fallthrough
323 case _WM_CURSOR:
324 if w.cursorIn {
325 windows.SetCursor(w.cursor)
326 return windows.TRUE
327 }
328 case _WM_OPTION:
329 w.setOptions()
330 }
331
332 return windows.DefWindowProc(hwnd, msg, wParam, lParam)
333 }
334
335 func getModifiers() key.Modifiers {
336 var kmods key.Modifiers
337 if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
338 kmods |= key.ModSuper
339 }
340 if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
341 kmods |= key.ModAlt
342 }
343 if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
344 kmods |= key.ModCtrl
345 }
346 if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
347 kmods |= key.ModShift
348 }
349 return kmods
350 }
351
352 func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
353 var typ pointer.Type
354 if press {
355 typ = pointer.Press
356 if w.pointerBtns == 0 {
357 windows.SetCapture(w.hwnd)
358 }
359 w.pointerBtns |= btn
360 } else {
361 typ = pointer.Release
362 w.pointerBtns &^= btn
363 if w.pointerBtns == 0 {
364 windows.ReleaseCapture()
365 }
366 }
367 x, y := coordsFromlParam(lParam)
368 p := f32.Point{X: float32(x), Y: float32(y)}
369 w.w.Event(pointer.Event{
370 Type: typ,
371 Source: pointer.Mouse,
372 Position: p,
373 Buttons: w.pointerBtns,
374 Time: windows.GetMessageTime(),
375 Modifiers: kmods,
376 })
377 }
378
379 func coordsFromlParam(lParam uintptr) (int, int) {
380 x := int(int16(lParam & 0xffff))
381 y := int(int16((lParam >> 16) & 0xffff))
382 return x, y
383 }
384
385 func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool) {
386 x, y := coordsFromlParam(lParam)
387 // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
388 // to other mouse events.
389 np := windows.Point{X: int32(x), Y: int32(y)}
390 windows.ScreenToClient(w.hwnd, &np)
391 p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
392 dist := float32(int16(wParam >> 16))
393 var sp f32.Point
394 if horizontal {
395 sp.X = dist
396 } else {
397 sp.Y = -dist
398 }
399 w.w.Event(pointer.Event{
400 Type: pointer.Scroll,
401 Source: pointer.Mouse,
402 Position: p,
403 Buttons: w.pointerBtns,
404 Scroll: sp,
405 Time: windows.GetMessageTime(),
406 })
407 }
408
409 // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
410 func (w *window) loop() error {
411 msg := new(windows.Msg)
412 loop:
413 for {
414 w.mu.Lock()
415 anim := w.animating
416 w.mu.Unlock()
417 if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
418 w.draw(false)
419 continue
420 }
421 switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
422 case -1:
423 return errors.New("GetMessage failed")
424 case 0:
425 // WM_QUIT received.
426 break loop
427 }
428 windows.TranslateMessage(msg)
429 windows.DispatchMessage(msg)
430 }
431 return nil
432 }
433
434 func (w *window) SetAnimating(anim bool) {
435 w.mu.Lock()
436 w.animating = anim
437 w.mu.Unlock()
438 if anim {
439 w.postRedraw()
440 }
441 }
442
443 func (w *window) postRedraw() {
444 if err := windows.PostMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil {
445 panic(err)
446 }
447 }
448
449 func (w *window) setStage(s system.Stage) {
450 w.stage = s
451 w.w.Event(system.StageEvent{Stage: s})
452 }
453
454 func (w *window) draw(sync bool) {
455 var r windows.Rect
456 windows.GetClientRect(w.hwnd, &r)
457 w.width = int(r.Right - r.Left)
458 w.height = int(r.Bottom - r.Top)
459 if w.width == 0 || w.height == 0 {
460 return
461 }
462 dpi := windows.GetWindowDPI(w.hwnd)
463 cfg := configForDPI(dpi)
464 w.minmax = getWindowConstraints(cfg, w.opts)
465 w.w.Event(FrameEvent{
466 FrameEvent: system.FrameEvent{
467 Now: time.Now(),
468 Size: image.Point{
469 X: w.width,
470 Y: w.height,
471 },
472 Metric: cfg,
473 },
474 Sync: sync,
475 })
476 }
477
478 func (w *window) destroy() {
479 if w.hdc != 0 {
480 windows.ReleaseDC(w.hdc)
481 w.hdc = 0
482 }
483 if w.hwnd != 0 {
484 windows.DestroyWindow(w.hwnd)
485 w.hwnd = 0
486 }
487 }
488
489 func (w *window) NewContext() (Context, error) {
490 sort.Slice(drivers, func(i, j int) bool {
491 return drivers[i].priority < drivers[j].priority
492 })
493 var errs []string
494 for _, b := range drivers {
495 ctx, err := b.initializer(w)
496 if err == nil {
497 return ctx, nil
498 }
499 errs = append(errs, err.Error())
500 }
501 if len(errs) > 0 {
502 return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
503 }
504 return nil, errors.New("NewContext: no available GPU drivers")
505 }
506
507 func (w *window) ReadClipboard() {
508 w.readClipboard()
509 }
510
511 func (w *window) readClipboard() error {
512 if err := windows.OpenClipboard(w.hwnd); err != nil {
513 return err
514 }
515 defer windows.CloseClipboard()
516 mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
517 if err != nil {
518 return err
519 }
520 ptr, err := windows.GlobalLock(mem)
521 if err != nil {
522 return err
523 }
524 defer windows.GlobalUnlock(mem)
525 content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
526 go func() {
527 w.w.Event(clipboard.Event{Text: content})
528 }()
529 return nil
530 }
531
532 func (w *window) Option(opts *Options) {
533 w.mu.Lock()
534 w.opts = opts
535 w.mu.Unlock()
536 if err := windows.PostMessage(w.hwnd, _WM_OPTION, 0, 0); err != nil {
537 panic(err)
538 }
539 }
540
541 func (w *window) setOptions() {
542 w.mu.Lock()
543 opts := w.opts
544 w.mu.Unlock()
545 if o := opts.Size; o != nil {
546 dpi := windows.GetSystemDPI()
547 cfg := configForDPI(dpi)
548 width := int32(cfg.Px(o.Width))
549 height := int32(cfg.Px(o.Height))
550
551 // Include the window decorations.
552 wr := windows.Rect{
553 Right: width,
554 Bottom: height,
555 }
556 dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
557 dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
558 windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)
559
560 dw, dh := width, height
561 width = wr.Right - wr.Left
562 height = wr.Bottom - wr.Top
563 w.deltas.width = width - dw
564 w.deltas.height = height - dh
565
566 w.opts.Size = o
567 windows.MoveWindow(w.hwnd, 0, 0, width, height, true)
568 }
569 if o := opts.MinSize; o != nil {
570 w.opts.MinSize = o
571 }
572 if o := opts.MaxSize; o != nil {
573 w.opts.MaxSize = o
574 }
575 if o := opts.Title; o != nil {
576 windows.SetWindowText(w.hwnd, *opts.Title)
577 }
578 if o := opts.WindowMode; o != nil {
579 w.SetWindowMode(*o)
580 }
581 }
582
583 func (w *window) SetWindowMode(mode WindowMode) {
584 // https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
585 switch mode {
586 case Windowed:
587 if w.placement == nil {
588 return
589 }
590 windows.SetWindowPlacement(w.hwnd, w.placement)
591 w.placement = nil
592 style := windows.GetWindowLong(w.hwnd)
593 windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW)
594 windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST,
595 0, 0, 0, 0,
596 windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
597 )
598 case Fullscreen:
599 if w.placement != nil {
600 return
601 }
602 w.placement = windows.GetWindowPlacement(w.hwnd)
603 style := windows.GetWindowLong(w.hwnd)
604 windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style&^windows.WS_OVERLAPPEDWINDOW)
605 mi := windows.GetMonitorInfo(w.hwnd)
606 windows.SetWindowPos(w.hwnd, 0,
607 mi.Monitor.Left, mi.Monitor.Top,
608 mi.Monitor.Right-mi.Monitor.Left,
609 mi.Monitor.Bottom-mi.Monitor.Top,
610 windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
611 )
612 }
613 }
614
615 func (w *window) WriteClipboard(s string) {
616 w.writeClipboard(s)
617 }
618
619 func (w *window) writeClipboard(s string) error {
620 if err := windows.OpenClipboard(w.hwnd); err != nil {
621 return err
622 }
623 defer windows.CloseClipboard()
624 if err := windows.EmptyClipboard(); err != nil {
625 return err
626 }
627 u16, err := gowindows.UTF16FromString(s)
628 if err != nil {
629 return err
630 }
631 n := len(u16) * int(unsafe.Sizeof(u16[0]))
632 mem, err := windows.GlobalAlloc(n)
633 if err != nil {
634 return err
635 }
636 ptr, err := windows.GlobalLock(mem)
637 if err != nil {
638 windows.GlobalFree(mem)
639 return err
640 }
641 var u16v []uint16
642 hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16v))
643 hdr.Data = ptr
644 hdr.Cap = len(u16)
645 hdr.Len = len(u16)
646 copy(u16v, u16)
647 windows.GlobalUnlock(mem)
648 if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
649 windows.GlobalFree(mem)
650 return err
651 }
652 return nil
653 }
654
655 func (w *window) SetCursor(name pointer.CursorName) {
656 c, err := loadCursor(name)
657 if err != nil {
658 c = resources.cursor
659 }
660 w.cursor = c
661 if err := windows.PostMessage(w.hwnd, _WM_CURSOR, 0, 0); err != nil {
662 panic(err)
663 }
664 }
665
666 func loadCursor(name pointer.CursorName) (syscall.Handle, error) {
667 var curID uint16
668 switch name {
669 default:
670 fallthrough
671 case pointer.CursorDefault:
672 return resources.cursor, nil
673 case pointer.CursorText:
674 curID = windows.IDC_IBEAM
675 case pointer.CursorPointer:
676 curID = windows.IDC_HAND
677 case pointer.CursorCrossHair:
678 curID = windows.IDC_CROSS
679 case pointer.CursorColResize:
680 curID = windows.IDC_SIZEWE
681 case pointer.CursorRowResize:
682 curID = windows.IDC_SIZENS
683 case pointer.CursorGrab:
684 curID = windows.IDC_SIZEALL
685 case pointer.CursorNone:
686 return 0, nil
687 }
688 return windows.LoadCursor(curID)
689 }
690
691 func (w *window) ShowTextInput(show bool) {}
692
693 func (w *window) HDC() syscall.Handle {
694 return w.hdc
695 }
696
697 func (w *window) HWND() (syscall.Handle, int, int) {
698 return w.hwnd, w.width, w.height
699 }
700
701 func (w *window) Close() {
702 windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
703 }
704
705 func convertKeyCode(code uintptr) (string, bool) {
706 if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
707 return string(rune(code)), true
708 }
709 var r string
710 switch code {
711 case windows.VK_ESCAPE:
712 r = key.NameEscape
713 case windows.VK_LEFT:
714 r = key.NameLeftArrow
715 case windows.VK_RIGHT:
716 r = key.NameRightArrow
717 case windows.VK_RETURN:
718 r = key.NameReturn
719 case windows.VK_UP:
720 r = key.NameUpArrow
721 case windows.VK_DOWN:
722 r = key.NameDownArrow
723 case windows.VK_HOME:
724 r = key.NameHome
725 case windows.VK_END:
726 r = key.NameEnd
727 case windows.VK_BACK:
728 r = key.NameDeleteBackward
729 case windows.VK_DELETE:
730 r = key.NameDeleteForward
731 case windows.VK_PRIOR:
732 r = key.NamePageUp
733 case windows.VK_NEXT:
734 r = key.NamePageDown
735 case windows.VK_F1:
736 r = "F1"
737 case windows.VK_F2:
738 r = "F2"
739 case windows.VK_F3:
740 r = "F3"
741 case windows.VK_F4:
742 r = "F4"
743 case windows.VK_F5:
744 r = "F5"
745 case windows.VK_F6:
746 r = "F6"
747 case windows.VK_F7:
748 r = "F7"
749 case windows.VK_F8:
750 r = "F8"
751 case windows.VK_F9:
752 r = "F9"
753 case windows.VK_F10:
754 r = "F10"
755 case windows.VK_F11:
756 r = "F11"
757 case windows.VK_F12:
758 r = "F12"
759 case windows.VK_TAB:
760 r = key.NameTab
761 case windows.VK_SPACE:
762 r = key.NameSpace
763 case windows.VK_OEM_1:
764 r = ";"
765 case windows.VK_OEM_PLUS:
766 r = "+"
767 case windows.VK_OEM_COMMA:
768 r = ","
769 case windows.VK_OEM_MINUS:
770 r = "-"
771 case windows.VK_OEM_PERIOD:
772 r = "."
773 case windows.VK_OEM_2:
774 r = "/"
775 case windows.VK_OEM_3:
776 r = "`"
777 case windows.VK_OEM_4:
778 r = "["
779 case windows.VK_OEM_5, windows.VK_OEM_102:
780 r = "\\"
781 case windows.VK_OEM_6:
782 r = "]"
783 case windows.VK_OEM_7:
784 r = "'"
785 default:
786 return "", false
787 }
788 return r, true
789 }
790
791 func configForDPI(dpi int) unit.Metric {
792 const inchPrDp = 1.0 / 96.0
793 ppdp := float32(dpi) * inchPrDp
794 return unit.Metric{
795 PxPerDp: ppdp,
796 PxPerSp: ppdp,
797 }
798 }
799