github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/app/internal/window/os_windows.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package window
     4  
     5  import (
     6  	"errors"
     7  	"image"
     8  	"runtime"
     9  	"sync"
    10  	"time"
    11  	"unicode"
    12  	"unsafe"
    13  
    14  	syscall "golang.org/x/sys/windows"
    15  
    16  	"github.com/gop9/olt/gio/app/internal/windows"
    17  
    18  	"github.com/gop9/olt/gio/f32"
    19  	"github.com/gop9/olt/gio/io/key"
    20  	"github.com/gop9/olt/gio/io/pointer"
    21  	"github.com/gop9/olt/gio/io/system"
    22  )
    23  
    24  var winMap = make(map[syscall.Handle]*window)
    25  
    26  type window struct {
    27  	hwnd        syscall.Handle
    28  	hdc         syscall.Handle
    29  	w           Callbacks
    30  	width       int
    31  	height      int
    32  	stage       system.Stage
    33  	dead        bool
    34  	pointerBtns pointer.Buttons
    35  
    36  	mu        sync.Mutex
    37  	animating bool
    38  }
    39  
    40  const _WM_REDRAW = windows.WM_USER + 0
    41  
    42  var onceMu sync.Mutex
    43  var mainDone = make(chan struct{})
    44  
    45  func Main() {
    46  	<-mainDone
    47  }
    48  
    49  func NewWindow(window Callbacks, opts *Options) error {
    50  	onceMu.Lock()
    51  	defer onceMu.Unlock()
    52  	if len(winMap) > 0 {
    53  		return errors.New("multiple windows are not supported")
    54  	}
    55  	cerr := make(chan error)
    56  	go func() {
    57  		// Call win32 API from a single OS thread.
    58  		runtime.LockOSThread()
    59  		w, err := createNativeWindow(opts)
    60  		if err != nil {
    61  			cerr <- err
    62  			return
    63  		}
    64  		defer w.destroy()
    65  		cerr <- nil
    66  		winMap[w.hwnd] = w
    67  		defer delete(winMap, w.hwnd)
    68  		w.w = window
    69  		w.w.SetDriver(w)
    70  		defer w.w.Event(system.DestroyEvent{})
    71  		windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT)
    72  		windows.SetForegroundWindow(w.hwnd)
    73  		windows.SetFocus(w.hwnd)
    74  		if err := w.loop(); err != nil {
    75  			panic(err)
    76  		}
    77  		close(mainDone)
    78  	}()
    79  	return <-cerr
    80  }
    81  
    82  func createNativeWindow(opts *Options) (*window, error) {
    83  	windows.SetProcessDPIAware()
    84  	cfg := configForDC()
    85  	hInst, err := windows.GetModuleHandle()
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	curs, err := windows.LoadCursor(windows.IDC_ARROW)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	wcls := windows.WndClassEx{
    94  		CbSize:        uint32(unsafe.Sizeof(windows.WndClassEx{})),
    95  		Style:         windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
    96  		LpfnWndProc:   syscall.NewCallback(windowProc),
    97  		HInstance:     hInst,
    98  		HCursor:       curs,
    99  		LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
   100  	}
   101  	cls, err := windows.RegisterClassEx(&wcls)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	wr := windows.Rect{
   106  		Right:  int32(cfg.Px(opts.Width)),
   107  		Bottom: int32(cfg.Px(opts.Height)),
   108  	}
   109  	dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
   110  	dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
   111  	windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)
   112  	hwnd, err := windows.CreateWindowEx(dwExStyle,
   113  		cls,
   114  		opts.Title,
   115  		dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
   116  		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
   117  		wr.Right-wr.Left,
   118  		wr.Bottom-wr.Top,
   119  		0,
   120  		0,
   121  		hInst,
   122  		0)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	w := &window{
   127  		hwnd: hwnd,
   128  	}
   129  	w.hdc, err = windows.GetDC(hwnd)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	return w, nil
   134  }
   135  
   136  func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
   137  	w := winMap[hwnd]
   138  	switch msg {
   139  	case windows.WM_UNICHAR:
   140  		if wParam == windows.UNICODE_NOCHAR {
   141  			// Tell the system that we accept WM_UNICHAR messages.
   142  			return 1
   143  		}
   144  		fallthrough
   145  	case windows.WM_CHAR:
   146  		if r := rune(wParam); unicode.IsPrint(r) {
   147  			w.w.Event(key.EditEvent{Text: string(r)})
   148  		}
   149  		// The message is processed.
   150  		return 1
   151  	case windows.WM_DPICHANGED:
   152  		// Let Windows know we're prepared for runtime DPI changes.
   153  		return 1
   154  	case windows.WM_ERASEBKGND:
   155  		// Avoid flickering between GPU content and background color.
   156  		return 1
   157  	case windows.WM_KEYDOWN, windows.WM_SYSKEYDOWN:
   158  		if n, ok := convertKeyCode(wParam); ok {
   159  			w.w.Event(key.Event{Name: n, Modifiers: getModifiers()})
   160  		}
   161  	case windows.WM_LBUTTONDOWN:
   162  		w.pointerButton(pointer.ButtonLeft, true, lParam, getModifiers())
   163  	case windows.WM_LBUTTONUP:
   164  		w.pointerButton(pointer.ButtonLeft, false, lParam, getModifiers())
   165  	case windows.WM_RBUTTONDOWN:
   166  		w.pointerButton(pointer.ButtonRight, true, lParam, getModifiers())
   167  	case windows.WM_RBUTTONUP:
   168  		w.pointerButton(pointer.ButtonRight, false, lParam, getModifiers())
   169  	case windows.WM_MBUTTONDOWN:
   170  		w.pointerButton(pointer.ButtonMiddle, true, lParam, getModifiers())
   171  	case windows.WM_MBUTTONUP:
   172  		w.pointerButton(pointer.ButtonMiddle, false, lParam, getModifiers())
   173  	case windows.WM_CANCELMODE:
   174  		w.w.Event(pointer.Event{
   175  			Type: pointer.Cancel,
   176  		})
   177  	case windows.WM_SETFOCUS:
   178  		w.w.Event(key.FocusEvent{Focus: true})
   179  	case windows.WM_KILLFOCUS:
   180  		w.w.Event(key.FocusEvent{Focus: false})
   181  	case windows.WM_MOUSEMOVE:
   182  		x, y := coordsFromlParam(lParam)
   183  		p := f32.Point{X: float32(x), Y: float32(y)}
   184  		w.w.Event(pointer.Event{
   185  			Type:     pointer.Move,
   186  			Source:   pointer.Mouse,
   187  			Position: p,
   188  			Time:     windows.GetMessageTime(),
   189  		})
   190  	case windows.WM_MOUSEWHEEL:
   191  		w.scrollEvent(wParam, lParam)
   192  	case windows.WM_DESTROY:
   193  		w.dead = true
   194  	case windows.WM_PAINT:
   195  		w.draw(true)
   196  	case windows.WM_SIZE:
   197  		switch wParam {
   198  		case windows.SIZE_MINIMIZED:
   199  			w.setStage(system.StagePaused)
   200  		case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED:
   201  			w.setStage(system.StageRunning)
   202  		}
   203  	}
   204  	return windows.DefWindowProc(hwnd, msg, wParam, lParam)
   205  }
   206  
   207  func getModifiers() key.Modifiers {
   208  	var kmods key.Modifiers
   209  	if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
   210  		kmods |= key.ModSuper
   211  	}
   212  	if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
   213  		kmods |= key.ModAlt
   214  	}
   215  	if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
   216  		kmods |= key.ModCtrl
   217  	}
   218  	if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
   219  		kmods |= key.ModShift
   220  	}
   221  	return kmods
   222  }
   223  
   224  func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
   225  	var typ pointer.Type
   226  	if press {
   227  		typ = pointer.Press
   228  		if w.pointerBtns == 0 {
   229  			windows.SetCapture(w.hwnd)
   230  		}
   231  		w.pointerBtns |= btn
   232  	} else {
   233  		typ = pointer.Release
   234  		w.pointerBtns &^= btn
   235  		if w.pointerBtns == 0 {
   236  			windows.ReleaseCapture()
   237  		}
   238  	}
   239  	x, y := coordsFromlParam(lParam)
   240  	p := f32.Point{X: float32(x), Y: float32(y)}
   241  	w.w.Event(pointer.Event{
   242  		Type:      typ,
   243  		Source:    pointer.Mouse,
   244  		Position:  p,
   245  		Buttons:   w.pointerBtns,
   246  		Time:      windows.GetMessageTime(),
   247  		Modifiers: kmods,
   248  	})
   249  }
   250  
   251  func coordsFromlParam(lParam uintptr) (int, int) {
   252  	x := int(int16(lParam & 0xffff))
   253  	y := int(int16((lParam >> 16) & 0xffff))
   254  	return x, y
   255  }
   256  
   257  func (w *window) scrollEvent(wParam, lParam uintptr) {
   258  	x, y := coordsFromlParam(lParam)
   259  	// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
   260  	// to other mouse events.
   261  	np := windows.Point{X: int32(x), Y: int32(y)}
   262  	windows.ScreenToClient(w.hwnd, &np)
   263  	p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
   264  	dist := float32(int16(wParam >> 16))
   265  	w.w.Event(pointer.Event{
   266  		Type:     pointer.Move,
   267  		Source:   pointer.Mouse,
   268  		Position: p,
   269  		Scroll:   f32.Point{Y: -dist},
   270  		Time:     windows.GetMessageTime(),
   271  	})
   272  }
   273  
   274  // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
   275  func (w *window) loop() error {
   276  	msg := new(windows.Msg)
   277  	for !w.dead {
   278  		w.mu.Lock()
   279  		anim := w.animating
   280  		w.mu.Unlock()
   281  		if anim && !windows.PeekMessage(msg, w.hwnd, 0, 0, windows.PM_NOREMOVE) {
   282  			w.draw(false)
   283  			continue
   284  		}
   285  		windows.GetMessage(msg, w.hwnd, 0, 0)
   286  		if msg.Message == windows.WM_QUIT {
   287  			windows.PostQuitMessage(msg.WParam)
   288  			break
   289  		}
   290  		windows.TranslateMessage(msg)
   291  		windows.DispatchMessage(msg)
   292  	}
   293  	return nil
   294  }
   295  
   296  func (w *window) SetAnimating(anim bool) {
   297  	w.mu.Lock()
   298  	w.animating = anim
   299  	w.mu.Unlock()
   300  	if anim {
   301  		w.postRedraw()
   302  	}
   303  }
   304  
   305  func (w *window) postRedraw() {
   306  	if err := windows.PostMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil {
   307  		panic(err)
   308  	}
   309  }
   310  
   311  func (w *window) setStage(s system.Stage) {
   312  	w.stage = s
   313  	w.w.Event(system.StageEvent{Stage: s})
   314  }
   315  
   316  func (w *window) draw(sync bool) {
   317  	var r windows.Rect
   318  	windows.GetClientRect(w.hwnd, &r)
   319  	w.width = int(r.Right - r.Left)
   320  	w.height = int(r.Bottom - r.Top)
   321  	cfg := configForDC()
   322  	cfg.now = time.Now()
   323  	w.w.Event(FrameEvent{
   324  		FrameEvent: system.FrameEvent{
   325  			Size: image.Point{
   326  				X: w.width,
   327  				Y: w.height,
   328  			},
   329  			Config: &cfg,
   330  		},
   331  		Sync: sync,
   332  	})
   333  }
   334  
   335  func (w *window) destroy() {
   336  	if w.hdc != 0 {
   337  		windows.ReleaseDC(w.hdc)
   338  		w.hdc = 0
   339  	}
   340  	if w.hwnd != 0 {
   341  		windows.DestroyWindow(w.hwnd)
   342  		w.hwnd = 0
   343  	}
   344  }
   345  
   346  func (w *window) ShowTextInput(show bool) {}
   347  
   348  func (w *window) HDC() syscall.Handle {
   349  	return w.hdc
   350  }
   351  
   352  func (w *window) HWND() (syscall.Handle, int, int) {
   353  	return w.hwnd, w.width, w.height
   354  }
   355  
   356  func convertKeyCode(code uintptr) (string, bool) {
   357  	if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
   358  		return string(code), true
   359  	}
   360  	var r string
   361  	switch code {
   362  	case windows.VK_ESCAPE:
   363  		r = key.NameEscape
   364  	case windows.VK_LEFT:
   365  		r = key.NameLeftArrow
   366  	case windows.VK_RIGHT:
   367  		r = key.NameRightArrow
   368  	case windows.VK_RETURN:
   369  		r = key.NameReturn
   370  	case windows.VK_UP:
   371  		r = key.NameUpArrow
   372  	case windows.VK_DOWN:
   373  		r = key.NameDownArrow
   374  	case windows.VK_HOME:
   375  		r = key.NameHome
   376  	case windows.VK_END:
   377  		r = key.NameEnd
   378  	case windows.VK_BACK:
   379  		r = key.NameDeleteBackward
   380  	case windows.VK_DELETE:
   381  		r = key.NameDeleteForward
   382  	case windows.VK_PRIOR:
   383  		r = key.NamePageUp
   384  	case windows.VK_NEXT:
   385  		r = key.NamePageDown
   386  	case windows.VK_F1:
   387  		r = "F1"
   388  	case windows.VK_F2:
   389  		r = "F2"
   390  	case windows.VK_F3:
   391  		r = "F3"
   392  	case windows.VK_F4:
   393  		r = "F4"
   394  	case windows.VK_F5:
   395  		r = "F5"
   396  	case windows.VK_F6:
   397  		r = "F6"
   398  	case windows.VK_F7:
   399  		r = "F7"
   400  	case windows.VK_F8:
   401  		r = "F8"
   402  	case windows.VK_F9:
   403  		r = "F9"
   404  	case windows.VK_F10:
   405  		r = "F10"
   406  	case windows.VK_F11:
   407  		r = "F11"
   408  	case windows.VK_F12:
   409  		r = "F12"
   410  	case windows.VK_TAB:
   411  		r = key.NameTab
   412  	case windows.VK_SPACE:
   413  		r = "Space"
   414  	case windows.VK_OEM_1:
   415  		r = ";"
   416  	case windows.VK_OEM_PLUS:
   417  		r = "+"
   418  	case windows.VK_OEM_COMMA:
   419  		r = ","
   420  	case windows.VK_OEM_MINUS:
   421  		r = "-"
   422  	case windows.VK_OEM_PERIOD:
   423  		r = "."
   424  	case windows.VK_OEM_2:
   425  		r = "/"
   426  	case windows.VK_OEM_3:
   427  		r = "`"
   428  	case windows.VK_OEM_4:
   429  		r = "["
   430  	case windows.VK_OEM_5, windows.VK_OEM_102:
   431  		r = "\\"
   432  	case windows.VK_OEM_6:
   433  		r = "]"
   434  	case windows.VK_OEM_7:
   435  		r = "'"
   436  	default:
   437  		return "", false
   438  	}
   439  	return r, true
   440  }
   441  
   442  func configForDC() config {
   443  	dpi := windows.GetSystemDPI()
   444  	const inchPrDp = 1.0 / 96.0
   445  	ppdp := float32(dpi) * inchPrDp
   446  	return config{
   447  		pxPerDp: ppdp,
   448  		pxPerSp: ppdp,
   449  	}
   450  }