gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/os_windows.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package app
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"image"
     9  	"io"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  	"unicode"
    16  	"unicode/utf8"
    17  	"unsafe"
    18  
    19  	syscall "golang.org/x/sys/windows"
    20  
    21  	"gioui.org/app/internal/windows"
    22  	"gioui.org/op"
    23  	"gioui.org/unit"
    24  	gowindows "golang.org/x/sys/windows"
    25  
    26  	"gioui.org/f32"
    27  	"gioui.org/io/event"
    28  	"gioui.org/io/key"
    29  	"gioui.org/io/pointer"
    30  	"gioui.org/io/system"
    31  	"gioui.org/io/transfer"
    32  )
    33  
    34  type Win32ViewEvent struct {
    35  	HWND uintptr
    36  }
    37  
    38  type window struct {
    39  	hwnd        syscall.Handle
    40  	hdc         syscall.Handle
    41  	w           *callbacks
    42  	pointerBtns pointer.Buttons
    43  
    44  	// cursorIn tracks whether the cursor was inside the window according
    45  	// to the most recent WM_SETCURSOR.
    46  	cursorIn bool
    47  	cursor   syscall.Handle
    48  
    49  	// placement saves the previous window position when in full screen mode.
    50  	placement *windows.WindowPlacement
    51  
    52  	animating   bool
    53  	initialized bool
    54  
    55  	borderSize image.Point
    56  	config     Config
    57  	loop       *eventLoop
    58  
    59  	// invMu avoids the race between destroying the window and Invalidate.
    60  	invMu sync.Mutex
    61  }
    62  
    63  const _WM_WAKEUP = windows.WM_USER + iota
    64  
    65  type gpuAPI struct {
    66  	priority    int
    67  	initializer func(w *window) (context, error)
    68  }
    69  
    70  // drivers is the list of potential Context implementations.
    71  var drivers []gpuAPI
    72  
    73  // winMap maps win32 HWNDs to *windows.
    74  var winMap sync.Map
    75  
    76  // iconID is the ID of the icon in the resource file.
    77  const iconID = 1
    78  
    79  var resources struct {
    80  	once sync.Once
    81  	// handle is the module handle from GetModuleHandle.
    82  	handle syscall.Handle
    83  	// class is the Gio window class from RegisterClassEx.
    84  	class uint16
    85  	// cursor is the arrow cursor resource.
    86  	cursor syscall.Handle
    87  }
    88  
    89  func osMain() {
    90  	select {}
    91  }
    92  
    93  func newWindow(win *callbacks, options []Option) {
    94  	done := make(chan struct{})
    95  	go func() {
    96  		// GetMessage and PeekMessage can filter on a window HWND, but
    97  		// then thread-specific messages such as WM_QUIT are ignored.
    98  		// Instead lock the thread so window messages arrive through
    99  		// unfiltered GetMessage calls.
   100  		runtime.LockOSThread()
   101  
   102  		w := &window{
   103  			w: win,
   104  		}
   105  		w.loop = newEventLoop(w.w, w.wakeup)
   106  		w.w.SetDriver(w)
   107  		err := w.init()
   108  		if err != nil {
   109  			w.ProcessEvent(DestroyEvent{Err: err})
   110  			return
   111  		}
   112  		winMap.Store(w.hwnd, w)
   113  		defer winMap.Delete(w.hwnd)
   114  		w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
   115  		w.Configure(options)
   116  		windows.SetForegroundWindow(w.hwnd)
   117  		windows.SetFocus(w.hwnd)
   118  		// Since the window class for the cursor is null,
   119  		// set it here to show the cursor.
   120  		w.SetCursor(pointer.CursorDefault)
   121  		w.initialized = true
   122  		done <- struct{}{}
   123  		w.loop.FlushEvents()
   124  		w.runLoop()
   125  	}()
   126  	<-done
   127  }
   128  
   129  // initResources initializes the resources global.
   130  func initResources() error {
   131  	windows.SetProcessDPIAware()
   132  	hInst, err := windows.GetModuleHandle()
   133  	if err != nil {
   134  		return err
   135  	}
   136  	resources.handle = hInst
   137  	c, err := windows.LoadCursor(windows.IDC_ARROW)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	resources.cursor = c
   142  	icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
   143  	wcls := windows.WndClassEx{
   144  		CbSize:        uint32(unsafe.Sizeof(windows.WndClassEx{})),
   145  		Style:         windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
   146  		LpfnWndProc:   syscall.NewCallback(windowProc),
   147  		HInstance:     hInst,
   148  		HIcon:         icon,
   149  		LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
   150  	}
   151  	cls, err := windows.RegisterClassEx(&wcls)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	resources.class = cls
   156  	return nil
   157  }
   158  
   159  const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
   160  
   161  func (w *window) init() error {
   162  	var resErr error
   163  	resources.once.Do(func() {
   164  		resErr = initResources()
   165  	})
   166  	if resErr != nil {
   167  		return resErr
   168  	}
   169  	const dwStyle = windows.WS_OVERLAPPEDWINDOW
   170  
   171  	hwnd, err := windows.CreateWindowEx(
   172  		dwExStyle,
   173  		resources.class,
   174  		"",
   175  		dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
   176  		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
   177  		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
   178  		0,
   179  		0,
   180  		resources.handle,
   181  		0)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	w.hdc, err = windows.GetDC(hwnd)
   186  	if err != nil {
   187  		windows.DestroyWindow(hwnd)
   188  		return err
   189  	}
   190  	w.hwnd = hwnd
   191  	return nil
   192  }
   193  
   194  // update() handles changes done by the user, and updates the configuration.
   195  // It reads the window style and size/position and updates w.config.
   196  // If anything has changed it emits a ConfigEvent to notify the application.
   197  func (w *window) update() {
   198  	cr := windows.GetClientRect(w.hwnd)
   199  	w.config.Size = image.Point{
   200  		X: int(cr.Right - cr.Left),
   201  		Y: int(cr.Bottom - cr.Top),
   202  	}
   203  
   204  	w.borderSize = image.Pt(
   205  		windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
   206  		windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
   207  	)
   208  	w.ProcessEvent(ConfigEvent{Config: w.config})
   209  }
   210  
   211  func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
   212  	win, exists := winMap.Load(hwnd)
   213  	if !exists {
   214  		return windows.DefWindowProc(hwnd, msg, wParam, lParam)
   215  	}
   216  
   217  	w := win.(*window)
   218  
   219  	switch msg {
   220  	case windows.WM_UNICHAR:
   221  		if wParam == windows.UNICODE_NOCHAR {
   222  			// Tell the system that we accept WM_UNICHAR messages.
   223  			return windows.TRUE
   224  		}
   225  		fallthrough
   226  	case windows.WM_CHAR:
   227  		if r := rune(wParam); unicode.IsPrint(r) {
   228  			w.w.EditorInsert(string(r))
   229  		}
   230  		// The message is processed.
   231  		return windows.TRUE
   232  	case windows.WM_DPICHANGED:
   233  		// Let Windows know we're prepared for runtime DPI changes.
   234  		return windows.TRUE
   235  	case windows.WM_ERASEBKGND:
   236  		// Avoid flickering between GPU content and background color.
   237  		return windows.TRUE
   238  	case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
   239  		if n, ok := convertKeyCode(wParam); ok {
   240  			e := key.Event{
   241  				Name:      n,
   242  				Modifiers: getModifiers(),
   243  				State:     key.Press,
   244  			}
   245  			if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
   246  				e.State = key.Release
   247  			}
   248  
   249  			w.ProcessEvent(e)
   250  
   251  			if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
   252  				// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
   253  				// such as cmd.exe and graphical debuggers also reserve F10.
   254  				return 0
   255  			}
   256  		}
   257  	case windows.WM_LBUTTONDOWN:
   258  		w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
   259  	case windows.WM_LBUTTONUP:
   260  		w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
   261  	case windows.WM_RBUTTONDOWN:
   262  		w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
   263  	case windows.WM_RBUTTONUP:
   264  		w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
   265  	case windows.WM_MBUTTONDOWN:
   266  		w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
   267  	case windows.WM_MBUTTONUP:
   268  		w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
   269  	case windows.WM_CANCELMODE:
   270  		w.ProcessEvent(pointer.Event{
   271  			Kind: pointer.Cancel,
   272  		})
   273  	case windows.WM_SETFOCUS:
   274  		w.config.Focused = true
   275  		w.ProcessEvent(ConfigEvent{Config: w.config})
   276  	case windows.WM_KILLFOCUS:
   277  		w.config.Focused = false
   278  		w.ProcessEvent(ConfigEvent{Config: w.config})
   279  	case windows.WM_NCHITTEST:
   280  		if w.config.Decorated {
   281  			// Let the system handle it.
   282  			break
   283  		}
   284  		x, y := coordsFromlParam(lParam)
   285  		np := windows.Point{X: int32(x), Y: int32(y)}
   286  		windows.ScreenToClient(w.hwnd, &np)
   287  		return w.hitTest(int(np.X), int(np.Y))
   288  	case windows.WM_MOUSEMOVE:
   289  		x, y := coordsFromlParam(lParam)
   290  		p := f32.Point{X: float32(x), Y: float32(y)}
   291  		w.ProcessEvent(pointer.Event{
   292  			Kind:      pointer.Move,
   293  			Source:    pointer.Mouse,
   294  			Position:  p,
   295  			Buttons:   w.pointerBtns,
   296  			Time:      windows.GetMessageTime(),
   297  			Modifiers: getModifiers(),
   298  		})
   299  	case windows.WM_MOUSEWHEEL:
   300  		w.scrollEvent(wParam, lParam, false, getModifiers())
   301  	case windows.WM_MOUSEHWHEEL:
   302  		w.scrollEvent(wParam, lParam, true, getModifiers())
   303  	case windows.WM_DESTROY:
   304  		w.ProcessEvent(Win32ViewEvent{})
   305  		w.ProcessEvent(DestroyEvent{})
   306  		if w.hdc != 0 {
   307  			windows.ReleaseDC(w.hdc)
   308  			w.hdc = 0
   309  		}
   310  		w.invMu.Lock()
   311  		// The system destroys the HWND for us.
   312  		w.hwnd = 0
   313  		w.invMu.Unlock()
   314  		windows.PostQuitMessage(0)
   315  	case windows.WM_NCCALCSIZE:
   316  		if w.config.Decorated {
   317  			// Let Windows handle decorations.
   318  			break
   319  		}
   320  		// No client areas; we draw decorations ourselves.
   321  		if wParam != 1 {
   322  			return 0
   323  		}
   324  		// lParam contains an NCCALCSIZE_PARAMS for us to adjust.
   325  		place := windows.GetWindowPlacement(w.hwnd)
   326  		if !place.IsMaximized() {
   327  			// Nothing do adjust.
   328  			return 0
   329  		}
   330  		// Adjust window position to avoid the extra padding in maximized
   331  		// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
   332  		// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
   333  		szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
   334  		mi := windows.GetMonitorInfo(w.hwnd)
   335  		szp.Rgrc[0] = mi.WorkArea
   336  		return 0
   337  	case windows.WM_PAINT:
   338  		w.draw(true)
   339  	case windows.WM_SIZE:
   340  		w.update()
   341  		switch wParam {
   342  		case windows.SIZE_MINIMIZED:
   343  			w.config.Mode = Minimized
   344  		case windows.SIZE_MAXIMIZED:
   345  			w.config.Mode = Maximized
   346  		case windows.SIZE_RESTORED:
   347  			if w.config.Mode != Fullscreen {
   348  				w.config.Mode = Windowed
   349  			}
   350  		}
   351  	case windows.WM_GETMINMAXINFO:
   352  		mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
   353  		var bw, bh int32
   354  		if w.config.Decorated {
   355  			r := windows.GetWindowRect(w.hwnd)
   356  			cr := windows.GetClientRect(w.hwnd)
   357  			bw = r.Right - r.Left - (cr.Right - cr.Left)
   358  			bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
   359  		}
   360  		if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
   361  			mm.PtMinTrackSize = windows.Point{
   362  				X: int32(p.X) + bw,
   363  				Y: int32(p.Y) + bh,
   364  			}
   365  		}
   366  		if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
   367  			mm.PtMaxTrackSize = windows.Point{
   368  				X: int32(p.X) + bw,
   369  				Y: int32(p.Y) + bh,
   370  			}
   371  		}
   372  		return 0
   373  	case windows.WM_SETCURSOR:
   374  		w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
   375  		if w.cursorIn {
   376  			windows.SetCursor(w.cursor)
   377  			return windows.TRUE
   378  		}
   379  	case _WM_WAKEUP:
   380  		w.loop.Wakeup()
   381  		w.loop.FlushEvents()
   382  	case windows.WM_IME_STARTCOMPOSITION:
   383  		imc := windows.ImmGetContext(w.hwnd)
   384  		if imc == 0 {
   385  			return windows.TRUE
   386  		}
   387  		defer windows.ImmReleaseContext(w.hwnd, imc)
   388  		sel := w.w.EditorState().Selection
   389  		caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent)))
   390  		icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
   391  		windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
   392  		windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
   393  	case windows.WM_IME_COMPOSITION:
   394  		imc := windows.ImmGetContext(w.hwnd)
   395  		if imc == 0 {
   396  			return windows.TRUE
   397  		}
   398  		defer windows.ImmReleaseContext(w.hwnd, imc)
   399  		state := w.w.EditorState()
   400  		rng := state.compose
   401  		if rng.Start == -1 {
   402  			rng = state.Selection.Range
   403  		}
   404  		if rng.Start > rng.End {
   405  			rng.Start, rng.End = rng.End, rng.Start
   406  		}
   407  		var replacement string
   408  		switch {
   409  		case lParam&windows.GCS_RESULTSTR != 0:
   410  			replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR)
   411  		case lParam&windows.GCS_COMPSTR != 0:
   412  			replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR)
   413  		}
   414  		end := rng.Start + utf8.RuneCountInString(replacement)
   415  		w.w.EditorReplace(rng, replacement)
   416  		state = w.w.EditorState()
   417  		comp := key.Range{
   418  			Start: rng.Start,
   419  			End:   end,
   420  		}
   421  		if lParam&windows.GCS_DELTASTART != 0 {
   422  			start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART)
   423  			comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start)
   424  		}
   425  		w.w.SetComposingRegion(comp)
   426  		pos := end
   427  		if lParam&windows.GCS_CURSORPOS != 0 {
   428  			rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS)
   429  			pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel)
   430  		}
   431  		w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
   432  		return windows.TRUE
   433  	case windows.WM_IME_ENDCOMPOSITION:
   434  		w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
   435  		return windows.TRUE
   436  	}
   437  
   438  	return windows.DefWindowProc(hwnd, msg, wParam, lParam)
   439  }
   440  
   441  func getModifiers() key.Modifiers {
   442  	var kmods key.Modifiers
   443  	if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
   444  		kmods |= key.ModSuper
   445  	}
   446  	if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
   447  		kmods |= key.ModAlt
   448  	}
   449  	if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
   450  		kmods |= key.ModCtrl
   451  	}
   452  	if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
   453  		kmods |= key.ModShift
   454  	}
   455  	return kmods
   456  }
   457  
   458  // hitTest returns the non-client area hit by the point, needed to
   459  // process WM_NCHITTEST.
   460  func (w *window) hitTest(x, y int) uintptr {
   461  	if w.config.Mode == Fullscreen {
   462  		return windows.HTCLIENT
   463  	}
   464  	if w.config.Mode != Windowed {
   465  		// Only windowed mode should allow resizing.
   466  		return windows.HTCLIENT
   467  	}
   468  	// Check for resize handle before system actions; otherwise it can be impossible to
   469  	// resize a custom-decorations window when the system move area is flush with the
   470  	// edge of the window.
   471  	top := y <= w.borderSize.Y
   472  	bottom := y >= w.config.Size.Y-w.borderSize.Y
   473  	left := x <= w.borderSize.X
   474  	right := x >= w.config.Size.X-w.borderSize.X
   475  	switch {
   476  	case top && left:
   477  		return windows.HTTOPLEFT
   478  	case top && right:
   479  		return windows.HTTOPRIGHT
   480  	case bottom && left:
   481  		return windows.HTBOTTOMLEFT
   482  	case bottom && right:
   483  		return windows.HTBOTTOMRIGHT
   484  	case top:
   485  		return windows.HTTOP
   486  	case bottom:
   487  		return windows.HTBOTTOM
   488  	case left:
   489  		return windows.HTLEFT
   490  	case right:
   491  		return windows.HTRIGHT
   492  	}
   493  	p := f32.Pt(float32(x), float32(y))
   494  	if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
   495  		return windows.HTCAPTION
   496  	}
   497  	return windows.HTCLIENT
   498  }
   499  
   500  func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
   501  	if !w.config.Focused {
   502  		windows.SetFocus(w.hwnd)
   503  	}
   504  
   505  	var kind pointer.Kind
   506  	if press {
   507  		kind = pointer.Press
   508  		if w.pointerBtns == 0 {
   509  			windows.SetCapture(w.hwnd)
   510  		}
   511  		w.pointerBtns |= btn
   512  	} else {
   513  		kind = pointer.Release
   514  		w.pointerBtns &^= btn
   515  		if w.pointerBtns == 0 {
   516  			windows.ReleaseCapture()
   517  		}
   518  	}
   519  	x, y := coordsFromlParam(lParam)
   520  	p := f32.Point{X: float32(x), Y: float32(y)}
   521  	w.ProcessEvent(pointer.Event{
   522  		Kind:      kind,
   523  		Source:    pointer.Mouse,
   524  		Position:  p,
   525  		Buttons:   w.pointerBtns,
   526  		Time:      windows.GetMessageTime(),
   527  		Modifiers: kmods,
   528  	})
   529  }
   530  
   531  func coordsFromlParam(lParam uintptr) (int, int) {
   532  	x := int(int16(lParam & 0xffff))
   533  	y := int(int16((lParam >> 16) & 0xffff))
   534  	return x, y
   535  }
   536  
   537  func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
   538  	x, y := coordsFromlParam(lParam)
   539  	// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
   540  	// to other mouse events.
   541  	np := windows.Point{X: int32(x), Y: int32(y)}
   542  	windows.ScreenToClient(w.hwnd, &np)
   543  	p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
   544  	dist := float32(int16(wParam >> 16))
   545  	var sp f32.Point
   546  	if horizontal {
   547  		sp.X = dist
   548  	} else {
   549  		// support horizontal scroll (shift + mousewheel)
   550  		if kmods == key.ModShift {
   551  			sp.X = -dist
   552  		} else {
   553  			sp.Y = -dist
   554  		}
   555  	}
   556  	w.ProcessEvent(pointer.Event{
   557  		Kind:      pointer.Scroll,
   558  		Source:    pointer.Mouse,
   559  		Position:  p,
   560  		Buttons:   w.pointerBtns,
   561  		Scroll:    sp,
   562  		Modifiers: kmods,
   563  		Time:      windows.GetMessageTime(),
   564  	})
   565  }
   566  
   567  // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
   568  func (w *window) runLoop() {
   569  	msg := new(windows.Msg)
   570  loop:
   571  	for {
   572  		anim := w.animating
   573  		if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
   574  			w.draw(false)
   575  			continue
   576  		}
   577  		switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
   578  		case -1:
   579  			panic(errors.New("GetMessage failed"))
   580  		case 0:
   581  			// WM_QUIT received.
   582  			break loop
   583  		}
   584  		windows.TranslateMessage(msg)
   585  		windows.DispatchMessage(msg)
   586  	}
   587  }
   588  
   589  func (w *window) EditorStateChanged(old, new editorState) {
   590  	imc := windows.ImmGetContext(w.hwnd)
   591  	if imc == 0 {
   592  		return
   593  	}
   594  	defer windows.ImmReleaseContext(w.hwnd, imc)
   595  	if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
   596  		windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
   597  	}
   598  }
   599  
   600  func (w *window) SetAnimating(anim bool) {
   601  	w.animating = anim
   602  }
   603  
   604  func (w *window) ProcessEvent(e event.Event) {
   605  	w.w.ProcessEvent(e)
   606  	if w.initialized {
   607  		w.loop.FlushEvents()
   608  	}
   609  }
   610  
   611  func (w *window) Event() event.Event {
   612  	return w.loop.Event()
   613  }
   614  
   615  func (w *window) Invalidate() {
   616  	w.loop.Invalidate()
   617  }
   618  
   619  func (w *window) Run(f func()) {
   620  	w.loop.Run(f)
   621  }
   622  
   623  func (w *window) Frame(frame *op.Ops) {
   624  	w.loop.Frame(frame)
   625  }
   626  
   627  func (w *window) wakeup() {
   628  	w.invMu.Lock()
   629  	defer w.invMu.Unlock()
   630  	if w.hwnd == 0 {
   631  		w.loop.Wakeup()
   632  		w.loop.FlushEvents()
   633  		return
   634  	}
   635  	if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
   636  		panic(err)
   637  	}
   638  }
   639  
   640  func (w *window) draw(sync bool) {
   641  	if w.config.Size.X == 0 || w.config.Size.Y == 0 {
   642  		return
   643  	}
   644  	dpi := windows.GetWindowDPI(w.hwnd)
   645  	cfg := configForDPI(dpi)
   646  	w.ProcessEvent(frameEvent{
   647  		FrameEvent: FrameEvent{
   648  			Now:    time.Now(),
   649  			Size:   w.config.Size,
   650  			Metric: cfg,
   651  		},
   652  		Sync: sync,
   653  	})
   654  }
   655  
   656  func (w *window) NewContext() (context, error) {
   657  	sort.Slice(drivers, func(i, j int) bool {
   658  		return drivers[i].priority < drivers[j].priority
   659  	})
   660  	var errs []string
   661  	for _, b := range drivers {
   662  		ctx, err := b.initializer(w)
   663  		if err == nil {
   664  			return ctx, nil
   665  		}
   666  		errs = append(errs, err.Error())
   667  	}
   668  	if len(errs) > 0 {
   669  		return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
   670  	}
   671  	return nil, errors.New("NewContext: no available GPU drivers")
   672  }
   673  
   674  func (w *window) ReadClipboard() {
   675  	w.readClipboard()
   676  }
   677  
   678  func (w *window) readClipboard() error {
   679  	if err := windows.OpenClipboard(w.hwnd); err != nil {
   680  		return err
   681  	}
   682  	defer windows.CloseClipboard()
   683  	mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
   684  	if err != nil {
   685  		return err
   686  	}
   687  	ptr, err := windows.GlobalLock(mem)
   688  	if err != nil {
   689  		return err
   690  	}
   691  	defer windows.GlobalUnlock(mem)
   692  	content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
   693  	w.ProcessEvent(transfer.DataEvent{
   694  		Type: "application/text",
   695  		Open: func() io.ReadCloser {
   696  			return io.NopCloser(strings.NewReader(content))
   697  		},
   698  	})
   699  	return nil
   700  }
   701  
   702  func (w *window) Configure(options []Option) {
   703  	dpi := windows.GetSystemDPI()
   704  	metric := configForDPI(dpi)
   705  	w.config.apply(metric, options)
   706  	windows.SetWindowText(w.hwnd, w.config.Title)
   707  
   708  	style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
   709  	var showMode int32
   710  	var x, y, width, height int32
   711  	swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
   712  	winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
   713  	style &^= winStyle
   714  	switch w.config.Mode {
   715  	case Minimized:
   716  		style |= winStyle
   717  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   718  		showMode = windows.SW_SHOWMINIMIZED
   719  
   720  	case Maximized:
   721  		style |= winStyle
   722  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   723  		showMode = windows.SW_SHOWMAXIMIZED
   724  
   725  	case Windowed:
   726  		style |= winStyle
   727  		showMode = windows.SW_SHOWNORMAL
   728  		// Get target for client area size.
   729  		width = int32(w.config.Size.X)
   730  		height = int32(w.config.Size.Y)
   731  		// Get the current window size and position.
   732  		wr := windows.GetWindowRect(w.hwnd)
   733  		x = wr.Left
   734  		y = wr.Top
   735  		if w.config.Decorated {
   736  			// Compute client size and position. Note that the client size is
   737  			// equal to the window size when we are in control of decorations.
   738  			r := windows.Rect{
   739  				Right:  width,
   740  				Bottom: height,
   741  			}
   742  			windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
   743  			width = r.Right - r.Left
   744  			height = r.Bottom - r.Top
   745  		}
   746  		if !w.config.Decorated {
   747  			// Enable drop shadows when we draw decorations.
   748  			windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
   749  		}
   750  
   751  	case Fullscreen:
   752  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   753  		mi := windows.GetMonitorInfo(w.hwnd)
   754  		x, y = mi.Monitor.Left, mi.Monitor.Top
   755  		width = mi.Monitor.Right - mi.Monitor.Left
   756  		height = mi.Monitor.Bottom - mi.Monitor.Top
   757  		showMode = windows.SW_SHOWMAXIMIZED
   758  	}
   759  	windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
   760  	windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
   761  	windows.ShowWindow(w.hwnd, showMode)
   762  
   763  	w.update()
   764  }
   765  
   766  func (w *window) WriteClipboard(mime string, s []byte) {
   767  	w.writeClipboard(string(s))
   768  }
   769  
   770  func (w *window) writeClipboard(s string) error {
   771  	if err := windows.OpenClipboard(w.hwnd); err != nil {
   772  		return err
   773  	}
   774  	defer windows.CloseClipboard()
   775  	if err := windows.EmptyClipboard(); err != nil {
   776  		return err
   777  	}
   778  	u16, err := gowindows.UTF16FromString(s)
   779  	if err != nil {
   780  		return err
   781  	}
   782  	n := len(u16) * int(unsafe.Sizeof(u16[0]))
   783  	mem, err := windows.GlobalAlloc(n)
   784  	if err != nil {
   785  		return err
   786  	}
   787  	ptr, err := windows.GlobalLock(mem)
   788  	if err != nil {
   789  		windows.GlobalFree(mem)
   790  		return err
   791  	}
   792  	u16v := unsafe.Slice((*uint16)(ptr), len(u16))
   793  	copy(u16v, u16)
   794  	windows.GlobalUnlock(mem)
   795  	if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
   796  		windows.GlobalFree(mem)
   797  		return err
   798  	}
   799  	return nil
   800  }
   801  
   802  func (w *window) SetCursor(cursor pointer.Cursor) {
   803  	c, err := loadCursor(cursor)
   804  	if err != nil {
   805  		c = resources.cursor
   806  	}
   807  	w.cursor = c
   808  	if w.cursorIn {
   809  		windows.SetCursor(w.cursor)
   810  	}
   811  }
   812  
   813  // windowsCursor contains mapping from pointer.Cursor to an IDC.
   814  var windowsCursor = [...]uint16{
   815  	pointer.CursorDefault:                  windows.IDC_ARROW,
   816  	pointer.CursorNone:                     0,
   817  	pointer.CursorText:                     windows.IDC_IBEAM,
   818  	pointer.CursorVerticalText:             windows.IDC_IBEAM,
   819  	pointer.CursorPointer:                  windows.IDC_HAND,
   820  	pointer.CursorCrosshair:                windows.IDC_CROSS,
   821  	pointer.CursorAllScroll:                windows.IDC_SIZEALL,
   822  	pointer.CursorColResize:                windows.IDC_SIZEWE,
   823  	pointer.CursorRowResize:                windows.IDC_SIZENS,
   824  	pointer.CursorGrab:                     windows.IDC_SIZEALL,
   825  	pointer.CursorGrabbing:                 windows.IDC_SIZEALL,
   826  	pointer.CursorNotAllowed:               windows.IDC_NO,
   827  	pointer.CursorWait:                     windows.IDC_WAIT,
   828  	pointer.CursorProgress:                 windows.IDC_APPSTARTING,
   829  	pointer.CursorNorthWestResize:          windows.IDC_SIZENWSE,
   830  	pointer.CursorNorthEastResize:          windows.IDC_SIZENESW,
   831  	pointer.CursorSouthWestResize:          windows.IDC_SIZENESW,
   832  	pointer.CursorSouthEastResize:          windows.IDC_SIZENWSE,
   833  	pointer.CursorNorthSouthResize:         windows.IDC_SIZENS,
   834  	pointer.CursorEastWestResize:           windows.IDC_SIZEWE,
   835  	pointer.CursorWestResize:               windows.IDC_SIZEWE,
   836  	pointer.CursorEastResize:               windows.IDC_SIZEWE,
   837  	pointer.CursorNorthResize:              windows.IDC_SIZENS,
   838  	pointer.CursorSouthResize:              windows.IDC_SIZENS,
   839  	pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW,
   840  	pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE,
   841  }
   842  
   843  func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) {
   844  	switch cursor {
   845  	case pointer.CursorDefault:
   846  		return resources.cursor, nil
   847  	case pointer.CursorNone:
   848  		return 0, nil
   849  	default:
   850  		return windows.LoadCursor(windowsCursor[cursor])
   851  	}
   852  }
   853  
   854  func (w *window) ShowTextInput(show bool) {}
   855  
   856  func (w *window) SetInputHint(_ key.InputHint) {}
   857  
   858  func (w *window) HDC() syscall.Handle {
   859  	return w.hdc
   860  }
   861  
   862  func (w *window) HWND() (syscall.Handle, int, int) {
   863  	return w.hwnd, w.config.Size.X, w.config.Size.Y
   864  }
   865  
   866  func (w *window) Perform(acts system.Action) {
   867  	walkActions(acts, func(a system.Action) {
   868  		switch a {
   869  		case system.ActionCenter:
   870  			if w.config.Mode != Windowed {
   871  				break
   872  			}
   873  			r := windows.GetWindowRect(w.hwnd)
   874  			dx := r.Right - r.Left
   875  			dy := r.Bottom - r.Top
   876  			// Calculate center position on current monitor.
   877  			mi := windows.GetMonitorInfo(w.hwnd).Monitor
   878  			x := (mi.Right - mi.Left - dx) / 2
   879  			y := (mi.Bottom - mi.Top - dy) / 2
   880  			windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
   881  		case system.ActionRaise:
   882  			w.raise()
   883  		case system.ActionClose:
   884  			windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
   885  		}
   886  	})
   887  }
   888  
   889  func (w *window) raise() {
   890  	windows.SetForegroundWindow(w.hwnd)
   891  	windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
   892  		windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
   893  }
   894  
   895  func convertKeyCode(code uintptr) (key.Name, bool) {
   896  	if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
   897  		return key.Name(rune(code)), true
   898  	}
   899  	var r key.Name
   900  
   901  	switch code {
   902  	case windows.VK_ESCAPE:
   903  		r = key.NameEscape
   904  	case windows.VK_LEFT:
   905  		r = key.NameLeftArrow
   906  	case windows.VK_RIGHT:
   907  		r = key.NameRightArrow
   908  	case windows.VK_RETURN:
   909  		r = key.NameReturn
   910  	case windows.VK_UP:
   911  		r = key.NameUpArrow
   912  	case windows.VK_DOWN:
   913  		r = key.NameDownArrow
   914  	case windows.VK_HOME:
   915  		r = key.NameHome
   916  	case windows.VK_END:
   917  		r = key.NameEnd
   918  	case windows.VK_BACK:
   919  		r = key.NameDeleteBackward
   920  	case windows.VK_DELETE:
   921  		r = key.NameDeleteForward
   922  	case windows.VK_PRIOR:
   923  		r = key.NamePageUp
   924  	case windows.VK_NEXT:
   925  		r = key.NamePageDown
   926  	case windows.VK_F1:
   927  		r = key.NameF1
   928  	case windows.VK_F2:
   929  		r = key.NameF2
   930  	case windows.VK_F3:
   931  		r = key.NameF3
   932  	case windows.VK_F4:
   933  		r = key.NameF4
   934  	case windows.VK_F5:
   935  		r = key.NameF5
   936  	case windows.VK_F6:
   937  		r = key.NameF6
   938  	case windows.VK_F7:
   939  		r = key.NameF7
   940  	case windows.VK_F8:
   941  		r = key.NameF8
   942  	case windows.VK_F9:
   943  		r = key.NameF9
   944  	case windows.VK_F10:
   945  		r = key.NameF10
   946  	case windows.VK_F11:
   947  		r = key.NameF11
   948  	case windows.VK_F12:
   949  		r = key.NameF12
   950  	case windows.VK_TAB:
   951  		r = key.NameTab
   952  	case windows.VK_SPACE:
   953  		r = key.NameSpace
   954  	case windows.VK_OEM_1:
   955  		r = ";"
   956  	case windows.VK_OEM_PLUS:
   957  		r = "+"
   958  	case windows.VK_OEM_COMMA:
   959  		r = ","
   960  	case windows.VK_OEM_MINUS:
   961  		r = "-"
   962  	case windows.VK_OEM_PERIOD:
   963  		r = "."
   964  	case windows.VK_OEM_2:
   965  		r = "/"
   966  	case windows.VK_OEM_3:
   967  		r = "`"
   968  	case windows.VK_OEM_4:
   969  		r = "["
   970  	case windows.VK_OEM_5, windows.VK_OEM_102:
   971  		r = "\\"
   972  	case windows.VK_OEM_6:
   973  		r = "]"
   974  	case windows.VK_OEM_7:
   975  		r = "'"
   976  	case windows.VK_CONTROL:
   977  		r = key.NameCtrl
   978  	case windows.VK_SHIFT:
   979  		r = key.NameShift
   980  	case windows.VK_MENU:
   981  		r = key.NameAlt
   982  	case windows.VK_LWIN, windows.VK_RWIN:
   983  		r = key.NameSuper
   984  	default:
   985  		return "", false
   986  	}
   987  	return r, true
   988  }
   989  
   990  func configForDPI(dpi int) unit.Metric {
   991  	const inchPrDp = 1.0 / 96.0
   992  	ppdp := float32(dpi) * inchPrDp
   993  	return unit.Metric{
   994  		PxPerDp: ppdp,
   995  		PxPerSp: ppdp,
   996  	}
   997  }
   998  
   999  func (Win32ViewEvent) implementsViewEvent() {}
  1000  func (Win32ViewEvent) ImplementsEvent()     {}