blob: 422774a75aca672365570ff6255c05d7b9ef63c5 [file] [log] [blame]
// 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 windriver
// TODO: implement a back buffer.
import (
"image"
"image/color"
"image/draw"
"sync"
"syscall"
"unsafe"
"golang.org/x/exp/shiny/driver/internal/pump"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/geom"
)
var (
windowsMu sync.Mutex
windows = map[syscall.Handle]*windowImpl{}
uploadsMu sync.Mutex
uploads = map[uintptr]upload{}
uploadID uintptr
)
type windowImpl struct {
hwnd syscall.Handle
pump pump.Pump
}
func newWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
hwnd, err := createWindow()
if err != nil {
return nil, err
}
w := &windowImpl{
hwnd: hwnd,
pump: pump.Make(),
}
windowsMu.Lock()
windows[hwnd] = w
windowsMu.Unlock()
// Send a fake size event.
// Windows won't generate the WM_WINDOWPOSCHANGED
// we trigger a resize on for the initial size, so we have to do
// it ourselves. The example/basic program assumes it will
// receive a size.Event for the initial window size that isn't 0x0.
var r _RECT
// TODO(andlabs) error check
_GetClientRect(w.hwnd, &r)
sendSizeEvent(w.hwnd, &r)
return w, nil
}
func (w *windowImpl) Release() {
if w.hwnd == 0 { // already released?
return
}
windowsMu.Lock()
delete(windows, w.hwnd)
windowsMu.Unlock()
// TODO(andlabs): check for errors from this?
// TODO(andlabs): remove unsafe
_DestroyWindow(syscall.Handle(uintptr(unsafe.Pointer(w.hwnd))))
w.hwnd = 0
w.pump.Release()
// TODO(andlabs): what happens if we're still painting?
}
func (w *windowImpl) Events() <-chan interface{} { return w.pump.Events() }
func (w *windowImpl) Send(event interface{}) { w.pump.Send(event) }
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
// Protect struct contents from being GCed
uploadsMu.Lock()
uploadID++
id := uploadID
uploads[id] = upload{
dp: dp,
src: src.(*bufferImpl),
sr: sr,
sender: sender,
uploader: w,
}
uploadsMu.Unlock()
_SendMessage(w.hwnd, msgUpload, id, 0)
}
type upload struct {
dp image.Point
src *bufferImpl
sr image.Rectangle
sender screen.Sender
uploader screen.Uploader
}
func handleUpload(hwnd syscall.Handle, id uintptr) error {
uploadsMu.Lock()
u := uploads[id]
delete(uploads, id)
uploadsMu.Unlock()
dc, err := _GetDC(hwnd)
if err != nil {
return err
}
defer _ReleaseDC(hwnd, dc)
u.src.preUpload(u.sender != nil)
// TODO: adjust if dp is outside dst bounds, or sr is outside src bounds.
err = blit(dc, _POINT{int32(u.dp.X), int32(u.dp.Y)}, u.src.hbitmap, &_RECT{
Left: int32(u.sr.Min.X),
Top: int32(u.sr.Min.Y),
Right: int32(u.sr.Max.X),
Bottom: int32(u.sr.Max.Y),
})
go func() {
u.src.postUpload()
if u.sender != nil {
u.sender.Send(screen.UploadedEvent{
Buffer: u.src,
Uploader: u.uploader,
})
}
}()
return err
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
rect := _RECT{
Left: int32(dr.Min.X),
Top: int32(dr.Min.Y),
Right: int32(dr.Max.X),
Bottom: int32(dr.Max.Y),
}
r, g, b, a := src.RGBA()
r >>= 8
g >>= 8
b >>= 8
a >>= 8
color := (a << 24) | (r << 16) | (g << 8) | b
msg := uint32(msgFillOver)
if op == draw.Src {
msg = msgFillSrc
}
// Note: this SendMessage won't return until after the fill
// completes, so using &rect is safe.
_SendMessage(w.hwnd, msg, uintptr(color), uintptr(unsafe.Pointer(&rect)))
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
// TODO
}
func (w *windowImpl) Publish() screen.PublishResult {
// TODO
return screen.PublishResult{}
}
func handlePaint(hwnd syscall.Handle) {
windowsMu.Lock()
w := windows[hwnd]
windowsMu.Unlock()
// TODO(andlabs) - this won't be necessary after the Go rewrite
// Windows sends spurious WM_PAINT messages at window
// creation.
if w == nil {
return
}
w.Send(paint.Event{}) // TODO(andlabs): fill struct field
}
func sendSizeEvent(hwnd syscall.Handle, r *_RECT) {
windowsMu.Lock()
w := windows[hwnd]
windowsMu.Unlock()
width := int(r.Right - r.Left)
height := int(r.Bottom - r.Top)
// TODO(andlabs): don't assume that PixelsPerPt == 1
w.Send(size.Event{
WidthPx: width,
HeightPx: height,
WidthPt: geom.Pt(width),
HeightPt: geom.Pt(height),
PixelsPerPt: 1,
})
}
func sendMouseEvent(hwnd syscall.Handle, uMsg uint32, x int32, y int32) {
var dir mouse.Direction
var button mouse.Button
windowsMu.Lock()
w := windows[hwnd]
windowsMu.Unlock()
switch uMsg {
case _WM_MOUSEMOVE:
dir = mouse.DirNone
case _WM_LBUTTONDOWN, _WM_MBUTTONDOWN, _WM_RBUTTONDOWN:
dir = mouse.DirPress
case _WM_LBUTTONUP, _WM_MBUTTONUP, _WM_RBUTTONUP:
dir = mouse.DirRelease
default:
panic("sendMouseEvent() called on non-mouse message")
}
switch uMsg {
case _WM_MOUSEMOVE:
button = mouse.ButtonNone
case _WM_LBUTTONDOWN, _WM_LBUTTONUP:
button = mouse.ButtonLeft
case _WM_MBUTTONDOWN, _WM_MBUTTONUP:
button = mouse.ButtonMiddle
case _WM_RBUTTONDOWN, _WM_RBUTTONUP:
button = mouse.ButtonRight
}
// TODO(andlabs): mouse wheel
w.Send(mouse.Event{
X: float32(x),
Y: float32(y),
Button: button,
Modifiers: keyModifiers(),
Direction: dir,
})
}
// 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
}
func windowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
switch uMsg {
case _WM_PAINT:
handlePaint(hwnd)
// defer to DefWindowProc; it will handle validation for us
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
case _WM_WINDOWPOSCHANGED:
wp := (*_WINDOWPOS)(unsafe.Pointer(lParam))
if wp.Flags&_SWP_NOSIZE != 0 {
break
}
var r _RECT
if _GetClientRect(hwnd, &r) != nil {
// TODO(andlabs)
}
sendSizeEvent(hwnd, &r)
return 0
case _WM_MOUSEMOVE, _WM_LBUTTONDOWN:
// TODO(andlabs): call SetFocus()?
case _WM_LBUTTONUP, _WM_MBUTTONDOWN, _WM_MBUTTONUP, _WM_RBUTTONDOWN, _WM_RBUTTONUP:
sendMouseEvent(hwnd, uMsg, _GET_X_LPARAM(lParam), _GET_Y_LPARAM(lParam))
return 0
case _WM_KEYDOWN, _WM_KEYUP, _WM_SYSKEYDOWN, _WM_SYSKEYUP:
// TODO
case msgFillSrc:
// TODO error checks
dc, err := _GetDC(hwnd)
if err != nil {
// TODO handle errors
break
}
r := (*_RECT)(unsafe.Pointer(lParam))
// TODO handle errors
fillSrc(dc, r, _COLORREF(wParam))
_ReleaseDC(hwnd, dc)
case msgFillOver:
// TODO error checks
dc, err := _GetDC(hwnd)
if err != nil {
// TODO handle errors
break
}
r := (*_RECT)(unsafe.Pointer(lParam))
// TODO handle errors
fillOver(dc, r, _COLORREF(wParam))
_ReleaseDC(hwnd, dc)
case msgUpload:
err := handleUpload(hwnd, wParam)
if err != nil {
// TODO handle errors
break
}
}
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
}
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 createWindow() (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("Shiny Window")
if err != nil {
return 0, err
}
h, 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
_ShowWindow(h, _SW_SHOWDEFAULT)
// TODO(andlabs): call UpdateWindow()
return h, nil
}