twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

win32.go (13292B)


      1 // Copyright 2015 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // +build windows
      6 
      7 // Package win32 implements a partial shiny screen driver using the Win32 API.
      8 // It provides window, lifecycle, key, and mouse management, but no drawing.
      9 // That is left to windriver (using GDI) or gldriver (using DirectX via ANGLE).
     10 package win32 // import "golang.org/x/exp/shiny/driver/internal/win32"
     11 
     12 import (
     13 	"fmt"
     14 	"runtime"
     15 	"sync"
     16 	"syscall"
     17 	"unsafe"
     18 
     19 	"golang.org/x/exp/shiny/screen"
     20 	"golang.org/x/mobile/event/key"
     21 	"golang.org/x/mobile/event/lifecycle"
     22 	"golang.org/x/mobile/event/mouse"
     23 	"golang.org/x/mobile/event/paint"
     24 	"golang.org/x/mobile/event/size"
     25 	"golang.org/x/mobile/geom"
     26 )
     27 
     28 // screenHWND is the handle to the "Screen window".
     29 // The Screen window encapsulates all screen.Screen operations
     30 // in an actual Windows window so they all run on the main thread.
     31 // Since any messages sent to a window will be executed on the
     32 // main thread, we can safely use the messages below.
     33 var screenHWND syscall.Handle
     34 
     35 const (
     36 	msgCreateWindow = _WM_USER + iota
     37 	msgMainCallback
     38 	msgShow
     39 	msgQuit
     40 	msgLast
     41 )
     42 
     43 // userWM is used to generate private (WM_USER and above) window message IDs
     44 // for use by screenWindowWndProc and windowWndProc.
     45 type userWM struct {
     46 	sync.Mutex
     47 	id uint32
     48 }
     49 
     50 func (m *userWM) next() uint32 {
     51 	m.Lock()
     52 	if m.id == 0 {
     53 		m.id = msgLast
     54 	}
     55 	r := m.id
     56 	m.id++
     57 	m.Unlock()
     58 	return r
     59 }
     60 
     61 var currentUserWM userWM
     62 
     63 func newWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
     64 	// TODO(brainman): convert windowClass to *uint16 once (in initWindowClass)
     65 	wcname, err := syscall.UTF16PtrFromString(windowClass)
     66 	if err != nil {
     67 		return 0, err
     68 	}
     69 	title, err := syscall.UTF16PtrFromString(opts.GetTitle())
     70 	if err != nil {
     71 		return 0, err
     72 	}
     73 	hwnd, err := _CreateWindowEx(0,
     74 		wcname, title,
     75 		_WS_OVERLAPPEDWINDOW,
     76 		_CW_USEDEFAULT, _CW_USEDEFAULT,
     77 		_CW_USEDEFAULT, _CW_USEDEFAULT,
     78 		0, 0, hThisInstance, 0)
     79 	if err != nil {
     80 		return 0, err
     81 	}
     82 	// TODO(andlabs): use proper nCmdShow
     83 	// TODO(andlabs): call UpdateWindow()
     84 
     85 	return hwnd, nil
     86 }
     87 
     88 // ResizeClientRect makes hwnd client rectangle opts.Width by opts.Height in size.
     89 func ResizeClientRect(hwnd syscall.Handle, opts *screen.NewWindowOptions) error {
     90 	if opts == nil || opts.Width <= 0 || opts.Height <= 0 {
     91 		return nil
     92 	}
     93 	var cr, wr _RECT
     94 	err := _GetClientRect(hwnd, &cr)
     95 	if err != nil {
     96 		return err
     97 	}
     98 	err = _GetWindowRect(hwnd, &wr)
     99 	if err != nil {
    100 		return err
    101 	}
    102 	w := (wr.Right - wr.Left) - (cr.Right - int32(opts.Width))
    103 	h := (wr.Bottom - wr.Top) - (cr.Bottom - int32(opts.Height))
    104 	return _MoveWindow(hwnd, wr.Left, wr.Top, w, h, false)
    105 }
    106 
    107 // Show shows a newly created window.
    108 // It sends the appropriate lifecycle events, makes the window appear
    109 // on the screen, and sends an initial size event.
    110 //
    111 // This is a separate step from NewWindow to give the driver a chance
    112 // to setup its internal state for a window before events start being
    113 // delivered.
    114 func Show(hwnd syscall.Handle) {
    115 	SendMessage(hwnd, msgShow, 0, 0)
    116 }
    117 
    118 func Release(hwnd syscall.Handle) {
    119 	SendMessage(hwnd, _WM_CLOSE, 0, 0)
    120 }
    121 
    122 func sendFocus(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
    123 	switch uMsg {
    124 	case _WM_SETFOCUS:
    125 		LifecycleEvent(hwnd, lifecycle.StageFocused)
    126 	case _WM_KILLFOCUS:
    127 		LifecycleEvent(hwnd, lifecycle.StageVisible)
    128 	default:
    129 		panic(fmt.Sprintf("unexpected focus message: %d", uMsg))
    130 	}
    131 	return _DefWindowProc(hwnd, uMsg, wParam, lParam)
    132 }
    133 
    134 func sendShow(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
    135 	LifecycleEvent(hwnd, lifecycle.StageVisible)
    136 	_ShowWindow(hwnd, _SW_SHOWDEFAULT)
    137 	sendSize(hwnd)
    138 	return 0
    139 }
    140 
    141 func sendSizeEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
    142 	wp := (*_WINDOWPOS)(unsafe.Pointer(lParam))
    143 	if wp.Flags&_SWP_NOSIZE != 0 {
    144 		return 0
    145 	}
    146 	sendSize(hwnd)
    147 	return 0
    148 }
    149 
    150 func sendSize(hwnd syscall.Handle) {
    151 	var r _RECT
    152 	if err := _GetClientRect(hwnd, &r); err != nil {
    153 		panic(err) // TODO(andlabs)
    154 	}
    155 
    156 	width := int(r.Right - r.Left)
    157 	height := int(r.Bottom - r.Top)
    158 
    159 	// TODO(andlabs): don't assume that PixelsPerPt == 1
    160 	SizeEvent(hwnd, size.Event{
    161 		WidthPx:     width,
    162 		HeightPx:    height,
    163 		WidthPt:     geom.Pt(width),
    164 		HeightPt:    geom.Pt(height),
    165 		PixelsPerPt: 1,
    166 	})
    167 }
    168 
    169 func sendClose(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
    170 	// TODO(ktye): DefWindowProc calls DestroyWindow by default.
    171 	// To intercept destruction of the window, return 0 and call
    172 	// DestroyWindow when appropriate.
    173 	LifecycleEvent(hwnd, lifecycle.StageDead)
    174 	return _DefWindowProc(hwnd, uMsg, wParam, lParam)
    175 }
    176 
    177 func sendMouseEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
    178 	e := mouse.Event{
    179 		X:         float32(_GET_X_LPARAM(lParam)),
    180 		Y:         float32(_GET_Y_LPARAM(lParam)),
    181 		Modifiers: keyModifiers(),
    182 	}
    183 
    184 	switch uMsg {
    185 	case _WM_MOUSEMOVE:
    186 		e.Direction = mouse.DirNone
    187 	case _WM_LBUTTONDOWN, _WM_MBUTTONDOWN, _WM_RBUTTONDOWN:
    188 		e.Direction = mouse.DirPress
    189 	case _WM_LBUTTONUP, _WM_MBUTTONUP, _WM_RBUTTONUP:
    190 		e.Direction = mouse.DirRelease
    191 	case _WM_MOUSEWHEEL:
    192 		// TODO: On a trackpad, a scroll can be a drawn-out affair with a
    193 		// distinct beginning and end. Should the intermediate events be
    194 		// DirNone?
    195 		e.Direction = mouse.DirStep
    196 
    197 		// Convert from screen to window coordinates.
    198 		p := _POINT{
    199 			int32(e.X),
    200 			int32(e.Y),
    201 		}
    202 		_ScreenToClient(hwnd, &p)
    203 		e.X = float32(p.X)
    204 		e.Y = float32(p.Y)
    205 	default:
    206 		panic("sendMouseEvent() called on non-mouse message")
    207 	}
    208 
    209 	switch uMsg {
    210 	case _WM_MOUSEMOVE:
    211 		// No-op.
    212 	case _WM_LBUTTONDOWN, _WM_LBUTTONUP:
    213 		e.Button = mouse.ButtonLeft
    214 	case _WM_MBUTTONDOWN, _WM_MBUTTONUP:
    215 		e.Button = mouse.ButtonMiddle
    216 	case _WM_RBUTTONDOWN, _WM_RBUTTONUP:
    217 		e.Button = mouse.ButtonRight
    218 	case _WM_MOUSEWHEEL:
    219 		// TODO: handle horizontal scrolling
    220 		delta := _GET_WHEEL_DELTA_WPARAM(wParam) / _WHEEL_DELTA
    221 		switch {
    222 		case delta > 0:
    223 			e.Button = mouse.ButtonWheelUp
    224 		case delta < 0:
    225 			e.Button = mouse.ButtonWheelDown
    226 			delta = -delta
    227 		default:
    228 			return
    229 		}
    230 		for delta > 0 {
    231 			MouseEvent(hwnd, e)
    232 			delta--
    233 		}
    234 		return
    235 	}
    236 
    237 	MouseEvent(hwnd, e)
    238 
    239 	return 0
    240 }
    241 
    242 // Precondition: this is called in immediate response to the message that triggered the event (so not after w.Send).
    243 func keyModifiers() (m key.Modifiers) {
    244 	down := func(x int32) bool {
    245 		// GetKeyState gets the key state at the time of the message, so this is what we want.
    246 		return _GetKeyState(x)&0x80 != 0
    247 	}
    248 
    249 	if down(_VK_CONTROL) {
    250 		m |= key.ModControl
    251 	}
    252 	if down(_VK_MENU) {
    253 		m |= key.ModAlt
    254 	}
    255 	if down(_VK_SHIFT) {
    256 		m |= key.ModShift
    257 	}
    258 	if down(_VK_LWIN) || down(_VK_RWIN) {
    259 		m |= key.ModMeta
    260 	}
    261 	return m
    262 }
    263 
    264 var (
    265 	MouseEvent     func(hwnd syscall.Handle, e mouse.Event)
    266 	PaintEvent     func(hwnd syscall.Handle, e paint.Event)
    267 	SizeEvent      func(hwnd syscall.Handle, e size.Event)
    268 	KeyEvent       func(hwnd syscall.Handle, e key.Event)
    269 	LifecycleEvent func(hwnd syscall.Handle, e lifecycle.Stage)
    270 
    271 	// TODO: use the golang.org/x/exp/shiny/driver/internal/lifecycler package
    272 	// instead of or together with the LifecycleEvent callback?
    273 )
    274 
    275 func sendPaint(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
    276 	PaintEvent(hwnd, paint.Event{})
    277 	return _DefWindowProc(hwnd, uMsg, wParam, lParam)
    278 }
    279 
    280 var screenMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){}
    281 
    282 func AddScreenMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
    283 	uMsg := currentUserWM.next()
    284 	screenMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
    285 		fn(hwnd, uMsg, wParam, lParam)
    286 		return 0
    287 	}
    288 	return uMsg
    289 }
    290 
    291 func screenWindowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
    292 	switch uMsg {
    293 	case msgCreateWindow:
    294 		p := (*newWindowParams)(unsafe.Pointer(lParam))
    295 		p.w, p.err = newWindow(p.opts)
    296 	case msgMainCallback:
    297 		go func() {
    298 			mainCallback()
    299 			SendScreenMessage(msgQuit, 0, 0)
    300 		}()
    301 	case msgQuit:
    302 		_PostQuitMessage(0)
    303 	}
    304 	fn := screenMsgs[uMsg]
    305 	if fn != nil {
    306 		return fn(hwnd, uMsg, wParam, lParam)
    307 	}
    308 	return _DefWindowProc(hwnd, uMsg, wParam, lParam)
    309 }
    310 
    311 //go:uintptrescapes
    312 
    313 func SendScreenMessage(uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
    314 	return SendMessage(screenHWND, uMsg, wParam, lParam)
    315 }
    316 
    317 var windowMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){
    318 	_WM_SETFOCUS:         sendFocus,
    319 	_WM_KILLFOCUS:        sendFocus,
    320 	_WM_PAINT:            sendPaint,
    321 	msgShow:              sendShow,
    322 	_WM_WINDOWPOSCHANGED: sendSizeEvent,
    323 	_WM_CLOSE:            sendClose,
    324 
    325 	_WM_LBUTTONDOWN: sendMouseEvent,
    326 	_WM_LBUTTONUP:   sendMouseEvent,
    327 	_WM_MBUTTONDOWN: sendMouseEvent,
    328 	_WM_MBUTTONUP:   sendMouseEvent,
    329 	_WM_RBUTTONDOWN: sendMouseEvent,
    330 	_WM_RBUTTONUP:   sendMouseEvent,
    331 	_WM_MOUSEMOVE:   sendMouseEvent,
    332 	_WM_MOUSEWHEEL:  sendMouseEvent,
    333 
    334 	_WM_KEYDOWN: sendKeyEvent,
    335 	_WM_KEYUP:   sendKeyEvent,
    336 	// TODO case _WM_SYSKEYDOWN, _WM_SYSKEYUP:
    337 }
    338 
    339 func AddWindowMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
    340 	uMsg := currentUserWM.next()
    341 	windowMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
    342 		fn(hwnd, uMsg, wParam, lParam)
    343 		return 0
    344 	}
    345 	return uMsg
    346 }
    347 
    348 func windowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
    349 	fn := windowMsgs[uMsg]
    350 	if fn != nil {
    351 		return fn(hwnd, uMsg, wParam, lParam)
    352 	}
    353 	return _DefWindowProc(hwnd, uMsg, wParam, lParam)
    354 }
    355 
    356 type newWindowParams struct {
    357 	opts *screen.NewWindowOptions
    358 	w    syscall.Handle
    359 	err  error
    360 }
    361 
    362 func NewWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
    363 	var p newWindowParams
    364 	p.opts = opts
    365 	SendScreenMessage(msgCreateWindow, 0, uintptr(unsafe.Pointer(&p)))
    366 	return p.w, p.err
    367 }
    368 
    369 const windowClass = "shiny_Window"
    370 const screenWindowClass = "shiny_ScreenWindow"
    371 
    372 func initWindowClass() (err error) {
    373 	wcname, err := syscall.UTF16PtrFromString(windowClass)
    374 	if err != nil {
    375 		return err
    376 	}
    377 	_, err = _RegisterClass(&_WNDCLASS{
    378 		LpszClassName: wcname,
    379 		LpfnWndProc:   syscall.NewCallback(windowWndProc),
    380 		HIcon:         hDefaultIcon,
    381 		HCursor:       hDefaultCursor,
    382 		HInstance:     hThisInstance,
    383 		// TODO(andlabs): change this to something else? NULL? the hollow brush?
    384 		HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
    385 	})
    386 	return err
    387 }
    388 
    389 func closeWindowClass() (err error) {
    390 	wcname, err := syscall.UTF16PtrFromString(windowClass)
    391 	if err != nil {
    392 		return err
    393 	}
    394 	_UnregisterClass(wcname, hThisInstance)
    395 
    396 	return nil
    397 }
    398 
    399 func initScreenWindow() (err error) {
    400 	swc, err := syscall.UTF16PtrFromString(screenWindowClass)
    401 	if err != nil {
    402 		return err
    403 	}
    404 	emptyString, err := syscall.UTF16PtrFromString("")
    405 	if err != nil {
    406 		return err
    407 	}
    408 	wc := _WNDCLASS{
    409 		LpszClassName: swc,
    410 		LpfnWndProc:   syscall.NewCallback(screenWindowWndProc),
    411 		HIcon:         hDefaultIcon,
    412 		HCursor:       hDefaultCursor,
    413 		HInstance:     hThisInstance,
    414 		HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
    415 	}
    416 	_, err = _RegisterClass(&wc)
    417 	if err != nil {
    418 		return err
    419 	}
    420 	screenHWND, err = _CreateWindowEx(0,
    421 		swc, emptyString,
    422 		_WS_OVERLAPPEDWINDOW,
    423 		_CW_USEDEFAULT, _CW_USEDEFAULT,
    424 		_CW_USEDEFAULT, _CW_USEDEFAULT,
    425 		_HWND_MESSAGE, 0, hThisInstance, 0)
    426 	if err != nil {
    427 		return err
    428 	}
    429 	return nil
    430 }
    431 
    432 func closeScreenWindow() (err error) {
    433 	// first destroy window
    434 	_DestroyWindow(screenHWND)
    435 
    436 	// then unregister class
    437 	swc, err := syscall.UTF16PtrFromString(screenWindowClass)
    438 	if err != nil {
    439 		return err
    440 	}
    441 	_UnregisterClass(swc, hThisInstance)
    442 
    443 	return nil
    444 }
    445 
    446 var (
    447 	hDefaultIcon   syscall.Handle
    448 	hDefaultCursor syscall.Handle
    449 	hThisInstance  syscall.Handle
    450 )
    451 
    452 func initCommon() (err error) {
    453 	hDefaultIcon, err = _LoadIcon(0, _IDI_APPLICATION)
    454 	if err != nil {
    455 		return err
    456 	}
    457 	hDefaultCursor, err = _LoadCursor(0, _IDC_ARROW)
    458 	if err != nil {
    459 		return err
    460 	}
    461 	// TODO(andlabs) hThisInstance
    462 	return nil
    463 }
    464 
    465 //go:uintptrescapes
    466 
    467 func SendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
    468 	return sendMessage(hwnd, uMsg, wParam, lParam)
    469 }
    470 
    471 var mainCallback func()
    472 
    473 func Main(f func()) (retErr error) {
    474 	// It does not matter which OS thread we are on.
    475 	// All that matters is that we confine all UI operations
    476 	// to the thread that created the respective window.
    477 	runtime.LockOSThread()
    478 
    479 	if err := initCommon(); err != nil {
    480 		return err
    481 	}
    482 
    483 	if err := initScreenWindow(); err != nil {
    484 		return err
    485 	}
    486 	defer func() {
    487 		// TODO(andlabs): log an error if this fails?
    488 		closeScreenWindow()
    489 	}()
    490 
    491 	if err := initWindowClass(); err != nil {
    492 		return err
    493 	}
    494 	defer func() {
    495 		// TODO(andlabs): log an error if this fails?
    496 		closeWindowClass()
    497 	}()
    498 
    499 	// Prime the pump.
    500 	mainCallback = f
    501 	_PostMessage(screenHWND, msgMainCallback, 0, 0)
    502 
    503 	// Main message pump.
    504 	var m _MSG
    505 	for {
    506 		done, err := _GetMessage(&m, 0, 0, 0)
    507 		if err != nil {
    508 			return fmt.Errorf("win32 GetMessage failed: %v", err)
    509 		}
    510 		if done == 0 { // WM_QUIT
    511 			break
    512 		}
    513 		_TranslateMessage(&m)
    514 		_DispatchMessage(&m)
    515 	}
    516 
    517 	return nil
    518 }