// SPDX-License-Identifier: Unlicense OR MIT package app import ( "errors" "fmt" "image" "io" "runtime" "sort" "strings" "sync" "time" "unicode" "unicode/utf8" "unsafe" syscall "golang.org/x/sys/windows" "gioui.org/app/internal/windows" "gioui.org/op" "gioui.org/unit" gowindows "golang.org/x/sys/windows" "gioui.org/f32" "gioui.org/io/event" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" "gioui.org/io/transfer" ) type Win32ViewEvent struct { HWND uintptr } type window struct { hwnd syscall.Handle hdc syscall.Handle w *callbacks pointerBtns pointer.Buttons // cursorIn tracks whether the cursor was inside the window according // to the most recent WM_SETCURSOR. cursorIn bool cursor syscall.Handle animating bool borderSize image.Point config Config // frameDims stores the last seen window frame width and height. frameDims image.Point loop *eventLoop } const _WM_WAKEUP = windows.WM_USER + iota type gpuAPI struct { priority int initializer func(w *window) (context, error) } // drivers is the list of potential Context implementations. var drivers []gpuAPI // winMap maps win32 HWNDs to *windows. var winMap sync.Map // iconID is the ID of the icon in the resource file. const iconID = 1 var resources struct { once sync.Once // handle is the module handle from GetModuleHandle. handle syscall.Handle // class is the Gio window class from RegisterClassEx. class uint16 // cursor is the arrow cursor resource. cursor syscall.Handle } func osMain() { select {} } func newWindow(win *callbacks, options []Option) { done := make(chan struct{}) go func() { // GetMessage and PeekMessage can filter on a window HWND, but // then thread-specific messages such as WM_QUIT are ignored. // Instead lock the thread so window messages arrive through // unfiltered GetMessage calls. runtime.LockOSThread() w := &window{ w: win, } w.loop = newEventLoop(w.w, w.wakeup) w.w.SetDriver(w) err := w.init() done <- struct{}{} if err != nil { w.ProcessEvent(DestroyEvent{Err: err}) return } winMap.Store(w.hwnd, w) defer winMap.Delete(w.hwnd) w.Configure(options) w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)}) windows.SetForegroundWindow(w.hwnd) windows.SetFocus(w.hwnd) // Since the window class for the cursor is null, // set it here to show the cursor. w.SetCursor(pointer.CursorDefault) w.runLoop() }() <-done } // initResources initializes the resources global. func initResources() error { windows.SetProcessDPIAware() hInst, err := windows.GetModuleHandle() if err != nil { return err } resources.handle = hInst c, err := windows.LoadCursor(windows.IDC_ARROW) if err != nil { return err } resources.cursor = c icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED) wcls := windows.WndClassEx{ CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})), Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC, LpfnWndProc: syscall.NewCallback(windowProc), HInstance: hInst, HIcon: icon, LpszClassName: syscall.StringToUTF16Ptr("GioWindow"), } cls, err := windows.RegisterClassEx(&wcls) if err != nil { return err } resources.class = cls return nil } const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE func (w *window) init() error { var resErr error resources.once.Do(func() { resErr = initResources() }) if resErr != nil { return resErr } const dwStyle = windows.WS_OVERLAPPEDWINDOW hwnd, err := windows.CreateWindowEx( dwExStyle, resources.class, "", dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN, windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, 0, 0, resources.handle, 0) if err != nil { return err } w.hdc, err = windows.GetDC(hwnd) if err != nil { windows.DestroyWindow(hwnd) return err } w.hwnd = hwnd return nil } // update handles changes done by the user, and updates the configuration. // It reads the window style and size/position and updates w.config. // If anything has changed it emits a ConfigEvent to notify the application. func (w *window) update() { p := windows.GetWindowPlacement(w.hwnd) if !p.IsMinimized() { r := windows.GetWindowRect(w.hwnd) cr := windows.GetClientRect(w.hwnd) w.config.Size = image.Point{ X: int(cr.Right - cr.Left), Y: int(cr.Bottom - cr.Top), } w.frameDims = image.Point{ X: int(r.Right - r.Left), Y: int(r.Bottom - r.Top), }.Sub(w.config.Size) } w.borderSize = image.Pt( windows.GetSystemMetrics(windows.SM_CXSIZEFRAME), windows.GetSystemMetrics(windows.SM_CYSIZEFRAME), ) style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE) switch { case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0: w.config.Mode = Maximized case p.IsMaximized(): w.config.Mode = Fullscreen default: w.config.Mode = Windowed } w.ProcessEvent(ConfigEvent{Config: w.config}) w.draw(true) } func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { win, exists := winMap.Load(hwnd) if !exists { return windows.DefWindowProc(hwnd, msg, wParam, lParam) } w := win.(*window) switch msg { case windows.WM_UNICHAR: if wParam == windows.UNICODE_NOCHAR { // Tell the system that we accept WM_UNICHAR messages. return windows.TRUE } fallthrough case windows.WM_CHAR: if r := rune(wParam); unicode.IsPrint(r) { w.w.EditorInsert(string(r)) } // The message is processed. return windows.TRUE case windows.WM_DPICHANGED: // Let Windows know we're prepared for runtime DPI changes. return windows.TRUE case windows.WM_ERASEBKGND: // Avoid flickering between GPU content and background color. return windows.TRUE case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP: if n, ok := convertKeyCode(wParam); ok { e := key.Event{ Name: n, Modifiers: getModifiers(), State: key.Press, } if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP { e.State = key.Release } w.ProcessEvent(e) if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) { // Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs // such as cmd.exe and graphical debuggers also reserve F10. return 0 } } case windows.WM_LBUTTONDOWN: w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers()) case windows.WM_LBUTTONUP: w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers()) case windows.WM_RBUTTONDOWN: w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers()) case windows.WM_RBUTTONUP: w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers()) case windows.WM_MBUTTONDOWN: w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers()) case windows.WM_MBUTTONUP: w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers()) case windows.WM_CANCELMODE: w.ProcessEvent(pointer.Event{ Kind: pointer.Cancel, }) case windows.WM_SETFOCUS: w.config.Focused = true w.ProcessEvent(ConfigEvent{Config: w.config}) case windows.WM_KILLFOCUS: w.config.Focused = false w.ProcessEvent(ConfigEvent{Config: w.config}) case windows.WM_NCHITTEST: if w.config.Decorated { // Let the system handle it. break } x, y := coordsFromlParam(lParam) np := windows.Point{X: int32(x), Y: int32(y)} windows.ScreenToClient(w.hwnd, &np) return w.hitTest(int(np.X), int(np.Y)) case windows.WM_MOUSEMOVE: x, y := coordsFromlParam(lParam) p := f32.Point{X: float32(x), Y: float32(y)} w.ProcessEvent(pointer.Event{ Kind: pointer.Move, Source: pointer.Mouse, Position: p, Buttons: w.pointerBtns, Time: windows.GetMessageTime(), Modifiers: getModifiers(), }) case windows.WM_MOUSEWHEEL: w.scrollEvent(wParam, lParam, false, getModifiers()) case windows.WM_MOUSEHWHEEL: w.scrollEvent(wParam, lParam, true, getModifiers()) case windows.WM_DESTROY: w.ProcessEvent(Win32ViewEvent{}) w.ProcessEvent(DestroyEvent{}) w.w = nil if w.hdc != 0 { windows.ReleaseDC(w.hdc) w.hdc = 0 } // The system destroys the HWND for us. w.hwnd = 0 windows.PostQuitMessage(0) return 0 case windows.WM_NCCALCSIZE: if w.config.Decorated { // Let Windows handle decorations. break } // No client areas; we draw decorations ourselves. if wParam != 1 { return 0 } // lParam contains an NCCALCSIZE_PARAMS for us to adjust. place := windows.GetWindowPlacement(w.hwnd) if !place.IsMaximized() { // Nothing do adjust. return 0 } // Adjust window position to avoid the extra padding in maximized // state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543. // Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows. szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam)) mi := windows.GetMonitorInfo(w.hwnd) szp.Rgrc[0] = mi.WorkArea return 0 case windows.WM_PAINT: w.draw(true) case windows.WM_STYLECHANGED: w.update() case windows.WM_WINDOWPOSCHANGED: w.update() case windows.WM_SIZE: w.update() case windows.WM_GETMINMAXINFO: mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam)) var frameDims image.Point if w.config.Decorated { frameDims = w.frameDims } if p := w.config.MinSize; p.X > 0 || p.Y > 0 { p = p.Add(frameDims) mm.PtMinTrackSize = windows.Point{ X: int32(p.X), Y: int32(p.Y), } } if p := w.config.MaxSize; p.X > 0 || p.Y > 0 { p = p.Add(frameDims) mm.PtMaxTrackSize = windows.Point{ X: int32(p.X), Y: int32(p.Y), } } return 0 case windows.WM_SETCURSOR: w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT if w.cursorIn { windows.SetCursor(w.cursor) return windows.TRUE } case _WM_WAKEUP: w.loop.Wakeup() w.loop.FlushEvents() case windows.WM_IME_STARTCOMPOSITION: imc := windows.ImmGetContext(w.hwnd) if imc == 0 { return windows.TRUE } defer windows.ImmReleaseContext(w.hwnd, imc) sel := w.w.EditorState().Selection caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent))) icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5)) windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y) windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y) case windows.WM_IME_COMPOSITION: imc := windows.ImmGetContext(w.hwnd) if imc == 0 { return windows.TRUE } defer windows.ImmReleaseContext(w.hwnd, imc) state := w.w.EditorState() rng := state.compose if rng.Start == -1 { rng = state.Selection.Range } if rng.Start > rng.End { rng.Start, rng.End = rng.End, rng.Start } var replacement string switch { case lParam&windows.GCS_RESULTSTR != 0: replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR) case lParam&windows.GCS_COMPSTR != 0: replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR) } end := rng.Start + utf8.RuneCountInString(replacement) w.w.EditorReplace(rng, replacement) state = w.w.EditorState() comp := key.Range{ Start: rng.Start, End: end, } if lParam&windows.GCS_DELTASTART != 0 { start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART) comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start) } w.w.SetComposingRegion(comp) pos := end if lParam&windows.GCS_CURSORPOS != 0 { rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS) pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel) } w.w.SetEditorSelection(key.Range{Start: pos, End: pos}) return windows.TRUE case windows.WM_IME_ENDCOMPOSITION: w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) return windows.TRUE } return windows.DefWindowProc(hwnd, msg, wParam, lParam) } func getModifiers() key.Modifiers { var kmods key.Modifiers if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 { kmods |= key.ModSuper } if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 { kmods |= key.ModAlt } if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 { kmods |= key.ModCtrl } if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 { kmods |= key.ModShift } return kmods } // hitTest returns the non-client area hit by the point, needed to // process WM_NCHITTEST. func (w *window) hitTest(x, y int) uintptr { if w.config.Mode != Windowed { // Only windowed mode should allow resizing. return windows.HTCLIENT } // Check for resize handle before system actions; otherwise it can be impossible to // resize a custom-decorations window when the system move area is flush with the // edge of the window. top := y <= w.borderSize.Y bottom := y >= w.config.Size.Y-w.borderSize.Y left := x <= w.borderSize.X right := x >= w.config.Size.X-w.borderSize.X switch { case top && left: return windows.HTTOPLEFT case top && right: return windows.HTTOPRIGHT case bottom && left: return windows.HTBOTTOMLEFT case bottom && right: return windows.HTBOTTOMRIGHT case top: return windows.HTTOP case bottom: return windows.HTBOTTOM case left: return windows.HTLEFT case right: return windows.HTRIGHT } p := f32.Pt(float32(x), float32(y)) if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove { return windows.HTCAPTION } return windows.HTCLIENT } func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) { if !w.config.Focused { windows.SetFocus(w.hwnd) } var kind pointer.Kind if press { kind = pointer.Press if w.pointerBtns == 0 { windows.SetCapture(w.hwnd) } w.pointerBtns |= btn } else { kind = pointer.Release w.pointerBtns &^= btn if w.pointerBtns == 0 { windows.ReleaseCapture() } } x, y := coordsFromlParam(lParam) p := f32.Point{X: float32(x), Y: float32(y)} w.ProcessEvent(pointer.Event{ Kind: kind, Source: pointer.Mouse, Position: p, Buttons: w.pointerBtns, Time: windows.GetMessageTime(), Modifiers: kmods, }) } func coordsFromlParam(lParam uintptr) (int, int) { x := int(int16(lParam & 0xffff)) y := int(int16((lParam >> 16) & 0xffff)) return x, y } func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) { x, y := coordsFromlParam(lParam) // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast // to other mouse events. np := windows.Point{X: int32(x), Y: int32(y)} windows.ScreenToClient(w.hwnd, &np) p := f32.Point{X: float32(np.X), Y: float32(np.Y)} dist := float32(int16(wParam >> 16)) var sp f32.Point if horizontal { sp.X = dist } else { // support horizontal scroll (shift + mousewheel) if kmods == key.ModShift { sp.X = -dist } else { sp.Y = -dist } } w.ProcessEvent(pointer.Event{ Kind: pointer.Scroll, Source: pointer.Mouse, Position: p, Buttons: w.pointerBtns, Scroll: sp, Modifiers: kmods, Time: windows.GetMessageTime(), }) } // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/ func (w *window) runLoop() { msg := new(windows.Msg) loop: for { anim := w.animating p := windows.GetWindowPlacement(w.hwnd) if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) { w.draw(false) continue } switch ret := windows.GetMessage(msg, 0, 0, 0); ret { case -1: panic(errors.New("GetMessage failed")) case 0: // WM_QUIT received. break loop } windows.TranslateMessage(msg) windows.DispatchMessage(msg) } } func (w *window) EditorStateChanged(old, new editorState) { imc := windows.ImmGetContext(w.hwnd) if imc == 0 { return } defer windows.ImmReleaseContext(w.hwnd, imc) if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet { windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0) } } func (w *window) SetAnimating(anim bool) { w.animating = anim } func (w *window) ProcessEvent(e event.Event) { w.w.ProcessEvent(e) w.loop.FlushEvents() } func (w *window) Event() event.Event { return w.loop.Event() } func (w *window) Invalidate() { w.loop.Invalidate() } func (w *window) Run(f func()) { w.loop.Run(f) } func (w *window) Frame(frame *op.Ops) { w.loop.Frame(frame) } func (w *window) wakeup() { if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil { panic(err) } } func (w *window) draw(sync bool) { if w.config.Size.X == 0 || w.config.Size.Y == 0 { return } dpi := windows.GetWindowDPI(w.hwnd) cfg := configForDPI(dpi) w.ProcessEvent(frameEvent{ FrameEvent: FrameEvent{ Now: time.Now(), Size: w.config.Size, Metric: cfg, }, Sync: sync, }) } func (w *window) NewContext() (context, error) { sort.Slice(drivers, func(i, j int) bool { return drivers[i].priority < drivers[j].priority }) var errs []string for _, b := range drivers { ctx, err := b.initializer(w) if err == nil { return ctx, nil } errs = append(errs, err.Error()) } if len(errs) > 0 { return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", ")) } return nil, errors.New("NewContext: no available GPU drivers") } func (w *window) ReadClipboard() { w.readClipboard() } func (w *window) readClipboard() error { if err := windows.OpenClipboard(w.hwnd); err != nil { return err } defer windows.CloseClipboard() mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT) if err != nil { return err } ptr, err := windows.GlobalLock(mem) if err != nil { return err } defer windows.GlobalUnlock(mem) content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr))) w.ProcessEvent(transfer.DataEvent{ Type: "application/text", Open: func() io.ReadCloser { return io.NopCloser(strings.NewReader(content)) }, }) return nil } func (w *window) Configure(options []Option) { dpi := windows.GetSystemDPI() metric := configForDPI(dpi) cnf := w.config cnf.apply(metric, options) w.config.Title = cnf.Title w.config.Decorated = cnf.Decorated w.config.MinSize = cnf.MinSize w.config.MaxSize = cnf.MaxSize windows.SetWindowText(w.hwnd, cnf.Title) style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE) var showMode int32 var x, y, width, height int32 swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED) winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW) style &^= winStyle switch cnf.Mode { case Minimized: style |= winStyle swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE showMode = windows.SW_SHOWMINIMIZED case Maximized: style |= winStyle swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE showMode = windows.SW_SHOWMAXIMIZED case Windowed: style |= winStyle showMode = windows.SW_SHOWNORMAL // Get target for client area size. width = int32(cnf.Size.X) height = int32(cnf.Size.Y) // Get the current window size and position. wr := windows.GetWindowRect(w.hwnd) x = wr.Left y = wr.Top if cnf.Decorated { // Compute client size and position. Note that the client size is // equal to the window size when we are in control of decorations. r := windows.Rect{ Right: width, Bottom: height, } windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle) width = r.Right - r.Left height = r.Bottom - r.Top } else { // Enable drop shadows when we draw decorations. windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1}) } case Fullscreen: swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE showMode = windows.SW_SHOWMAXIMIZED } windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style) windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle) windows.ShowWindow(w.hwnd, showMode) } func (w *window) WriteClipboard(mime string, s []byte) { w.writeClipboard(string(s)) } func (w *window) writeClipboard(s string) error { if err := windows.OpenClipboard(w.hwnd); err != nil { return err } defer windows.CloseClipboard() if err := windows.EmptyClipboard(); err != nil { return err } u16, err := gowindows.UTF16FromString(s) if err != nil { return err } n := len(u16) * int(unsafe.Sizeof(u16[0])) mem, err := windows.GlobalAlloc(n) if err != nil { return err } ptr, err := windows.GlobalLock(mem) if err != nil { windows.GlobalFree(mem) return err } u16v := unsafe.Slice((*uint16)(ptr), len(u16)) copy(u16v, u16) windows.GlobalUnlock(mem) if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil { windows.GlobalFree(mem) return err } return nil } func (w *window) SetCursor(cursor pointer.Cursor) { c, err := loadCursor(cursor) if err != nil { c = resources.cursor } w.cursor = c if w.cursorIn { windows.SetCursor(w.cursor) } } // windowsCursor contains mapping from pointer.Cursor to an IDC. var windowsCursor = [...]uint16{ pointer.CursorDefault: windows.IDC_ARROW, pointer.CursorNone: 0, pointer.CursorText: windows.IDC_IBEAM, pointer.CursorVerticalText: windows.IDC_IBEAM, pointer.CursorPointer: windows.IDC_HAND, pointer.CursorCrosshair: windows.IDC_CROSS, pointer.CursorAllScroll: windows.IDC_SIZEALL, pointer.CursorColResize: windows.IDC_SIZEWE, pointer.CursorRowResize: windows.IDC_SIZENS, pointer.CursorGrab: windows.IDC_SIZEALL, pointer.CursorGrabbing: windows.IDC_SIZEALL, pointer.CursorNotAllowed: windows.IDC_NO, pointer.CursorWait: windows.IDC_WAIT, pointer.CursorProgress: windows.IDC_APPSTARTING, pointer.CursorNorthWestResize: windows.IDC_SIZENWSE, pointer.CursorNorthEastResize: windows.IDC_SIZENESW, pointer.CursorSouthWestResize: windows.IDC_SIZENESW, pointer.CursorSouthEastResize: windows.IDC_SIZENWSE, pointer.CursorNorthSouthResize: windows.IDC_SIZENS, pointer.CursorEastWestResize: windows.IDC_SIZEWE, pointer.CursorWestResize: windows.IDC_SIZEWE, pointer.CursorEastResize: windows.IDC_SIZEWE, pointer.CursorNorthResize: windows.IDC_SIZENS, pointer.CursorSouthResize: windows.IDC_SIZENS, pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW, pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE, } func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) { switch cursor { case pointer.CursorDefault: return resources.cursor, nil case pointer.CursorNone: return 0, nil default: return windows.LoadCursor(windowsCursor[cursor]) } } func (w *window) ShowTextInput(show bool) {} func (w *window) SetInputHint(_ key.InputHint) {} func (w *window) HDC() syscall.Handle { return w.hdc } func (w *window) HWND() (syscall.Handle, int, int) { return w.hwnd, w.config.Size.X, w.config.Size.Y } func (w *window) Perform(acts system.Action) { walkActions(acts, func(a system.Action) { switch a { case system.ActionCenter: if w.config.Mode != Windowed { break } r := windows.GetWindowRect(w.hwnd) dx := r.Right - r.Left dy := r.Bottom - r.Top // Calculate center position on current monitor. mi := windows.GetMonitorInfo(w.hwnd).Monitor x := (mi.Right - mi.Left - dx) / 2 y := (mi.Bottom - mi.Top - dy) / 2 windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED) case system.ActionRaise: w.raise() case system.ActionClose: windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0) } }) } func (w *window) raise() { windows.SetForegroundWindow(w.hwnd) windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0, windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW) } func convertKeyCode(code uintptr) (key.Name, bool) { if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' { return key.Name(rune(code)), true } var r key.Name switch code { case windows.VK_ESCAPE: r = key.NameEscape case windows.VK_LEFT: r = key.NameLeftArrow case windows.VK_RIGHT: r = key.NameRightArrow case windows.VK_RETURN: r = key.NameReturn case windows.VK_UP: r = key.NameUpArrow case windows.VK_DOWN: r = key.NameDownArrow case windows.VK_HOME: r = key.NameHome case windows.VK_END: r = key.NameEnd case windows.VK_BACK: r = key.NameDeleteBackward case windows.VK_DELETE: r = key.NameDeleteForward case windows.VK_PRIOR: r = key.NamePageUp case windows.VK_NEXT: r = key.NamePageDown case windows.VK_F1: r = key.NameF1 case windows.VK_F2: r = key.NameF2 case windows.VK_F3: r = key.NameF3 case windows.VK_F4: r = key.NameF4 case windows.VK_F5: r = key.NameF5 case windows.VK_F6: r = key.NameF6 case windows.VK_F7: r = key.NameF7 case windows.VK_F8: r = key.NameF8 case windows.VK_F9: r = key.NameF9 case windows.VK_F10: r = key.NameF10 case windows.VK_F11: r = key.NameF11 case windows.VK_F12: r = key.NameF12 case windows.VK_TAB: r = key.NameTab case windows.VK_SPACE: r = key.NameSpace case windows.VK_OEM_1: r = ";" case windows.VK_OEM_PLUS: r = "+" case windows.VK_OEM_COMMA: r = "," case windows.VK_OEM_MINUS: r = "-" case windows.VK_OEM_PERIOD: r = "." case windows.VK_OEM_2: r = "/" case windows.VK_OEM_3: r = "`" case windows.VK_OEM_4: r = "[" case windows.VK_OEM_5, windows.VK_OEM_102: r = "\\" case windows.VK_OEM_6: r = "]" case windows.VK_OEM_7: r = "'" case windows.VK_CONTROL: r = key.NameCtrl case windows.VK_SHIFT: r = key.NameShift case windows.VK_MENU: r = key.NameAlt case windows.VK_LWIN, windows.VK_RWIN: r = key.NameSuper default: return "", false } return r, true } func configForDPI(dpi int) unit.Metric { const inchPrDp = 1.0 / 96.0 ppdp := float32(dpi) * inchPrDp return unit.Metric{ PxPerDp: ppdp, PxPerSp: ppdp, } } func (Win32ViewEvent) implementsViewEvent() {} func (Win32ViewEvent) ImplementsEvent() {} func (w Win32ViewEvent) Valid() bool { return w != (Win32ViewEvent{}) }