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