aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/app/os_wayland.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/app/os_wayland.go')
-rw-r--r--vendor/gioui.org/app/os_wayland.go1623
1 files changed, 1623 insertions, 0 deletions
diff --git a/vendor/gioui.org/app/os_wayland.go b/vendor/gioui.org/app/os_wayland.go
new file mode 100644
index 0000000..ac7aa17
--- /dev/null
+++ b/vendor/gioui.org/app/os_wayland.go
@@ -0,0 +1,1623 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd) && !nowayland
+// +build linux,!android freebsd
+// +build !nowayland
+
+package app
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "image"
+ "io"
+ "io/ioutil"
+ "math"
+ "os"
+ "os/exec"
+ "strconv"
+ "sync"
+ "time"
+ "unsafe"
+
+ syscall "golang.org/x/sys/unix"
+
+ "gioui.org/app/internal/xkb"
+ "gioui.org/f32"
+ "gioui.org/internal/fling"
+ "gioui.org/io/clipboard"
+ "gioui.org/io/key"
+ "gioui.org/io/pointer"
+ "gioui.org/io/system"
+ "gioui.org/unit"
+)
+
+// 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;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_xdg_shell.c
+//go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_xdg_decoration.c
+//go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_text_input.c
+
+/*
+#cgo linux pkg-config: wayland-client wayland-cursor
+#cgo freebsd openbsd LDFLAGS: -lwayland-client -lwayland-cursor
+#cgo freebsd CFLAGS: -I/usr/local/include
+#cgo freebsd LDFLAGS: -L/usr/local/lib
+
+#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"
+
+extern const struct wl_registry_listener gio_registry_listener;
+extern const struct wl_surface_listener gio_surface_listener;
+extern const struct xdg_surface_listener gio_xdg_surface_listener;
+extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener;
+extern const struct xdg_wm_base_listener gio_xdg_wm_base_listener;
+extern const struct wl_callback_listener gio_callback_listener;
+extern const struct wl_output_listener gio_output_listener;
+extern const struct wl_seat_listener gio_seat_listener;
+extern const struct wl_pointer_listener gio_pointer_listener;
+extern const struct wl_touch_listener gio_touch_listener;
+extern const struct wl_keyboard_listener gio_keyboard_listener;
+extern const struct zwp_text_input_v3_listener gio_zwp_text_input_v3_listener;
+extern const struct wl_data_device_listener gio_data_device_listener;
+extern const struct wl_data_offer_listener gio_data_offer_listener;
+extern const struct wl_data_source_listener gio_data_source_listener;
+*/
+import "C"
+
+type wlDisplay struct {
+ disp *C.struct_wl_display
+ reg *C.struct_wl_registry
+ compositor *C.struct_wl_compositor
+ wm *C.struct_xdg_wm_base
+ imm *C.struct_zwp_text_input_manager_v3
+ shm *C.struct_wl_shm
+ dataDeviceManager *C.struct_wl_data_device_manager
+ decor *C.struct_zxdg_decoration_manager_v1
+ seat *wlSeat
+ xkb *xkb.Context
+ outputMap map[C.uint32_t]*C.struct_wl_output
+ outputConfig map[*C.struct_wl_output]*wlOutput
+
+ // Notification pipe fds.
+ notify struct {
+ read, write int
+ }
+
+ repeat repeatState
+}
+
+type wlSeat struct {
+ disp *wlDisplay
+ seat *C.struct_wl_seat
+ name C.uint32_t
+ pointer *C.struct_wl_pointer
+ touch *C.struct_wl_touch
+ keyboard *C.struct_wl_keyboard
+ im *C.struct_zwp_text_input_v3
+
+ // The most recent input serial.
+ serial C.uint32_t
+
+ pointerFocus *window
+ keyboardFocus *window
+ touchFoci map[C.int32_t]*window
+
+ // Clipboard support.
+ dataDev *C.struct_wl_data_device
+ // offers is a map from active wl_data_offers to
+ // the list of mime types they support.
+ offers map[*C.struct_wl_data_offer][]string
+ // clipboard is the wl_data_offer for the clipboard.
+ clipboard *C.struct_wl_data_offer
+ // mimeType is the chosen mime type of clipboard.
+ mimeType string
+ // source represents the clipboard content of the most recent
+ // clipboard write, if any.
+ source *C.struct_wl_data_source
+ // content is the data belonging to source.
+ content []byte
+}
+
+type repeatState struct {
+ rate int
+ delay time.Duration
+
+ key uint32
+ win *callbacks
+ stopC chan struct{}
+
+ start time.Duration
+ last time.Duration
+ mu sync.Mutex
+ now time.Duration
+}
+
+type window struct {
+ w *callbacks
+ disp *wlDisplay
+ surf *C.struct_wl_surface
+ wmSurf *C.struct_xdg_surface
+ topLvl *C.struct_xdg_toplevel
+ decor *C.struct_zxdg_toplevel_decoration_v1
+ ppdp, ppsp float32
+ scroll struct {
+ time time.Duration
+ steps image.Point
+ dist f32.Point
+ }
+ pointerBtns pointer.Buttons
+ lastPos f32.Point
+ lastTouch f32.Point
+
+ cursor struct {
+ theme *C.struct_wl_cursor_theme
+ cursor *C.struct_wl_cursor
+ surf *C.struct_wl_surface
+ }
+
+ fling struct {
+ yExtrapolation fling.Extrapolation
+ xExtrapolation fling.Extrapolation
+ anim fling.Animation
+ start bool
+ dir f32.Point
+ }
+
+ stage system.Stage
+ dead bool
+ lastFrameCallback *C.struct_wl_callback
+
+ animating bool
+ needAck bool
+ // The most recent configure serial waiting to be ack'ed.
+ serial C.uint32_t
+ newScale bool
+ scale int
+ // size is the unscaled window size (unlike config.Size which is scaled).
+ size image.Point
+ config Config
+
+ wakeups chan struct{}
+}
+
+type poller struct {
+ pollfds [2]syscall.PollFd
+ // buf is scratch space for draining the notification pipe.
+ buf [100]byte
+}
+
+type wlOutput struct {
+ width int
+ height int
+ physWidth int
+ physHeight int
+ transform C.int32_t
+ scale int
+ windows []*window
+}
+
+// callbackMap maps Wayland native handles to corresponding Go
+// references. It is necessary because the the Wayland client API
+// forces the use of callbacks and storing pointers to Go values
+// in C is forbidden.
+var callbackMap sync.Map
+
+// clipboardMimeTypes is a list of supported clipboard mime types, in
+// order of preference.
+var clipboardMimeTypes = []string{"text/plain;charset=utf8", "UTF8_STRING", "text/plain", "TEXT", "STRING"}
+
+var (
+ newWaylandEGLContext func(w *window) (context, error)
+ newWaylandVulkanContext func(w *window) (context, error)
+)
+
+func init() {
+ wlDriver = newWLWindow
+}
+
+func newWLWindow(callbacks *callbacks, options []Option) error {
+ d, err := newWLDisplay()
+ if err != nil {
+ return err
+ }
+ w, err := d.createNativeWindow(options)
+ if err != nil {
+ d.destroy()
+ return err
+ }
+ w.w = callbacks
+ go func() {
+ defer d.destroy()
+ defer w.destroy()
+
+ w.w.SetDriver(w)
+ // Finish and commit setup from createNativeWindow.
+ w.Configure(options)
+ C.wl_surface_commit(w.surf)
+ if err := w.loop(); err != nil {
+ panic(err)
+ }
+ }()
+ return nil
+}
+
+func (d *wlDisplay) writeClipboard(content []byte) error {
+ s := d.seat
+ if s == nil {
+ return nil
+ }
+ // Clear old offer.
+ if s.source != nil {
+ C.wl_data_source_destroy(s.source)
+ s.source = nil
+ s.content = nil
+ }
+ if d.dataDeviceManager == nil || s.dataDev == nil {
+ return nil
+ }
+ s.content = content
+ s.source = C.wl_data_device_manager_create_data_source(d.dataDeviceManager)
+ C.wl_data_source_add_listener(s.source, &C.gio_data_source_listener, unsafe.Pointer(s.seat))
+ for _, mime := range clipboardMimeTypes {
+ C.wl_data_source_offer(s.source, C.CString(mime))
+ }
+ C.wl_data_device_set_selection(s.dataDev, s.source, s.serial)
+ return nil
+}
+
+func (d *wlDisplay) readClipboard() (io.ReadCloser, error) {
+ s := d.seat
+ if s == nil {
+ return nil, nil
+ }
+ if s.clipboard == nil {
+ return nil, nil
+ }
+ r, w, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ // wl_data_offer_receive performs and implicit dup(2) of the write end
+ // of the pipe. Close our version.
+ defer w.Close()
+ cmimeType := C.CString(s.mimeType)
+ defer C.free(unsafe.Pointer(cmimeType))
+ C.wl_data_offer_receive(s.clipboard, cmimeType, C.int(w.Fd()))
+ return r, nil
+}
+
+func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
+ if d.compositor == nil {
+ return nil, errors.New("wayland: no compositor available")
+ }
+ if d.wm == nil {
+ return nil, errors.New("wayland: no xdg_wm_base available")
+ }
+ if d.shm == nil {
+ return nil, errors.New("wayland: no wl_shm available")
+ }
+ if len(d.outputMap) == 0 {
+ return nil, errors.New("wayland: no outputs available")
+ }
+ var scale int
+ for _, conf := range d.outputConfig {
+ if s := conf.scale; s > scale {
+ scale = s
+ }
+ }
+ ppdp := detectUIScale()
+
+ w := &window{
+ disp: d,
+ scale: scale,
+ newScale: scale != 1,
+ ppdp: ppdp,
+ ppsp: ppdp,
+ wakeups: make(chan struct{}, 1),
+ }
+ w.surf = C.wl_compositor_create_surface(d.compositor)
+ if w.surf == nil {
+ w.destroy()
+ return nil, errors.New("wayland: wl_compositor_create_surface failed")
+ }
+ callbackStore(unsafe.Pointer(w.surf), w)
+ w.wmSurf = C.xdg_wm_base_get_xdg_surface(d.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")
+ }
+ w.cursor.theme = C.wl_cursor_theme_load(nil, 32, d.shm)
+ if w.cursor.theme == nil {
+ w.destroy()
+ return nil, errors.New("wayland: wl_cursor_theme_load failed")
+ }
+ cname := C.CString("left_ptr")
+ defer C.free(unsafe.Pointer(cname))
+ w.cursor.cursor = C.wl_cursor_theme_get_cursor(w.cursor.theme, cname)
+ if w.cursor.cursor == nil {
+ w.destroy()
+ return nil, errors.New("wayland: wl_cursor_theme_get_cursor failed")
+ }
+ w.cursor.surf = C.wl_compositor_create_surface(d.compositor)
+ if w.cursor.surf == nil {
+ w.destroy()
+ return nil, errors.New("wayland: wl_compositor_create_surface failed")
+ }
+ C.xdg_wm_base_add_listener(d.wm, &C.gio_xdg_wm_base_listener, unsafe.Pointer(w.surf))
+ C.wl_surface_add_listener(w.surf, &C.gio_surface_listener, unsafe.Pointer(w.surf))
+ C.xdg_surface_add_listener(w.wmSurf, &C.gio_xdg_surface_listener, unsafe.Pointer(w.surf))
+ C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))
+
+ if d.decor != nil {
+ // Request server side decorations.
+ w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl)
+ C.zxdg_toplevel_decoration_v1_set_mode(w.decor, C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE)
+ }
+ w.updateOpaqueRegion()
+ return w, nil
+}
+
+func callbackDelete(k unsafe.Pointer) {
+ callbackMap.Delete(k)
+}
+
+func callbackStore(k unsafe.Pointer, v interface{}) {
+ callbackMap.Store(k, v)
+}
+
+func callbackLoad(k unsafe.Pointer) interface{} {
+ v, exists := callbackMap.Load(k)
+ if !exists {
+ panic("missing callback entry")
+ }
+ return v
+}
+
+//export gio_onSeatCapabilities
+func gio_onSeatCapabilities(data unsafe.Pointer, seat *C.struct_wl_seat, caps C.uint32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ s.updateCaps(caps)
+}
+
+// flushOffers remove all wl_data_offers that isn't the clipboard
+// content.
+func (s *wlSeat) flushOffers() {
+ for o := range s.offers {
+ if o == s.clipboard {
+ continue
+ }
+ // We're only interested in clipboard offers.
+ delete(s.offers, o)
+ callbackDelete(unsafe.Pointer(o))
+ C.wl_data_offer_destroy(o)
+ }
+}
+
+func (s *wlSeat) destroy() {
+ if s.source != nil {
+ C.wl_data_source_destroy(s.source)
+ s.source = nil
+ }
+ if s.im != nil {
+ C.zwp_text_input_v3_destroy(s.im)
+ s.im = nil
+ }
+ if s.pointer != nil {
+ C.wl_pointer_release(s.pointer)
+ }
+ if s.touch != nil {
+ C.wl_touch_release(s.touch)
+ }
+ if s.keyboard != nil {
+ C.wl_keyboard_release(s.keyboard)
+ }
+ s.clipboard = nil
+ s.flushOffers()
+ if s.dataDev != nil {
+ C.wl_data_device_release(s.dataDev)
+ }
+ if s.seat != nil {
+ callbackDelete(unsafe.Pointer(s.seat))
+ C.wl_seat_release(s.seat)
+ }
+}
+
+func (s *wlSeat) updateCaps(caps C.uint32_t) {
+ if s.im == nil && s.disp.imm != nil {
+ s.im = C.zwp_text_input_manager_v3_get_text_input(s.disp.imm, s.seat)
+ C.zwp_text_input_v3_add_listener(s.im, &C.gio_zwp_text_input_v3_listener, unsafe.Pointer(s.seat))
+ }
+ switch {
+ case s.pointer == nil && caps&C.WL_SEAT_CAPABILITY_POINTER != 0:
+ s.pointer = C.wl_seat_get_pointer(s.seat)
+ C.wl_pointer_add_listener(s.pointer, &C.gio_pointer_listener, unsafe.Pointer(s.seat))
+ case s.pointer != nil && caps&C.WL_SEAT_CAPABILITY_POINTER == 0:
+ C.wl_pointer_release(s.pointer)
+ s.pointer = nil
+ }
+ switch {
+ case s.touch == nil && caps&C.WL_SEAT_CAPABILITY_TOUCH != 0:
+ s.touch = C.wl_seat_get_touch(s.seat)
+ C.wl_touch_add_listener(s.touch, &C.gio_touch_listener, unsafe.Pointer(s.seat))
+ case s.touch != nil && caps&C.WL_SEAT_CAPABILITY_TOUCH == 0:
+ C.wl_touch_release(s.touch)
+ s.touch = nil
+ }
+ switch {
+ case s.keyboard == nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD != 0:
+ s.keyboard = C.wl_seat_get_keyboard(s.seat)
+ C.wl_keyboard_add_listener(s.keyboard, &C.gio_keyboard_listener, unsafe.Pointer(s.seat))
+ case s.keyboard != nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD == 0:
+ C.wl_keyboard_release(s.keyboard)
+ s.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 := callbackLoad(data).(*window)
+ w.serial = serial
+ w.needAck = true
+ w.setStage(system.StageRunning)
+ w.draw(true)
+}
+
+//export gio_onToplevelClose
+func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
+ w := callbackLoad(data).(*window)
+ 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 := callbackLoad(data).(*window)
+ if width != 0 && height != 0 {
+ w.size = image.Pt(int(width), 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
+ }
+ d := callbackLoad(data).(*wlDisplay)
+ c := d.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) {
+ d := callbackLoad(data).(*wlDisplay)
+ c := d.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) {
+ d := callbackLoad(data).(*wlDisplay)
+ c := d.outputConfig[output]
+ c.scale = int(scale)
+}
+
+//export gio_onOutputDone
+func gio_onOutputDone(data unsafe.Pointer, output *C.struct_wl_output) {
+ d := callbackLoad(data).(*wlDisplay)
+ conf := d.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 := callbackLoad(data).(*window)
+ conf := w.disp.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 := callbackLoad(data).(*window)
+ conf := w.disp.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) {
+ d := callbackLoad(data).(*wlDisplay)
+ switch C.GoString(cintf) {
+ case "wl_compositor":
+ d.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.wl_output_add_listener(output, &C.gio_output_listener, unsafe.Pointer(d.disp))
+ d.outputMap[name] = output
+ d.outputConfig[output] = new(wlOutput)
+ case "wl_seat":
+ if d.seat != nil {
+ break
+ }
+ s := (*C.struct_wl_seat)(C.wl_registry_bind(reg, name, &C.wl_seat_interface, 5))
+ if s == nil {
+ // No support for v5 protocol.
+ break
+ }
+ d.seat = &wlSeat{
+ disp: d,
+ name: name,
+ seat: s,
+ offers: make(map[*C.struct_wl_data_offer][]string),
+ touchFoci: make(map[C.int32_t]*window),
+ }
+ callbackStore(unsafe.Pointer(s), d.seat)
+ C.wl_seat_add_listener(s, &C.gio_seat_listener, unsafe.Pointer(s))
+ if d.dataDeviceManager == nil {
+ break
+ }
+ d.seat.dataDev = C.wl_data_device_manager_get_data_device(d.dataDeviceManager, s)
+ if d.seat.dataDev == nil {
+ break
+ }
+ callbackStore(unsafe.Pointer(d.seat.dataDev), d.seat)
+ C.wl_data_device_add_listener(d.seat.dataDev, &C.gio_data_device_listener, unsafe.Pointer(d.seat.dataDev))
+ case "wl_shm":
+ d.shm = (*C.struct_wl_shm)(C.wl_registry_bind(reg, name, &C.wl_shm_interface, 1))
+ case "xdg_wm_base":
+ d.wm = (*C.struct_xdg_wm_base)(C.wl_registry_bind(reg, name, &C.xdg_wm_base_interface, 1))
+ case "zxdg_decoration_manager_v1":
+ d.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":
+ d.imm = (*C.struct_zwp_text_input_manager_v3)(C.wl_registry_bind(reg, name, &C.zwp_text_input_manager_v3_interface, 1))*/
+ case "wl_data_device_manager":
+ d.dataDeviceManager = (*C.struct_wl_data_device_manager)(C.wl_registry_bind(reg, name, &C.wl_data_device_manager_interface, 3))
+ }
+}
+
+//export gio_onDataOfferOffer
+func gio_onDataOfferOffer(data unsafe.Pointer, offer *C.struct_wl_data_offer, mime *C.char) {
+ s := callbackLoad(data).(*wlSeat)
+ s.offers[offer] = append(s.offers[offer], C.GoString(mime))
+}
+
+//export gio_onDataOfferSourceActions
+func gio_onDataOfferSourceActions(data unsafe.Pointer, offer *C.struct_wl_data_offer, acts C.uint32_t) {
+}
+
+//export gio_onDataOfferAction
+func gio_onDataOfferAction(data unsafe.Pointer, offer *C.struct_wl_data_offer, act C.uint32_t) {
+}
+
+//export gio_onDataDeviceOffer
+func gio_onDataDeviceOffer(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) {
+ s := callbackLoad(data).(*wlSeat)
+ callbackStore(unsafe.Pointer(id), s)
+ C.wl_data_offer_add_listener(id, &C.gio_data_offer_listener, unsafe.Pointer(id))
+ s.offers[id] = nil
+}
+
+//export gio_onDataDeviceEnter
+func gio_onDataDeviceEnter(data unsafe.Pointer, dataDev *C.struct_wl_data_device, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t, id *C.struct_wl_data_offer) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ s.flushOffers()
+}
+
+//export gio_onDataDeviceLeave
+func gio_onDataDeviceLeave(data unsafe.Pointer, dataDev *C.struct_wl_data_device) {
+}
+
+//export gio_onDataDeviceMotion
+func gio_onDataDeviceMotion(data unsafe.Pointer, dataDev *C.struct_wl_data_device, t C.uint32_t, x, y C.wl_fixed_t) {
+}
+
+//export gio_onDataDeviceDrop
+func gio_onDataDeviceDrop(data unsafe.Pointer, dataDev *C.struct_wl_data_device) {
+}
+
+//export gio_onDataDeviceSelection
+func gio_onDataDeviceSelection(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) {
+ s := callbackLoad(data).(*wlSeat)
+ defer s.flushOffers()
+ s.clipboard = nil
+loop:
+ for _, want := range clipboardMimeTypes {
+ for _, got := range s.offers[id] {
+ if want != got {
+ continue
+ }
+ s.clipboard = id
+ s.mimeType = got
+ break loop
+ }
+ }
+}
+
+//export gio_onRegistryGlobalRemove
+func gio_onRegistryGlobalRemove(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t) {
+ d := callbackLoad(data).(*wlDisplay)
+ if s := d.seat; s != nil && name == s.name {
+ s.destroy()
+ d.seat = nil
+ }
+ if output, exists := d.outputMap[name]; exists {
+ C.wl_output_destroy(output)
+ delete(d.outputMap, name)
+ delete(d.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) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ w := callbackLoad(unsafe.Pointer(surf)).(*window)
+ s.touchFoci[id] = 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,
+ Modifiers: w.disp.xkb.Modifiers(),
+ })
+}
+
+//export gio_onTouchUp
+func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, id C.int32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ w := s.touchFoci[id]
+ delete(s.touchFoci, id)
+ w.w.Event(pointer.Event{
+ Type: pointer.Release,
+ Source: pointer.Touch,
+ Position: w.lastTouch,
+ PointerID: pointer.ID(id),
+ Time: time.Duration(t) * time.Millisecond,
+ Modifiers: w.disp.xkb.Modifiers(),
+ })
+}
+
+//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) {
+ s := callbackLoad(data).(*wlSeat)
+ w := s.touchFoci[id]
+ 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,
+ Modifiers: w.disp.xkb.Modifiers(),
+ })
+}
+
+//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) {
+ s := callbackLoad(data).(*wlSeat)
+ for id, w := range s.touchFoci {
+ delete(s.touchFoci, id)
+ 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) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ w := callbackLoad(unsafe.Pointer(surf)).(*window)
+ s.pointerFocus = w
+ w.setCursor(pointer, serial)
+ 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) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+}
+
+//export gio_onPointerMotion
+func gio_onPointerMotion(data unsafe.Pointer, p *C.struct_wl_pointer, t C.uint32_t, x, y C.wl_fixed_t) {
+ s := callbackLoad(data).(*wlSeat)
+ w := s.pointerFocus
+ w.resetFling()
+ w.onPointerMotion(x, y, t)
+}
+
+//export gio_onPointerButton
+func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t, wbtn, state C.uint32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ w := s.pointerFocus
+ // From linux-event-codes.h.
+ const (
+ BTN_LEFT = 0x110
+ BTN_RIGHT = 0x111
+ BTN_MIDDLE = 0x112
+ )
+ var btn pointer.Buttons
+ switch wbtn {
+ case BTN_LEFT:
+ btn = pointer.ButtonPrimary
+ case BTN_RIGHT:
+ btn = pointer.ButtonSecondary
+ case BTN_MIDDLE:
+ btn = pointer.ButtonTertiary
+ default:
+ return
+ }
+ var typ pointer.Type
+ switch state {
+ case 0:
+ w.pointerBtns &^= btn
+ typ = pointer.Release
+ case 1:
+ w.pointerBtns |= btn
+ typ = pointer.Press
+ }
+ w.flushScroll()
+ w.resetFling()
+ w.w.Event(pointer.Event{
+ Type: typ,
+ Source: pointer.Mouse,
+ Buttons: w.pointerBtns,
+ Position: w.lastPos,
+ Time: time.Duration(t) * time.Millisecond,
+ Modifiers: w.disp.xkb.Modifiers(),
+ })
+}
+
+//export gio_onPointerAxis
+func gio_onPointerAxis(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.uint32_t, value C.wl_fixed_t) {
+ s := callbackLoad(data).(*wlSeat)
+ w := s.pointerFocus
+ 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, p *C.struct_wl_pointer) {
+ s := callbackLoad(data).(*wlSeat)
+ w := s.pointerFocus
+ 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.getConfig()
+ 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
+}
+
+//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, p *C.struct_wl_pointer, t, axis C.uint32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ w := s.pointerFocus
+ w.fling.start = true
+}
+
+//export gio_onPointerAxisDiscrete
+func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ w := s.pointerFocus
+ 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) ReadClipboard() {
+ r, err := w.disp.readClipboard()
+ // Send empty responses on unavailable clipboards or errors.
+ if r == nil || err != nil {
+ w.w.Event(clipboard.Event{})
+ return
+ }
+ // Don't let slow clipboard transfers block event loop.
+ go func() {
+ defer r.Close()
+ data, _ := ioutil.ReadAll(r)
+ w.w.Event(clipboard.Event{Text: string(data)})
+ }()
+}
+
+func (w *window) WriteClipboard(s string) {
+ w.disp.writeClipboard([]byte(s))
+}
+
+func (w *window) Configure(options []Option) {
+ _, cfg := w.getConfig()
+ prev := w.config
+ cnf := w.config
+ cnf.apply(cfg, options)
+ if prev.Size != cnf.Size {
+ w.size = image.Pt(cnf.Size.X/w.scale, cnf.Size.Y/w.scale)
+ w.config.Size = cnf.Size
+ }
+ if prev.Title != cnf.Title {
+ w.config.Title = cnf.Title
+ title := C.CString(cnf.Title)
+ C.xdg_toplevel_set_title(w.topLvl, title)
+ C.free(unsafe.Pointer(title))
+ }
+ if w.config != prev {
+ w.w.Event(ConfigEvent{Config: w.config})
+ }
+}
+
+func (w *window) Raise() {}
+
+func (w *window) SetCursor(name pointer.CursorName) {
+ ptr := w.disp.seat.pointer
+ if ptr == nil {
+ return
+ }
+ if name == pointer.CursorNone {
+ C.wl_pointer_set_cursor(ptr, w.serial, nil, 0, 0)
+ return
+ }
+ switch name {
+ default:
+ fallthrough
+ case pointer.CursorDefault:
+ name = "left_ptr"
+ case pointer.CursorText:
+ name = "xterm"
+ case pointer.CursorPointer:
+ name = "hand1"
+ case pointer.CursorCrossHair:
+ name = "crosshair"
+ case pointer.CursorRowResize:
+ name = "top_side"
+ case pointer.CursorColResize:
+ name = "left_side"
+ case pointer.CursorGrab:
+ name = "hand1"
+ }
+ cname := C.CString(string(name))
+ defer C.free(unsafe.Pointer(cname))
+ c := C.wl_cursor_theme_get_cursor(w.cursor.theme, cname)
+ if c == nil {
+ return
+ }
+ w.cursor.cursor = c
+ w.setCursor(ptr, w.serial)
+}
+
+func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
+ // Get images[0].
+ img := *w.cursor.cursor.images
+ buf := C.wl_cursor_image_get_buffer(img)
+ if buf == nil {
+ return
+ }
+ C.wl_pointer_set_cursor(pointer, serial, w.cursor.surf, C.int32_t(img.hotspot_x), C.int32_t(img.hotspot_y))
+ C.wl_surface_attach(w.cursor.surf, buf, 0, 0)
+ C.wl_surface_damage(w.cursor.surf, 0, 0, C.int32_t(img.width), C.int32_t(img.height))
+ C.wl_surface_commit(w.cursor.surf)
+}
+
+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))
+ s := callbackLoad(data).(*wlSeat)
+ s.disp.repeat.Stop(0)
+ s.disp.xkb.DestroyKeymapState()
+ if format != C.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 {
+ return
+ }
+ if err := s.disp.xkb.LoadKeymap(int(format), int(fd), int(size)); err != nil {
+ // TODO: Do better.
+ panic(err)
+ }
+}
+
+//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) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ w := callbackLoad(unsafe.Pointer(surf)).(*window)
+ s.keyboardFocus = w
+ s.disp.repeat.Stop(0)
+ 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) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ s.disp.repeat.Stop(0)
+ w := s.keyboardFocus
+ 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) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ w := s.keyboardFocus
+ t := time.Duration(timestamp) * time.Millisecond
+ s.disp.repeat.Stop(t)
+ w.resetFling()
+ kc := mapXKBKeycode(uint32(keyCode))
+ ks := mapXKBKeyState(uint32(state))
+ for _, e := range w.disp.xkb.DispatchKey(kc, ks) {
+ w.w.Event(e)
+ }
+ if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
+ return
+ }
+ if w.disp.xkb.IsRepeatKey(kc) {
+ w.disp.repeat.Start(w, kc, t)
+ }
+}
+
+func mapXKBKeycode(keyCode uint32) uint32 {
+ // According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode."
+ return keyCode + 8
+}
+
+func mapXKBKeyState(state uint32) key.State {
+ switch state {
+ case C.WL_KEYBOARD_KEY_STATE_RELEASED:
+ return key.Release
+ default:
+ return key.Press
+ }
+}
+
+func (r *repeatState) Start(w *window, keyCode uint32, 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.disp.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(d *wlDisplay) {
+ 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
+ }
+ for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
+ r.win.Event(e)
+ }
+ 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)
+ w := callbackLoad(data).(*window)
+ if w.lastFrameCallback == callback {
+ w.lastFrameCallback = nil
+ w.draw(false)
+ }
+}
+
+func (w *window) loop() error {
+ var p poller
+ for {
+ if err := w.disp.dispatch(&p); err != nil {
+ return err
+ }
+ select {
+ case <-w.wakeups:
+ w.w.Event(wakeupEvent{})
+ default:
+ }
+ if w.dead {
+ w.w.Event(system.DestroyEvent{})
+ break
+ }
+ // pass false to skip unnecessary drawing.
+ w.draw(false)
+ }
+ return nil
+}
+
+func (d *wlDisplay) dispatch(p *poller) error {
+ dispfd := C.wl_display_get_fd(d.disp)
+ // Poll for events and notifications.
+ pollfds := append(p.pollfds[:0],
+ syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
+ syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
+ )
+ dispFd := &pollfds[0]
+ if ret, err := C.wl_display_flush(d.disp); ret < 0 {
+ if err != syscall.EAGAIN {
+ return fmt.Errorf("wayland: wl_display_flush failed: %v", err)
+ }
+ // EAGAIN means the output buffer was full. Poll for
+ // POLLOUT to know when we can write again.
+ dispFd.Events |= syscall.POLLOUT
+ }
+ if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
+ return fmt.Errorf("wayland: poll failed: %v", err)
+ }
+ // Clear notifications.
+ for {
+ _, err := syscall.Read(d.notify.read, p.buf[:])
+ if err == syscall.EAGAIN {
+ break
+ }
+ if err != nil {
+ return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
+ }
+ }
+ // Handle events
+ switch {
+ case dispFd.Revents&syscall.POLLIN != 0:
+ if ret, err := C.wl_display_dispatch(d.disp); ret < 0 {
+ return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err)
+ }
+ case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0:
+ return errors.New("wayland: display file descriptor gone")
+ }
+ d.repeat.Repeat(d)
+ return nil
+}
+
+func (w *window) Wakeup() {
+ select {
+ case w.wakeups <- struct{}{}:
+ default:
+ }
+ w.disp.wakeup()
+}
+
+func (w *window) SetAnimating(anim bool) {
+ w.animating = anim
+}
+
+// Wakeup wakes up the event loop through the notification pipe.
+func (d *wlDisplay) wakeup() {
+ oneByte := make([]byte, 1)
+ if _, err := syscall.Write(d.notify.write, oneByte); err != nil && err != syscall.EAGAIN {
+ panic(fmt.Errorf("failed to write to pipe: %v", err))
+ }
+}
+
+func (w *window) destroy() {
+ if w.cursor.surf != nil {
+ C.wl_surface_destroy(w.cursor.surf)
+ }
+ if w.cursor.theme != nil {
+ C.wl_cursor_theme_destroy(w.cursor.theme)
+ }
+ if w.topLvl != nil {
+ C.xdg_toplevel_destroy(w.topLvl)
+ }
+ if w.surf != nil {
+ C.wl_surface_destroy(w.surf)
+ }
+ if w.wmSurf != nil {
+ C.xdg_surface_destroy(w.wmSurf)
+ }
+ if w.decor != nil {
+ C.zxdg_toplevel_decoration_v1_destroy(w.decor)
+ }
+ callbackDelete(unsafe.Pointer(w.surf))
+}
+
+//export gio_onKeyboardModifiers
+func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, depressed, latched, locked, group C.uint32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+ d := s.disp
+ d.repeat.Stop(0)
+ if d.xkb == nil {
+ return
+ }
+ d.xkb.UpdateMask(uint32(depressed), uint32(latched), uint32(locked), uint32(group), uint32(group), uint32(group))
+}
+
+//export gio_onKeyboardRepeatInfo
+func gio_onKeyboardRepeatInfo(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, rate, delay C.int32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ d := s.disp
+ d.repeat.Stop(0)
+ d.repeat.rate = int(rate)
+ d.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) {
+ s := callbackLoad(data).(*wlSeat)
+ s.serial = serial
+}
+
+//export gio_onDataSourceTarget
+func gio_onDataSourceTarget(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char) {
+}
+
+//export gio_onDataSourceSend
+func gio_onDataSourceSend(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char, fd C.int32_t) {
+ s := callbackLoad(data).(*wlSeat)
+ content := s.content
+ go func() {
+ defer syscall.Close(int(fd))
+ syscall.Write(int(fd), content)
+ }()
+}
+
+//export gio_onDataSourceCancelled
+func gio_onDataSourceCancelled(data unsafe.Pointer, source *C.struct_wl_data_source) {
+ s := callbackLoad(data).(*wlSeat)
+ if s.source == source {
+ s.content = nil
+ s.source = nil
+ }
+ C.wl_data_source_destroy(source)
+}
+
+//export gio_onDataSourceDNDDropPerformed
+func gio_onDataSourceDNDDropPerformed(data unsafe.Pointer, source *C.struct_wl_data_source) {
+}
+
+//export gio_onDataSourceDNDFinished
+func gio_onDataSourceDNDFinished(data unsafe.Pointer, source *C.struct_wl_data_source) {
+}
+
+//export gio_onDataSourceAction
+func gio_onDataSourceAction(data unsafe.Pointer, source *C.struct_wl_data_source, act C.uint32_t) {
+}
+
+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 axes 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.Scroll,
+ Source: pointer.Mouse,
+ Buttons: w.pointerBtns,
+ Position: w.lastPos,
+ Scroll: total,
+ Time: w.scroll.time,
+ Modifiers: w.disp.xkb.Modifiers(),
+ })
+ 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,
+ Buttons: w.pointerBtns,
+ Source: pointer.Mouse,
+ Time: time.Duration(t) * time.Millisecond,
+ Modifiers: w.disp.xkb.Modifiers(),
+ })
+}
+
+func (w *window) updateOpaqueRegion() {
+ reg := C.wl_compositor_create_region(w.disp.compositor)
+ C.wl_region_add(reg, 0, 0, C.int32_t(w.size.X), C.int32_t(w.size.Y))
+ 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 w.disp.outputConfig {
+ for _, w2 := range conf.windows {
+ if w2 == w {
+ found = true
+ if conf.scale > scale {
+ scale = conf.scale
+ }
+ }
+ }
+ }
+ if found && scale != w.scale {
+ w.scale = scale
+ w.newScale = true
+ }
+ if !found {
+ w.setStage(system.StagePaused)
+ } else {
+ w.setStage(system.StageRunning)
+ w.draw(true)
+ }
+}
+
+func (w *window) getConfig() (image.Point, unit.Metric) {
+ size := w.size.Mul(w.scale)
+ return size, unit.Metric{
+ PxPerDp: w.ppdp * float32(w.scale),
+ PxPerSp: w.ppsp * float32(w.scale),
+ }
+}
+
+func (w *window) draw(sync bool) {
+ w.flushScroll()
+ anim := w.animating || w.fling.anim.Active()
+ dead := w.dead
+ if dead || (!anim && !sync) {
+ return
+ }
+ size, cfg := w.getConfig()
+ if size != w.config.Size {
+ w.config.Size = size
+ w.w.Event(ConfigEvent{Config: w.config})
+ }
+ if cfg == (unit.Metric{}) {
+ return
+ }
+ if anim && w.lastFrameCallback == nil {
+ w.lastFrameCallback = C.wl_surface_frame(w.surf)
+ // Use the surface as listener data for gio_onFrameDone.
+ C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
+ }
+ w.w.Event(frameEvent{
+ FrameEvent: system.FrameEvent{
+ Now: time.Now(),
+ Size: w.config.Size,
+ Metric: cfg,
+ },
+ Sync: sync,
+ })
+}
+
+func (w *window) setStage(s system.Stage) {
+ if s == w.stage {
+ return
+ }
+ w.stage = s
+ w.w.Event(system.StageEvent{Stage: s})
+}
+
+func (w *window) display() *C.struct_wl_display {
+ return w.disp.disp
+}
+
+func (w *window) surface() (*C.struct_wl_surface, int, int) {
+ if w.needAck {
+ C.xdg_surface_ack_configure(w.wmSurf, w.serial)
+ w.needAck = false
+ }
+ if w.newScale {
+ C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
+ w.newScale = false
+ }
+ sz, _ := w.getConfig()
+ return w.surf, sz.X, sz.Y
+}
+
+func (w *window) ShowTextInput(show bool) {}
+
+func (w *window) SetInputHint(_ key.InputHint) {}
+
+// Close the window.
+func (w *window) Close() {
+ w.dead = true
+}
+
+// Maximize the window. Not implemented for Wayland.
+func (w *window) Maximize() {}
+
+// Center the window. Not implemented for Wayland.
+func (w *window) Center() {}
+
+func (w *window) NewContext() (context, error) {
+ var firstErr error
+ if f := newWaylandVulkanContext; f != nil {
+ c, err := f(w)
+ if err == nil {
+ return c, nil
+ }
+ firstErr = err
+ }
+ if f := newWaylandEGLContext; f != nil {
+ c, err := f(w)
+ if err == nil {
+ return c, nil
+ }
+ firstErr = err
+ }
+ if firstErr != nil {
+ return nil, firstErr
+ }
+ return nil, errors.New("wayland: no available GPU backends")
+}
+
+// detectUIScale reports the system UI scale, or 1.0 if it fails.
+func detectUIScale() 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 newWLDisplay() (*wlDisplay, error) {
+ d := &wlDisplay{
+ outputMap: make(map[C.uint32_t]*C.struct_wl_output),
+ outputConfig: make(map[*C.struct_wl_output]*wlOutput),
+ }
+ pipe := make([]int, 2)
+ if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
+ return nil, fmt.Errorf("wayland: failed to create pipe: %v", err)
+ }
+ d.notify.read = pipe[0]
+ d.notify.write = pipe[1]
+ xkb, err := xkb.New()
+ if err != nil {
+ d.destroy()
+ return nil, fmt.Errorf("wayland: %v", err)
+ }
+ d.xkb = xkb
+ d.disp, err = C.wl_display_connect(nil)
+ if d.disp == nil {
+ d.destroy()
+ return nil, fmt.Errorf("wayland: wl_display_connect failed: %v", err)
+ }
+ callbackMap.Store(unsafe.Pointer(d.disp), d)
+ d.reg = C.wl_display_get_registry(d.disp)
+ if d.reg == nil {
+ d.destroy()
+ return nil, errors.New("wayland: wl_display_get_registry failed")
+ }
+ C.wl_registry_add_listener(d.reg, &C.gio_registry_listener, unsafe.Pointer(d.disp))
+ // Wait for the server to register all its globals to the
+ // registry listener (gio_onRegistryGlobal).
+ C.wl_display_roundtrip(d.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(d.disp)
+ return d, nil
+}
+
+func (d *wlDisplay) destroy() {
+ if d.notify.write != 0 {
+ syscall.Close(d.notify.write)
+ d.notify.write = 0
+ }
+ if d.notify.read != 0 {
+ syscall.Close(d.notify.read)
+ d.notify.read = 0
+ }
+ d.repeat.Stop(0)
+ if d.xkb != nil {
+ d.xkb.Destroy()
+ d.xkb = nil
+ }
+ if d.seat != nil {
+ d.seat.destroy()
+ d.seat = nil
+ }
+ if d.imm != nil {
+ C.zwp_text_input_manager_v3_destroy(d.imm)
+ }
+ if d.decor != nil {
+ C.zxdg_decoration_manager_v1_destroy(d.decor)
+ }
+ if d.shm != nil {
+ C.wl_shm_destroy(d.shm)
+ }
+ if d.compositor != nil {
+ C.wl_compositor_destroy(d.compositor)
+ }
+ if d.wm != nil {
+ C.xdg_wm_base_destroy(d.wm)
+ }
+ for _, output := range d.outputMap {
+ C.wl_output_destroy(output)
+ }
+ if d.reg != nil {
+ C.wl_registry_destroy(d.reg)
+ }
+ if d.disp != nil {
+ C.wl_display_disconnect(d.disp)
+ callbackDelete(unsafe.Pointer(d.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)
+}