github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/wm/os_windows.go (about)

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