123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- // 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{})
- }
|