mirror of https://github.com/miguelmota/cointop
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
492 lines
12 KiB
Go
492 lines
12 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build windows
|
|
|
|
// Package win32 implements a partial shiny screen driver using the Win32 API.
|
|
// It provides window, lifecycle, key, and mouse management, but no drawing.
|
|
// That is left to windriver (using GDI) or gldriver (using DirectX via ANGLE).
|
|
package win32 // import "golang.org/x/exp/shiny/driver/internal/win32"
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/exp/shiny/screen"
|
|
"golang.org/x/mobile/event/key"
|
|
"golang.org/x/mobile/event/lifecycle"
|
|
"golang.org/x/mobile/event/mouse"
|
|
"golang.org/x/mobile/event/paint"
|
|
"golang.org/x/mobile/event/size"
|
|
"golang.org/x/mobile/geom"
|
|
)
|
|
|
|
// screenHWND is the handle to the "Screen window".
|
|
// The Screen window encapsulates all screen.Screen operations
|
|
// in an actual Windows window so they all run on the main thread.
|
|
// Since any messages sent to a window will be executed on the
|
|
// main thread, we can safely use the messages below.
|
|
var screenHWND syscall.Handle
|
|
|
|
const (
|
|
msgCreateWindow = _WM_USER + iota
|
|
msgMainCallback
|
|
msgShow
|
|
msgQuit
|
|
msgLast
|
|
)
|
|
|
|
// userWM is used to generate private (WM_USER and above) window message IDs
|
|
// for use by screenWindowWndProc and windowWndProc.
|
|
type userWM struct {
|
|
sync.Mutex
|
|
id uint32
|
|
}
|
|
|
|
func (m *userWM) next() uint32 {
|
|
m.Lock()
|
|
if m.id == 0 {
|
|
m.id = msgLast
|
|
}
|
|
r := m.id
|
|
m.id++
|
|
m.Unlock()
|
|
return r
|
|
}
|
|
|
|
var currentUserWM userWM
|
|
|
|
func newWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
|
|
// TODO(brainman): convert windowClass to *uint16 once (in initWindowClass)
|
|
wcname, err := syscall.UTF16PtrFromString(windowClass)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
title, err := syscall.UTF16PtrFromString(opts.GetTitle())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
hwnd, err := _CreateWindowEx(0,
|
|
wcname, title,
|
|
_WS_OVERLAPPEDWINDOW,
|
|
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
|
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
|
0, 0, hThisInstance, 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
// TODO(andlabs): use proper nCmdShow
|
|
// TODO(andlabs): call UpdateWindow()
|
|
|
|
return hwnd, nil
|
|
}
|
|
|
|
// ResizeClientRect makes hwnd client rectangle opts.Width by opts.Height in size.
|
|
func ResizeClientRect(hwnd syscall.Handle, opts *screen.NewWindowOptions) error {
|
|
if opts == nil || opts.Width <= 0 || opts.Height <= 0 {
|
|
return nil
|
|
}
|
|
var cr, wr _RECT
|
|
err := _GetClientRect(hwnd, &cr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = _GetWindowRect(hwnd, &wr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w := (wr.Right - wr.Left) - (cr.Right - int32(opts.Width))
|
|
h := (wr.Bottom - wr.Top) - (cr.Bottom - int32(opts.Height))
|
|
return _MoveWindow(hwnd, wr.Left, wr.Top, w, h, false)
|
|
}
|
|
|
|
// Show shows a newly created window.
|
|
// It sends the appropriate lifecycle events, makes the window appear
|
|
// on the screen, and sends an initial size event.
|
|
//
|
|
// This is a separate step from NewWindow to give the driver a chance
|
|
// to setup its internal state for a window before events start being
|
|
// delivered.
|
|
func Show(hwnd syscall.Handle) {
|
|
SendMessage(hwnd, msgShow, 0, 0)
|
|
}
|
|
|
|
func Release(hwnd syscall.Handle) {
|
|
SendMessage(hwnd, _WM_CLOSE, 0, 0)
|
|
}
|
|
|
|
func sendFocus(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
|
switch uMsg {
|
|
case _WM_SETFOCUS:
|
|
LifecycleEvent(hwnd, lifecycle.StageFocused)
|
|
case _WM_KILLFOCUS:
|
|
LifecycleEvent(hwnd, lifecycle.StageVisible)
|
|
default:
|
|
panic(fmt.Sprintf("unexpected focus message: %d", uMsg))
|
|
}
|
|
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
|
|
func sendShow(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
|
LifecycleEvent(hwnd, lifecycle.StageVisible)
|
|
_ShowWindow(hwnd, _SW_SHOWDEFAULT)
|
|
sendSize(hwnd)
|
|
return 0
|
|
}
|
|
|
|
func sendSizeEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
|
wp := (*_WINDOWPOS)(unsafe.Pointer(lParam))
|
|
if wp.Flags&_SWP_NOSIZE != 0 {
|
|
return 0
|
|
}
|
|
sendSize(hwnd)
|
|
return 0
|
|
}
|
|
|
|
func sendSize(hwnd syscall.Handle) {
|
|
var r _RECT
|
|
if err := _GetClientRect(hwnd, &r); err != nil {
|
|
panic(err) // TODO(andlabs)
|
|
}
|
|
|
|
width := int(r.Right - r.Left)
|
|
height := int(r.Bottom - r.Top)
|
|
|
|
// TODO(andlabs): don't assume that PixelsPerPt == 1
|
|
SizeEvent(hwnd, size.Event{
|
|
WidthPx: width,
|
|
HeightPx: height,
|
|
WidthPt: geom.Pt(width),
|
|
HeightPt: geom.Pt(height),
|
|
PixelsPerPt: 1,
|
|
})
|
|
}
|
|
|
|
func sendClose(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
|
// TODO(ktye): DefWindowProc calls DestroyWindow by default.
|
|
// To intercept destruction of the window, return 0 and call
|
|
// DestroyWindow when appropriate.
|
|
LifecycleEvent(hwnd, lifecycle.StageDead)
|
|
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
|
|
func sendMouseEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
|
e := mouse.Event{
|
|
X: float32(_GET_X_LPARAM(lParam)),
|
|
Y: float32(_GET_Y_LPARAM(lParam)),
|
|
Modifiers: keyModifiers(),
|
|
}
|
|
|
|
switch uMsg {
|
|
case _WM_MOUSEMOVE:
|
|
e.Direction = mouse.DirNone
|
|
case _WM_LBUTTONDOWN, _WM_MBUTTONDOWN, _WM_RBUTTONDOWN:
|
|
e.Direction = mouse.DirPress
|
|
case _WM_LBUTTONUP, _WM_MBUTTONUP, _WM_RBUTTONUP:
|
|
e.Direction = mouse.DirRelease
|
|
case _WM_MOUSEWHEEL:
|
|
// TODO: On a trackpad, a scroll can be a drawn-out affair with a
|
|
// distinct beginning and end. Should the intermediate events be
|
|
// DirNone?
|
|
e.Direction = mouse.DirStep
|
|
|
|
// Convert from screen to window coordinates.
|
|
p := _POINT{
|
|
int32(e.X),
|
|
int32(e.Y),
|
|
}
|
|
_ScreenToClient(hwnd, &p)
|
|
e.X = float32(p.X)
|
|
e.Y = float32(p.Y)
|
|
default:
|
|
panic("sendMouseEvent() called on non-mouse message")
|
|
}
|
|
|
|
switch uMsg {
|
|
case _WM_MOUSEMOVE:
|
|
// No-op.
|
|
case _WM_LBUTTONDOWN, _WM_LBUTTONUP:
|
|
e.Button = mouse.ButtonLeft
|
|
case _WM_MBUTTONDOWN, _WM_MBUTTONUP:
|
|
e.Button = mouse.ButtonMiddle
|
|
case _WM_RBUTTONDOWN, _WM_RBUTTONUP:
|
|
e.Button = mouse.ButtonRight
|
|
case _WM_MOUSEWHEEL:
|
|
// TODO: handle horizontal scrolling
|
|
delta := _GET_WHEEL_DELTA_WPARAM(wParam) / _WHEEL_DELTA
|
|
switch {
|
|
case delta > 0:
|
|
e.Button = mouse.ButtonWheelUp
|
|
case delta < 0:
|
|
e.Button = mouse.ButtonWheelDown
|
|
delta = -delta
|
|
default:
|
|
return
|
|
}
|
|
for delta > 0 {
|
|
MouseEvent(hwnd, e)
|
|
delta--
|
|
}
|
|
return
|
|
}
|
|
|
|
MouseEvent(hwnd, e)
|
|
|
|
return 0
|
|
}
|
|
|
|
// Precondition: this is called in immediate response to the message that triggered the event (so not after w.Send).
|
|
func keyModifiers() (m key.Modifiers) {
|
|
down := func(x int32) bool {
|
|
// GetKeyState gets the key state at the time of the message, so this is what we want.
|
|
return _GetKeyState(x)&0x80 != 0
|
|
}
|
|
|
|
if down(_VK_CONTROL) {
|
|
m |= key.ModControl
|
|
}
|
|
if down(_VK_MENU) {
|
|
m |= key.ModAlt
|
|
}
|
|
if down(_VK_SHIFT) {
|
|
m |= key.ModShift
|
|
}
|
|
if down(_VK_LWIN) || down(_VK_RWIN) {
|
|
m |= key.ModMeta
|
|
}
|
|
return m
|
|
}
|
|
|
|
var (
|
|
MouseEvent func(hwnd syscall.Handle, e mouse.Event)
|
|
PaintEvent func(hwnd syscall.Handle, e paint.Event)
|
|
SizeEvent func(hwnd syscall.Handle, e size.Event)
|
|
KeyEvent func(hwnd syscall.Handle, e key.Event)
|
|
LifecycleEvent func(hwnd syscall.Handle, e lifecycle.Stage)
|
|
|
|
// TODO: use the golang.org/x/exp/shiny/driver/internal/lifecycler package
|
|
// instead of or together with the LifecycleEvent callback?
|
|
)
|
|
|
|
func sendPaint(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
|
PaintEvent(hwnd, paint.Event{})
|
|
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
|
|
var screenMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){}
|
|
|
|
func AddScreenMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
|
|
uMsg := currentUserWM.next()
|
|
screenMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
|
|
fn(hwnd, uMsg, wParam, lParam)
|
|
return 0
|
|
}
|
|
return uMsg
|
|
}
|
|
|
|
func screenWindowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
|
switch uMsg {
|
|
case msgCreateWindow:
|
|
p := (*newWindowParams)(unsafe.Pointer(lParam))
|
|
p.w, p.err = newWindow(p.opts)
|
|
case msgMainCallback:
|
|
go func() {
|
|
mainCallback()
|
|
SendScreenMessage(msgQuit, 0, 0)
|
|
}()
|
|
case msgQuit:
|
|
_PostQuitMessage(0)
|
|
}
|
|
fn := screenMsgs[uMsg]
|
|
if fn != nil {
|
|
return fn(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
|
|
//go:uintptrescapes
|
|
|
|
func SendScreenMessage(uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
|
return SendMessage(screenHWND, uMsg, wParam, lParam)
|
|
}
|
|
|
|
var windowMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){
|
|
_WM_SETFOCUS: sendFocus,
|
|
_WM_KILLFOCUS: sendFocus,
|
|
_WM_PAINT: sendPaint,
|
|
msgShow: sendShow,
|
|
_WM_WINDOWPOSCHANGED: sendSizeEvent,
|
|
_WM_CLOSE: sendClose,
|
|
|
|
_WM_LBUTTONDOWN: sendMouseEvent,
|
|
_WM_LBUTTONUP: sendMouseEvent,
|
|
_WM_MBUTTONDOWN: sendMouseEvent,
|
|
_WM_MBUTTONUP: sendMouseEvent,
|
|
_WM_RBUTTONDOWN: sendMouseEvent,
|
|
_WM_RBUTTONUP: sendMouseEvent,
|
|
_WM_MOUSEMOVE: sendMouseEvent,
|
|
_WM_MOUSEWHEEL: sendMouseEvent,
|
|
|
|
_WM_KEYDOWN: sendKeyEvent,
|
|
_WM_KEYUP: sendKeyEvent,
|
|
// TODO case _WM_SYSKEYDOWN, _WM_SYSKEYUP:
|
|
}
|
|
|
|
func AddWindowMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
|
|
uMsg := currentUserWM.next()
|
|
windowMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
|
|
fn(hwnd, uMsg, wParam, lParam)
|
|
return 0
|
|
}
|
|
return uMsg
|
|
}
|
|
|
|
func windowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
|
fn := windowMsgs[uMsg]
|
|
if fn != nil {
|
|
return fn(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
|
|
type newWindowParams struct {
|
|
opts *screen.NewWindowOptions
|
|
w syscall.Handle
|
|
err error
|
|
}
|
|
|
|
func NewWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
|
|
var p newWindowParams
|
|
p.opts = opts
|
|
SendScreenMessage(msgCreateWindow, 0, uintptr(unsafe.Pointer(&p)))
|
|
return p.w, p.err
|
|
}
|
|
|
|
const windowClass = "shiny_Window"
|
|
|
|
func initWindowClass() (err error) {
|
|
wcname, err := syscall.UTF16PtrFromString(windowClass)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = _RegisterClass(&_WNDCLASS{
|
|
LpszClassName: wcname,
|
|
LpfnWndProc: syscall.NewCallback(windowWndProc),
|
|
HIcon: hDefaultIcon,
|
|
HCursor: hDefaultCursor,
|
|
HInstance: hThisInstance,
|
|
// TODO(andlabs): change this to something else? NULL? the hollow brush?
|
|
HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
|
|
})
|
|
return err
|
|
}
|
|
|
|
func initScreenWindow() (err error) {
|
|
const screenWindowClass = "shiny_ScreenWindow"
|
|
swc, err := syscall.UTF16PtrFromString(screenWindowClass)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
emptyString, err := syscall.UTF16PtrFromString("")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
wc := _WNDCLASS{
|
|
LpszClassName: swc,
|
|
LpfnWndProc: syscall.NewCallback(screenWindowWndProc),
|
|
HIcon: hDefaultIcon,
|
|
HCursor: hDefaultCursor,
|
|
HInstance: hThisInstance,
|
|
HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
|
|
}
|
|
_, err = _RegisterClass(&wc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
screenHWND, err = _CreateWindowEx(0,
|
|
swc, emptyString,
|
|
_WS_OVERLAPPEDWINDOW,
|
|
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
|
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
|
_HWND_MESSAGE, 0, hThisInstance, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
hDefaultIcon syscall.Handle
|
|
hDefaultCursor syscall.Handle
|
|
hThisInstance syscall.Handle
|
|
)
|
|
|
|
func initCommon() (err error) {
|
|
hDefaultIcon, err = _LoadIcon(0, _IDI_APPLICATION)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hDefaultCursor, err = _LoadCursor(0, _IDC_ARROW)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// TODO(andlabs) hThisInstance
|
|
return nil
|
|
}
|
|
|
|
//go:uintptrescapes
|
|
|
|
func SendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
|
return sendMessage(hwnd, uMsg, wParam, lParam)
|
|
}
|
|
|
|
var mainCallback func()
|
|
|
|
func Main(f func()) (retErr error) {
|
|
// It does not matter which OS thread we are on.
|
|
// All that matters is that we confine all UI operations
|
|
// to the thread that created the respective window.
|
|
runtime.LockOSThread()
|
|
|
|
if err := initCommon(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := initScreenWindow(); err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
// TODO(andlabs): log an error if this fails?
|
|
_DestroyWindow(screenHWND)
|
|
// TODO(andlabs): unregister window class
|
|
}()
|
|
|
|
if err := initWindowClass(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Prime the pump.
|
|
mainCallback = f
|
|
_PostMessage(screenHWND, msgMainCallback, 0, 0)
|
|
|
|
// Main message pump.
|
|
var m _MSG
|
|
for {
|
|
done, err := _GetMessage(&m, 0, 0, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("win32 GetMessage failed: %v", err)
|
|
}
|
|
if done == 0 { // WM_QUIT
|
|
break
|
|
}
|
|
_TranslateMessage(&m)
|
|
_DispatchMessage(&m)
|
|
}
|
|
|
|
return nil
|
|
}
|