// SPDX-License-Identifier: Unlicense OR MIT // +build linux,!android,!nowayland freebsd package wm import ( "bytes" "errors" "fmt" "image" "io" "io/ioutil" "math" "os" "os/exec" "strconv" "sync" "time" "unsafe" syscall "golang.org/x/sys/unix" "github.com/p9c/p9/pkg/gel/gio/app/internal/xkb" "github.com/p9c/p9/pkg/gel/gio/f32" "github.com/p9c/p9/pkg/gel/gio/internal/fling" "github.com/p9c/p9/pkg/gel/gio/io/clipboard" "github.com/p9c/p9/pkg/gel/gio/io/key" "github.com/p9c/p9/pkg/gel/gio/io/pointer" "github.com/p9c/p9/pkg/gel/gio/io/system" "github.com/p9c/p9/pkg/gel/gio/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;^;// +build linux,!android,!nowayland freebsd\\n\\n;" wayland_xdg_shell.c //go:generate sed -i "1s;^;// +build linux,!android,!nowayland freebsd\\n\\n;" wayland_xdg_decoration.c //go:generate sed -i "1s;^;// +build linux,!android,!nowayland freebsd\\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 #include #include #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 mu sync.Mutex animating bool opts *Options needAck bool // The most recent configure serial waiting to be ack'ed. serial C.uint32_t width int height int newScale bool scale int // readClipboard tracks whether a ClipboardEvent is requested. readClipboard bool // writeClipboard is set whenever a clipboard write is requested. writeClipboard *string } 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"} func init() { wlDriver = newWLWindow } func newWLWindow(window Callbacks, opts *Options) error { d, err := newWLDisplay() if err != nil { return err } w, err := d.createNativeWindow(opts) if err != nil { d.destroy() return err } w.w = window go func() { defer d.destroy() defer w.destroy() w.w.SetDriver(w) 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(opts *Options) (*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, } 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)) w.setOptions(opts) 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() C.wl_surface_commit(w.surf) 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.mu.Lock() w.serial = serial w.needAck = true w.mu.Unlock() 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.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 } 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.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.disp.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, 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() { w.mu.Lock() w.readClipboard = true w.mu.Unlock() w.disp.wakeup() } func (w *window) WriteClipboard(s string) { w.mu.Lock() w.writeClipboard = &s w.mu.Unlock() w.disp.wakeup() } func (w *window) Option(opts *Options) { w.mu.Lock() w.opts = opts w.mu.Unlock() w.disp.wakeup() } func (w *window) setOptions(opts *Options) { _, _, cfg := w.config() if o := opts.Size; o != nil { w.width = cfg.Px(o.Width) w.height = cfg.Px(o.Height) } if o := opts.Title; o != nil { title := C.CString(*o) C.xdg_toplevel_set_title(w.topLvl, title) C.free(unsafe.Pointer(title)) } } func (w *window) SetCursor(name pointer.CursorName) { if name == pointer.CursorNone { C.wl_pointer_set_cursor(w.disp.seat.pointer, 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(w.disp.seat.pointer, 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 } if w.dead { w.w.Event(system.DestroyEvent{}) break } w.process() } return nil } func (w *window) process() { w.mu.Lock() readClipboard := w.readClipboard writeClipboard := w.writeClipboard opts := w.opts w.readClipboard = false w.writeClipboard = nil w.opts = nil w.mu.Unlock() if 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)}) }() } if writeClipboard != nil { w.disp.writeClipboard([]byte(*writeClipboard)) } if opts != nil { w.setOptions(opts) } // pass false to skip unnecessary drawing. w.draw(false) } 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) SetAnimating(anim bool) { w.mu.Lock() w.animating = anim w.mu.Unlock() w.disp.wakeup() } // 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.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 w.disp.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(system.StagePaused) } else { w.setStage(system.StageRunning) w.draw(true) } } func (w *window) config() (int, int, unit.Metric) { width, height := w.width*w.scale, w.height*w.scale return width, height, unit.Metric{ PxPerDp: w.ppdp * float32(w.scale), PxPerSp: w.ppsp * float32(w.scale), } } func (w *window) draw(sync bool) { w.flushScroll() w.mu.Lock() anim := w.animating || w.fling.anim.Active() dead := w.dead w.mu.Unlock() if dead || (!anim && !sync) { return } width, height, cfg := 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: image.Point{ X: width, Y: height, }, 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 } 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) {} // Close the window. Not implemented for Wayland. func (w *window) Close() {} // 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) }