Files
gio/app/os_wayland.go
T
Elias Naur 2dcbf6fe3c app: confine the eglWindow indirection to the Wayland backend
Only the Wayland backend needs an wl_egl_window between the wl_surface
and EGL. Move code dealing with the indirection to Wayland specific
code.

Then, introduce the eglDriver interface instead of referencing the
native window type directly. This will help when multiple backends are
supported at runtime (e.g. Wayland+X11).

Finally, move the eglDriver implementation methods from GOOS-specific
code to separate EGL-specific files, allowing EGL types to be used
directly instead of unsafe.Pointer and uinptr.

The result is simpler generic EGL code, and easier path towards X11
support.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-10-02 23:07:54 +02:00

1190 lines
30 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
// +build linux,!android
package app
import (
"bytes"
"errors"
"fmt"
"image"
"math"
"os/exec"
"strconv"
"sync"
"time"
"unsafe"
"gioui.org/f32"
"gioui.org/internal/fling"
"gioui.org/io/key"
"gioui.org/io/pointer"
syscall "golang.org/x/sys/unix"
)
// Use wayland-scanner to generate glue code for the xdg-shell and xdg-decoration extensions.
//go:generate wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.h
//go:generate wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.c
//go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.h
//go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.c
//go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.h
//go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.c
//go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_xdg_shell.c
//go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_xdg_decoration.c
//go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_text_input.c
/*
#cgo LDFLAGS: -lwayland-client -lwayland-cursor
#include <stdlib.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
#include "wayland_text_input.h"
#include "wayland_xdg_shell.h"
#include "wayland_xdg_decoration.h"
#include "os_wayland.h"
*/
import "C"
type wlConn struct {
disp *C.struct_wl_display
compositor *C.struct_wl_compositor
wm *C.struct_xdg_wm_base
imm *C.struct_zwp_text_input_manager_v3
im *C.struct_zwp_text_input_v3
shm *C.struct_wl_shm
cursor struct {
theme *C.struct_wl_cursor_theme
cursor *C.struct_wl_cursor
surf *C.struct_wl_surface
}
decor *C.struct_zxdg_decoration_manager_v1
seat *C.struct_wl_seat
seatName C.uint32_t
pointer *C.struct_wl_pointer
touch *C.struct_wl_touch
keyboard *C.struct_wl_keyboard
xkb *xkb
repeat repeatState
}
type repeatState struct {
rate int
delay time.Duration
key C.uint32_t
win *Window
stopC chan struct{}
start time.Duration
last time.Duration
mu sync.Mutex
now time.Duration
}
type window struct {
w *Window
disp *C.struct_wl_display
surf *C.struct_wl_surface
wmSurf *C.struct_xdg_surface
topLvl *C.struct_xdg_toplevel
decor *C.struct_zxdg_toplevel_decoration_v1
// Notification pipe fds.
notify struct {
read, write int
}
ppdp, ppsp float32
scroll struct {
time time.Duration
steps image.Point
dist f32.Point
}
lastPos f32.Point
lastTouch f32.Point
fling struct {
yExtrapolation fling.Extrapolation
xExtrapolation fling.Extrapolation
anim fling.Animation
start bool
dir f32.Point
}
stage Stage
dead bool
pendingErr error
lastFrameCallback *C.struct_wl_callback
mu sync.Mutex
animating bool
needAck bool
// The last configure serial waiting to be ack'ed.
serial C.uint32_t
width int
height int
newScale bool
scale int
}
type wlOutput struct {
width int
height int
physWidth int
physHeight int
transform C.int32_t
scale int
windows []*window
}
var connMu sync.Mutex
var conn *wlConn
var mainDone = make(chan struct{})
var (
winMap = make(map[interface{}]*window)
outputMap = make(map[C.uint32_t]*C.struct_wl_output)
outputConfig = make(map[*C.struct_wl_output]*wlOutput)
)
func main() {
<-mainDone
}
func createWindow(window *Window, opts *windowOptions) error {
connMu.Lock()
defer connMu.Unlock()
if len(winMap) > 0 {
return errors.New("multiple windows are not supported")
}
if err := waylandConnect(); err != nil {
return err
}
w, err := createNativeWindow(opts)
if err != nil {
conn.destroy()
return err
}
w.w = window
go func() {
w.w.setDriver(w)
w.loop()
w.destroy()
conn.destroy()
close(mainDone)
}()
return nil
}
func createNativeWindow(opts *windowOptions) (*window, error) {
pipe := make([]int, 2)
if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
return nil, fmt.Errorf("createNativeWindow: failed to create pipe: %v", err)
}
fontScale := detectFontScale()
var ppmm float32
var scale int
for _, conf := range outputConfig {
if d, err := conf.ppmm(); err == nil && d > ppmm {
ppmm = d
}
if s := conf.scale; s > scale {
scale = s
}
}
ppdp := ppmm * mmPrDp
ppsp := ppdp * fontScale
ppdp *= monitorScale
if ppdp < minDensity {
ppdp = minDensity
}
if ppsp < minDensity {
ppsp = minDensity
}
w := &window{
disp: conn.disp,
scale: scale,
newScale: scale != 1,
ppdp: ppdp,
ppsp: ppsp,
}
w.notify.read = pipe[0]
w.notify.write = pipe[1]
w.surf = C.wl_compositor_create_surface(conn.compositor)
if w.surf == nil {
w.destroy()
return nil, errors.New("wayland: wl_compositor_create_surface failed")
}
w.wmSurf = C.xdg_wm_base_get_xdg_surface(conn.wm, w.surf)
if w.wmSurf == nil {
w.destroy()
return nil, errors.New("wayland: xdg_wm_base_get_xdg_surface failed")
}
w.topLvl = C.xdg_surface_get_toplevel(w.wmSurf)
if w.topLvl == nil {
w.destroy()
return nil, errors.New("wayland: xdg_surface_get_toplevel failed")
}
C.gio_xdg_wm_base_add_listener(conn.wm)
C.gio_wl_surface_add_listener(w.surf)
C.gio_xdg_surface_add_listener(w.wmSurf)
C.gio_xdg_toplevel_add_listener(w.topLvl)
title := C.CString(opts.Title)
C.xdg_toplevel_set_title(w.topLvl, title)
C.free(unsafe.Pointer(title))
_, _, cfg := w.config()
w.width = cfg.Px(opts.Width)
w.height = cfg.Px(opts.Height)
if conn.decor != nil {
// Request server side decorations.
w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(conn.decor, w.topLvl)
C.zxdg_toplevel_decoration_v1_set_mode(w.decor, C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE)
}
w.updateOpaqueRegion()
C.wl_surface_commit(w.surf)
winMap[w.topLvl] = w
winMap[w.surf] = w
winMap[w.wmSurf] = w
return w, nil
}
//export gio_onSeatCapabilities
func gio_onSeatCapabilities(data unsafe.Pointer, seat *C.struct_wl_seat, caps C.uint32_t) {
if seat != conn.seat {
panic("unexpected seat")
}
if conn.im == nil && conn.imm != nil {
conn.im = C.zwp_text_input_manager_v3_get_text_input(conn.imm, conn.seat)
C.gio_zwp_text_input_v3_add_listener(conn.im)
}
switch {
case conn.pointer == nil && caps&C.WL_SEAT_CAPABILITY_POINTER != 0:
conn.pointer = C.wl_seat_get_pointer(seat)
C.gio_wl_pointer_add_listener(conn.pointer)
case conn.pointer != nil && caps&C.WL_SEAT_CAPABILITY_POINTER == 0:
C.wl_pointer_release(conn.pointer)
conn.pointer = nil
}
switch {
case conn.touch == nil && caps&C.WL_SEAT_CAPABILITY_TOUCH != 0:
conn.touch = C.wl_seat_get_touch(seat)
C.gio_wl_touch_add_listener(conn.touch)
case conn.touch != nil && caps&C.WL_SEAT_CAPABILITY_TOUCH == 0:
C.wl_touch_release(conn.touch)
conn.touch = nil
}
switch {
case conn.keyboard == nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD != 0:
conn.keyboard = C.wl_seat_get_keyboard(seat)
C.gio_wl_keyboard_add_listener(conn.keyboard)
case conn.keyboard != nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD == 0:
C.wl_keyboard_release(conn.keyboard)
conn.keyboard = nil
}
}
//export gio_onSeatName
func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
}
//export gio_onXdgSurfaceConfigure
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
w := winMap[wmSurf]
w.mu.Lock()
w.serial = serial
w.needAck = true
w.mu.Unlock()
w.setStage(StageRunning)
w.draw(true)
}
//export gio_onToplevelClose
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
w := winMap[topLvl]
w.dead = true
}
//export gio_onToplevelConfigure
func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel, width, height C.int32_t, states *C.struct_wl_array) {
w := winMap[topLvl]
if width != 0 && height != 0 {
w.mu.Lock()
defer w.mu.Unlock()
w.width = int(width)
w.height = int(height)
w.updateOpaqueRegion()
}
}
//export gio_onOutputMode
func gio_onOutputMode(data unsafe.Pointer, output *C.struct_wl_output, flags C.uint32_t, width, height, refresh C.int32_t) {
if flags&C.WL_OUTPUT_MODE_CURRENT == 0 {
return
}
c := outputConfig[output]
c.width = int(width)
c.height = int(height)
}
//export gio_onOutputGeometry
func gio_onOutputGeometry(data unsafe.Pointer, output *C.struct_wl_output, x, y, physWidth, physHeight, subpixel C.int32_t, make, model *C.char, transform C.int32_t) {
c := outputConfig[output]
c.transform = transform
c.physWidth = int(physWidth)
c.physHeight = int(physHeight)
}
//export gio_onOutputScale
func gio_onOutputScale(data unsafe.Pointer, output *C.struct_wl_output, scale C.int32_t) {
c := outputConfig[output]
c.scale = int(scale)
}
//export gio_onOutputDone
func gio_onOutputDone(data unsafe.Pointer, output *C.struct_wl_output) {
conf := outputConfig[output]
for _, w := range conf.windows {
w.draw(true)
}
}
//export gio_onSurfaceEnter
func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) {
w := winMap[surf]
conf := outputConfig[output]
var found bool
for _, w2 := range conf.windows {
if w2 == w {
found = true
break
}
}
if !found {
conf.windows = append(conf.windows, w)
}
w.updateOutputs()
}
//export gio_onSurfaceLeave
func gio_onSurfaceLeave(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) {
w := winMap[surf]
conf := outputConfig[output]
for i, w2 := range conf.windows {
if w2 == w {
conf.windows = append(conf.windows[:i], conf.windows[i+1:]...)
break
}
}
w.updateOutputs()
}
//export gio_onRegistryGlobal
func gio_onRegistryGlobal(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t, cintf *C.char, version C.uint32_t) {
switch C.GoString(cintf) {
case "wl_compositor":
conn.compositor = (*C.struct_wl_compositor)(C.wl_registry_bind(reg, name, &C.wl_compositor_interface, 3))
case "wl_output":
output := (*C.struct_wl_output)(C.wl_registry_bind(reg, name, &C.wl_output_interface, 2))
C.gio_wl_output_add_listener(output)
outputMap[name] = output
outputConfig[output] = new(wlOutput)
case "wl_seat":
if conn.seat == nil {
conn.seatName = name
conn.seat = (*C.struct_wl_seat)(C.wl_registry_bind(reg, name, &C.wl_seat_interface, 5))
C.gio_wl_seat_add_listener(conn.seat)
}
case "wl_shm":
conn.shm = (*C.struct_wl_shm)(C.wl_registry_bind(reg, name, &C.wl_shm_interface, 1))
case "xdg_wm_base":
conn.wm = (*C.struct_xdg_wm_base)(C.wl_registry_bind(reg, name, &C.xdg_wm_base_interface, 1))
case "zxdg_decoration_manager_v1":
conn.decor = (*C.struct_zxdg_decoration_manager_v1)(C.wl_registry_bind(reg, name, &C.zxdg_decoration_manager_v1_interface, 1))
// TODO: Implement and test text-input support.
/*case "zwp_text_input_manager_v3":
conn.imm = (*C.struct_zwp_text_input_manager_v3)(C.wl_registry_bind(reg, name, &C.zwp_text_input_manager_v3_interface, 1))*/
}
}
//export gio_onRegistryGlobalRemove
func gio_onRegistryGlobalRemove(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t) {
if conn.seat != nil && name == conn.seatName {
if conn.im != nil {
C.zwp_text_input_v3_destroy(conn.im)
conn.im = nil
}
if conn.pointer != nil {
delete(winMap, conn.pointer)
}
if conn.touch != nil {
delete(winMap, conn.touch)
}
if conn.keyboard != nil {
delete(winMap, conn.keyboard)
}
C.wl_seat_release(conn.seat)
conn.seat = nil
}
if output, exists := outputMap[name]; exists {
C.wl_output_destroy(output)
delete(outputMap, name)
delete(outputConfig, output)
}
}
//export gio_onTouchDown
func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, surf *C.struct_wl_surface, id C.int32_t, x, y C.wl_fixed_t) {
w := winMap[surf]
winMap[touch] = w
w.lastTouch = f32.Point{
X: fromFixed(x) * float32(w.scale),
Y: fromFixed(y) * float32(w.scale),
}
w.w.event(pointer.Event{
Type: pointer.Press,
Source: pointer.Touch,
Position: w.lastTouch,
PointerID: pointer.ID(id),
Time: time.Duration(t) * time.Millisecond,
})
}
//export gio_onTouchUp
func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, id C.int32_t) {
w := winMap[touch]
w.w.event(pointer.Event{
Type: pointer.Release,
Source: pointer.Touch,
Position: w.lastTouch,
PointerID: pointer.ID(id),
Time: time.Duration(t) * time.Millisecond,
})
}
//export gio_onTouchMotion
func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32_t, id C.int32_t, x, y C.wl_fixed_t) {
w := winMap[touch]
w.lastTouch = f32.Point{
X: fromFixed(x) * float32(w.scale),
Y: fromFixed(y) * float32(w.scale),
}
w.w.event(pointer.Event{
Type: pointer.Move,
Position: w.lastTouch,
Source: pointer.Touch,
PointerID: pointer.ID(id),
Time: time.Duration(t) * time.Millisecond,
})
}
//export gio_onTouchFrame
func gio_onTouchFrame(data unsafe.Pointer, touch *C.struct_wl_touch) {
}
//export gio_onTouchCancel
func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
w := winMap[touch]
w.w.event(pointer.Event{
Type: pointer.Cancel,
Source: pointer.Touch,
})
}
//export gio_onPointerEnter
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
// Get images[0].
img := *conn.cursor.cursor.images
buf := C.wl_cursor_image_get_buffer(img)
if buf == nil {
return
}
C.wl_pointer_set_cursor(pointer, serial, conn.cursor.surf, C.int32_t(img.hotspot_x), C.int32_t(img.hotspot_y))
C.wl_surface_attach(conn.cursor.surf, buf, 0, 0)
C.wl_surface_damage(conn.cursor.surf, 0, 0, C.int32_t(img.width), C.int32_t(img.height))
C.wl_surface_commit(conn.cursor.surf)
w := winMap[surf]
winMap[pointer] = w
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
}
//export gio_onPointerLeave
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surface *C.struct_wl_surface) {
}
//export gio_onPointerMotion
func gio_onPointerMotion(data unsafe.Pointer, p *C.struct_wl_pointer, t C.uint32_t, x, y C.wl_fixed_t) {
w := winMap[p]
w.resetFling()
w.onPointerMotion(x, y, t)
}
//export gio_onPointerButton
func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t, button, state C.uint32_t) {
w := winMap[p]
// From linux-event-codes.h.
const BTN_LEFT = 0x110
if button != BTN_LEFT {
return
}
var typ pointer.Type
switch state {
case 0:
typ = pointer.Release
case 1:
typ = pointer.Press
}
w.flushScroll()
w.resetFling()
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Mouse,
Position: w.lastPos,
Time: time.Duration(t) * time.Millisecond,
})
}
//export gio_onPointerAxis
func gio_onPointerAxis(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.uint32_t, value C.wl_fixed_t) {
w := winMap[ptr]
v := fromFixed(value)
w.resetFling()
if w.scroll.dist == (f32.Point{}) {
w.scroll.time = time.Duration(t) * time.Millisecond
}
switch axis {
case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL:
w.scroll.dist.X += v
case C.WL_POINTER_AXIS_VERTICAL_SCROLL:
w.scroll.dist.Y += v
}
}
//export gio_onPointerFrame
func gio_onPointerFrame(data unsafe.Pointer, pointer *C.struct_wl_pointer) {
w := winMap[pointer]
w.flushScroll()
w.flushFling()
}
func (w *window) flushFling() {
if !w.fling.start {
return
}
w.fling.start = false
estx, esty := w.fling.xExtrapolation.Estimate(), w.fling.yExtrapolation.Estimate()
w.fling.xExtrapolation = fling.Extrapolation{}
w.fling.yExtrapolation = fling.Extrapolation{}
vel := float32(math.Sqrt(float64(estx.Velocity*estx.Velocity + esty.Velocity*esty.Velocity)))
_, _, c := w.config()
if !w.fling.anim.Start(&c, time.Now(), vel) {
return
}
invDist := 1 / vel
w.fling.dir.X = estx.Velocity * invDist
w.fling.dir.Y = esty.Velocity * invDist
// Wake up the window loop.
w.wakeup()
}
//export gio_onPointerAxisSource
func gio_onPointerAxisSource(data unsafe.Pointer, pointer *C.struct_wl_pointer, source C.uint32_t) {
}
//export gio_onPointerAxisStop
func gio_onPointerAxisStop(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.uint32_t) {
w := winMap[ptr]
w.fling.start = true
}
//export gio_onPointerAxisDiscrete
func gio_onPointerAxisDiscrete(data unsafe.Pointer, pointer *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) {
w := winMap[pointer]
w.resetFling()
switch axis {
case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL:
w.scroll.steps.X += int(discrete)
case C.WL_POINTER_AXIS_VERTICAL_SCROLL:
w.scroll.steps.Y += int(discrete)
}
}
func (w *window) resetFling() {
w.fling.start = false
w.fling.anim = fling.Animation{}
}
//export gio_onKeyboardKeymap
func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) {
defer syscall.Close(int(fd))
conn.repeat.Stop(0)
if conn.xkb != nil {
conn.xkb.Destroy()
}
if format != C.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 {
return
}
xkb, err := newXKB(format, fd, size)
if err != nil {
// TODO: Do better.
panic(err)
}
conn.xkb = xkb
}
//export gio_onKeyboardEnter
func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface, keys *C.struct_wl_array) {
conn.repeat.Stop(0)
w := winMap[surf]
winMap[keyboard] = w
w.w.event(key.FocusEvent{Focus: true})
}
//export gio_onKeyboardLeave
func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface) {
conn.repeat.Stop(0)
w := winMap[keyboard]
w.w.event(key.FocusEvent{Focus: false})
}
//export gio_onKeyboardKey
func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, timestamp, keyCode, state C.uint32_t) {
t := time.Duration(timestamp) * time.Millisecond
w := winMap[keyboard]
w.resetFling()
conn.repeat.Stop(t)
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED || conn.xkb == nil {
return
}
conn.xkb.dispatchKey(w.w, keyCode)
if conn.xkb.isRepeatKey(keyCode) {
conn.repeat.Start(w, keyCode, t)
}
}
func (r *repeatState) Start(w *window, keyCode C.uint32_t, t time.Duration) {
if r.rate <= 0 {
return
}
stopC := make(chan struct{})
r.start = t
r.last = 0
r.now = 0
r.stopC = stopC
r.key = keyCode
r.win = w.w
rate, delay := r.rate, r.delay
go func() {
timer := time.NewTimer(delay)
for {
select {
case <-timer.C:
case <-stopC:
close(stopC)
return
}
r.Advance(delay)
w.wakeup()
delay = time.Second / time.Duration(rate)
timer.Reset(delay)
}
}()
}
func (r *repeatState) Stop(t time.Duration) {
if r.stopC == nil {
return
}
r.stopC <- struct{}{}
<-r.stopC
r.stopC = nil
t -= r.start
if r.now > t {
r.now = t
}
}
func (r *repeatState) Advance(dt time.Duration) {
r.mu.Lock()
defer r.mu.Unlock()
r.now += dt
}
func (r *repeatState) Repeat() {
if r.rate <= 0 {
return
}
r.mu.Lock()
now := r.now
r.mu.Unlock()
for {
var delay time.Duration
if r.last < r.delay {
delay = r.delay
} else {
delay = time.Second / time.Duration(r.rate)
}
if r.last+delay > now {
break
}
conn.xkb.dispatchKey(r.win, r.key)
r.last += delay
}
}
//export gio_onFrameDone
func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.uint32_t) {
C.wl_callback_destroy(callback)
surf := (*C.struct_wl_surface)(data)
w := winMap[surf]
if w.lastFrameCallback == callback {
w.lastFrameCallback = nil
w.draw(false)
}
}
func (w *window) loop() {
dispfd := C.wl_display_get_fd(conn.disp)
// Poll for events and notifications.
pollfds := []syscall.PollFd{
{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
{Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
}
dispEvents := &pollfds[0].Revents
// Plenty of room for a backlog of notifications.
var buf = make([]byte, 100)
loop:
for {
C.wl_display_dispatch_pending(conn.disp)
if ret := C.wl_display_flush(conn.disp); ret < 0 {
break
}
if w.dead {
w.w.event(DestroyEvent{Err: w.pendingErr})
break
}
// Clear poll events.
*dispEvents = 0
if _, err := syscall.Ppoll(pollfds, nil, nil); err != nil && err != syscall.EINTR {
panic(fmt.Errorf("ppoll failed: %v", err))
}
redraw := false
// Clear notifications.
for {
_, err := syscall.Read(w.notify.read, buf)
if err == syscall.EAGAIN {
break
}
if err != nil {
panic(fmt.Errorf("read from notify pipe failed: %v", err))
}
redraw = true
}
// Handle events
switch {
case *dispEvents&syscall.POLLIN != 0:
if ret := C.wl_display_dispatch(conn.disp); ret < 0 {
break loop
}
case *dispEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
break loop
}
conn.repeat.Repeat()
if redraw {
w.draw(false)
}
}
}
func (w *window) setAnimating(anim bool) {
w.mu.Lock()
w.animating = anim
animating := w.isAnimating()
w.mu.Unlock()
if animating {
w.wakeup()
}
}
// Wakeup wakes up the event loop through the notification pipe.
func (w *window) wakeup() {
oneByte := make([]byte, 1)
if _, err := syscall.Write(w.notify.write, oneByte); err != nil && err != syscall.EAGAIN {
panic(fmt.Errorf("failed to write to pipe: %v", err))
}
}
func (w *window) destroy() {
if w.notify.write != 0 {
syscall.Close(w.notify.write)
w.notify.write = 0
}
if w.notify.read != 0 {
syscall.Close(w.notify.read)
w.notify.read = 0
}
if w.topLvl != nil {
delete(winMap, w.topLvl)
C.xdg_toplevel_destroy(w.topLvl)
}
if w.surf != nil {
delete(winMap, w.surf)
C.wl_surface_destroy(w.surf)
}
if w.wmSurf != nil {
delete(winMap, w.wmSurf)
C.xdg_surface_destroy(w.wmSurf)
}
if w.decor != nil {
C.zxdg_toplevel_decoration_v1_destroy(w.decor)
}
}
//export gio_onKeyboardModifiers
func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, depressed, latched, locked, group C.uint32_t) {
conn.repeat.Stop(0)
if conn.xkb == nil {
return
}
conn.xkb.updateMask(depressed, latched, locked, group)
}
//export gio_onKeyboardRepeatInfo
func gio_onKeyboardRepeatInfo(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, rate, delay C.int32_t) {
conn.repeat.Stop(0)
conn.repeat.rate = int(rate)
conn.repeat.delay = time.Duration(delay) * time.Millisecond
}
//export gio_onTextInputEnter
func gio_onTextInputEnter(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) {
}
//export gio_onTextInputLeave
func gio_onTextInputLeave(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) {
}
//export gio_onTextInputPreeditString
func gio_onTextInputPreeditString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char, begin, end C.int32_t) {
}
//export gio_onTextInputCommitString
func gio_onTextInputCommitString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char) {
}
//export gio_onTextInputDeleteSurroundingText
func gio_onTextInputDeleteSurroundingText(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, before, after C.uint32_t) {
}
//export gio_onTextInputDone
func gio_onTextInputDone(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, serial C.uint32_t) {
}
// ppmm returns the approximate pixels per millimeter for the output.
func (c *wlOutput) ppmm() (float32, error) {
if c.physWidth == 0 || c.physHeight == 0 {
return 0, errors.New("no physical size data for output")
}
// Because of https://gitlab.gnome.org/GNOME/mutter/issues/369, output dimensions might be undetectably swapped.
// Instead, compute and return sqrt(px²/mm²).
density := float32(math.Sqrt(float64(c.width*c.height) / float64(c.physWidth*c.physHeight)))
return density, nil
}
func (w *window) flushScroll() {
var fling f32.Point
if w.fling.anim.Active() {
dist := float32(w.fling.anim.Tick(time.Now()))
fling = w.fling.dir.Mul(dist)
}
// The Wayland reported scroll distance for
// discrete scroll axis is only 10 pixels, where
// 100 seems more appropriate.
const discreteScale = 10
if w.scroll.steps.X != 0 {
w.scroll.dist.X *= discreteScale
}
if w.scroll.steps.Y != 0 {
w.scroll.dist.Y *= discreteScale
}
total := w.scroll.dist.Add(fling)
if total == (f32.Point{}) {
return
}
w.w.event(pointer.Event{
Type: pointer.Move,
Source: pointer.Mouse,
Position: w.lastPos,
Scroll: total,
Time: w.scroll.time,
})
if w.scroll.steps == (image.Point{}) {
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
}
w.scroll.dist = f32.Point{}
w.scroll.steps = image.Point{}
}
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
w.flushScroll()
w.lastPos = f32.Point{
X: fromFixed(x) * float32(w.scale),
Y: fromFixed(y) * float32(w.scale),
}
w.w.event(pointer.Event{
Type: pointer.Move,
Position: w.lastPos,
Source: pointer.Mouse,
Time: time.Duration(t) * time.Millisecond,
})
}
func (w *window) updateOpaqueRegion() {
reg := C.wl_compositor_create_region(conn.compositor)
C.wl_region_add(reg, 0, 0, C.int32_t(w.width), C.int32_t(w.height))
C.wl_surface_set_opaque_region(w.surf, reg)
C.wl_region_destroy(reg)
}
func (w *window) updateOutputs() {
scale := 1
var found bool
for _, conf := range outputConfig {
for _, w2 := range conf.windows {
if w2 == w {
found = true
if conf.scale > scale {
scale = conf.scale
}
}
}
}
w.mu.Lock()
if found && scale != w.scale {
w.scale = scale
w.newScale = true
}
w.mu.Unlock()
if !found {
w.setStage(StagePaused)
} else {
w.setStage(StageRunning)
w.draw(true)
}
}
func (w *window) config() (int, int, Config) {
width, height := w.width*w.scale, w.height*w.scale
return width, height, Config{
pxPerDp: w.ppdp * float32(w.scale),
pxPerSp: w.ppsp * float32(w.scale),
}
}
func (w *window) isAnimating() bool {
return w.animating || w.fling.anim.Active()
}
func (w *window) kill(err error) {
if w.pendingErr == nil {
w.pendingErr = err
}
w.dead = true
}
func (w *window) draw(sync bool) {
w.flushScroll()
w.mu.Lock()
animating := w.isAnimating()
dead := w.dead
w.mu.Unlock()
if dead || (!animating && !sync) {
return
}
width, height, cfg := w.config()
if cfg == (Config{}) {
return
}
if animating && w.lastFrameCallback == nil {
w.lastFrameCallback = C.wl_surface_frame(w.surf)
// Use the surface as listener data for gio_onFrameDone.
C.gio_wl_callback_add_listener(w.lastFrameCallback, unsafe.Pointer(w.surf))
}
cfg.now = time.Now()
w.w.event(UpdateEvent{
Size: image.Point{
X: width,
Y: height,
},
Config: cfg,
sync: sync,
})
}
func (w *window) setStage(s Stage) {
if s == w.stage {
return
}
w.stage = s
w.w.event(StageEvent{s})
}
func (w *window) display() *C.struct_wl_display {
return w.disp
}
func (w *window) surface() (*C.struct_wl_surface, int, int) {
w.mu.Lock()
defer w.mu.Unlock()
if w.needAck {
C.xdg_surface_ack_configure(w.wmSurf, w.serial)
w.needAck = false
}
width, height, scale := w.width, w.height, w.scale
if w.newScale {
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(scale))
w.newScale = false
}
return w.surf, width * scale, height * scale
}
func (w *window) showTextInput(show bool) {}
// detectFontScale reports current font scale, or 1.0
// if it fails.
func detectFontScale() float32 {
// TODO: What about other window environments?
out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output()
if err != nil {
return 1.0
}
scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32)
if err != nil {
return 1.0
}
return float32(scale)
}
func waylandConnect() error {
c := new(wlConn)
conn = c
c.disp = C.wl_display_connect(nil)
if c.disp == nil {
c.destroy()
return errors.New("wayland: wl_display_connect failed")
}
reg := C.wl_display_get_registry(c.disp)
if reg == nil {
c.destroy()
return errors.New("wayland: wl_display_get_registry failed")
}
C.gio_wl_registry_add_listener(reg)
// Wait for the server to register all its globals to the
// registry listener (gio_onRegistryGlobal).
C.wl_display_roundtrip(c.disp)
// Configuration listeners are added to outputs by gio_onRegistryGlobal.
// We need another roundtrip to get the initial output configurations
// through the gio_onOutput* callbacks.
C.wl_display_roundtrip(c.disp)
if c.compositor == nil {
c.destroy()
return errors.New("wayland: no compositor available")
}
if c.wm == nil {
c.destroy()
return errors.New("wayland: no xdg_wm_base available")
}
if c.shm == nil {
c.destroy()
return errors.New("wayland: no wl_shm available")
}
if len(outputMap) == 0 {
c.destroy()
return errors.New("wayland: no outputs available")
}
c.cursor.theme = C.wl_cursor_theme_load(nil, 32, c.shm)
if c.cursor.theme == nil {
c.destroy()
return errors.New("wayland: wl_cursor_theme_load failed")
}
cname := C.CString("left_ptr")
defer C.free(unsafe.Pointer(cname))
c.cursor.cursor = C.wl_cursor_theme_get_cursor(c.cursor.theme, cname)
if c.cursor.cursor == nil {
c.destroy()
return errors.New("wayland: wl_cursor_theme_get_cursor failed")
}
c.cursor.surf = C.wl_compositor_create_surface(conn.compositor)
if c.cursor.surf == nil {
c.destroy()
return errors.New("wayland: wl_compositor_create_surface failed")
}
return nil
}
func (c *wlConn) destroy() {
c.repeat.Stop(0)
if c.xkb != nil {
c.xkb.Destroy()
c.xkb = nil
}
if c.cursor.surf != nil {
C.wl_surface_destroy(c.cursor.surf)
}
if c.cursor.theme != nil {
C.wl_cursor_theme_destroy(c.cursor.theme)
}
if c.keyboard != nil {
C.wl_keyboard_release(c.keyboard)
}
if c.pointer != nil {
C.wl_pointer_release(c.pointer)
}
if c.touch != nil {
C.wl_touch_release(c.touch)
}
if c.im != nil {
C.zwp_text_input_v3_destroy(c.im)
}
if c.imm != nil {
C.zwp_text_input_manager_v3_destroy(c.imm)
}
if c.seat != nil {
C.wl_seat_release(c.seat)
}
if c.decor != nil {
C.zxdg_decoration_manager_v1_destroy(c.decor)
}
if c.shm != nil {
C.wl_shm_destroy(c.shm)
}
if c.compositor != nil {
C.wl_compositor_destroy(c.compositor)
}
if c.wm != nil {
C.xdg_wm_base_destroy(c.wm)
}
for _, output := range outputMap {
C.wl_output_destroy(output)
}
if c.disp != nil {
C.wl_display_disconnect(c.disp)
}
}
// fromFixed converts a Wayland wl_fixed_t 23.8 number to float32.
func fromFixed(v C.wl_fixed_t) float32 {
// Convert to float64 to avoid overflow.
// From wayland-util.h.
b := ((1023 + 44) << 52) + (1 << 51) + uint64(v)
f := math.Float64frombits(b) - (3 << 43)
return float32(f)
}