blob: 85183b108960b7e0bbad555983261e1d9bb7dd97 [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.
package x11driver
// TODO: implement a back buffer.
import (
"image"
"image/color"
"image/draw"
"sync"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/render"
"github.com/BurntSushi/xgb/xproto"
"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"
)
type windowImpl struct {
s *screenImpl
xw xproto.Window
xg xproto.Gcontext
xp render.Picture
pump pump.Pump
xevents chan xgb.Event
// This next group of variables are mutable, but are only modified in the
// screenImpl.run goroutine.
width, height int
mu sync.Mutex
released bool
}
func (w *windowImpl) Events() <-chan interface{} { return w.pump.Events() }
func (w *windowImpl) Send(event interface{}) { w.pump.Send(event) }
func (w *windowImpl) Release() {
w.mu.Lock()
released := w.released
w.released = true
w.mu.Unlock()
if released {
return
}
render.FreePicture(w.s.xc, w.xp)
xproto.FreeGC(w.s.xc, w.xg)
xproto.DestroyWindow(w.s.xc, w.xw)
w.pump.Release()
}
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
src.(*bufferImpl).upload(w, xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr, sender)
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
fill(w.s.xc, w.xp, dr, src, op)
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
src.(*textureImpl).draw(w.xp, &src2dst, sr, op, opts)
}
func (w *windowImpl) Publish() screen.PublishResult {
// TODO.
return screen.PublishResult{}
}
func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) {
// TODO: lifecycle events.
newWidth, newHeight := int(ev.Width), int(ev.Height)
if w.width == newWidth && w.height == newHeight {
return
}
w.width, w.height = newWidth, newHeight
// TODO: don't assume that PixelsPerPt == 1.
w.Send(size.Event{
WidthPx: newWidth,
HeightPx: newHeight,
WidthPt: geom.Pt(newWidth),
HeightPt: geom.Pt(newHeight),
PixelsPerPt: 1,
})
// TODO: translate X11 expose events to shiny paint events, instead of
// sending this synthetic paint event as a hack.
w.Send(paint.Event{})
}
func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) {
// The key event's rune depends on whether the shift key is down.
unshifted := rune(w.s.keysyms[detail][0])
r := unshifted
if state&xShiftMask != 0 {
r = rune(w.s.keysyms[detail][1])
// In X11, a zero xproto.Keysym when shift is down means to use what
// the xproto.Keysym is when shift is up.
if r == 0 {
r = unshifted
}
}
// The key event's code is independent of whether the shift key is down.
var c key.Code
if 0 <= unshifted && unshifted < 0x80 {
// TODO: distinguish the regular '2' key and number-pad '2' key (with
// Num-Lock).
c = asciiKeycodes[unshifted]
} else {
r, c = -1, nonUnicodeKeycodes[unshifted]
}
// TODO: Unicode-but-not-ASCII keysyms like the Swiss keyboard's 'รถ'.
w.Send(key.Event{
Rune: r,
Code: c,
Modifiers: keyModifiers(state),
Direction: dir,
})
}
func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) {
// TODO: should a mouse.Event have a separate MouseModifiers field, for
// which buttons are pressed during a mouse move?
w.Send(mouse.Event{
X: float32(x),
Y: float32(y),
Button: mouse.Button(b),
Modifiers: keyModifiers(state),
Direction: dir,
})
}