mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-02 16:06:19 +00:00
5eeaadccea
As discussed in gio#53, Linux font scales conflate two settings: the fractional UI scale and the user preference as a result of conditions such as impaired vision. The former setting should apply to both dps and sps, while the latter only to sps. However, with the assumption that more users presumable change the font scale for the former reason rather than the latter, we should apply the font scale to both dps and sps in Gio. Signed-off-by: Elias Naur <mail@eliasnaur.com>
1199 lines
31 KiB
Go
1199 lines
31 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
// +build linux,!android,!nowayland freebsd
|
|
|
|
package window
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"math"
|
|
"os/exec"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"gioui.org/app/internal/xkb"
|
|
"gioui.org/f32"
|
|
"gioui.org/internal/fling"
|
|
"gioui.org/io/key"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/io/system"
|
|
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,!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 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.Context
|
|
|
|
repeat repeatState
|
|
}
|
|
|
|
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 *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 system.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 (
|
|
winMap = make(map[interface{}]*window)
|
|
outputMap = make(map[C.uint32_t]*C.struct_wl_output)
|
|
outputConfig = make(map[*C.struct_wl_output]*wlOutput)
|
|
)
|
|
|
|
func init() {
|
|
wlDriver = newWLWindow
|
|
}
|
|
|
|
func newWLWindow(window Callbacks, opts *Options) 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 *Options) (*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 * fontScale
|
|
if ppdp < minDensity {
|
|
ppdp = minDensity
|
|
}
|
|
|
|
w := &window{
|
|
disp: conn.disp,
|
|
scale: scale,
|
|
newScale: scale != 1,
|
|
ppdp: ppdp,
|
|
ppsp: ppdp,
|
|
}
|
|
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(system.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 := xkb.New(int(format), int(fd), int(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
|
|
}
|
|
kc := uint32(keyCode)
|
|
for _, e := range conn.xkb.DispatchKey(kc) {
|
|
w.w.Event(e)
|
|
}
|
|
if conn.xkb.IsRepeatKey(kc) {
|
|
conn.repeat.Start(w, kc, t)
|
|
}
|
|
}
|
|
|
|
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.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
|
|
}
|
|
for _, e := range conn.xkb.DispatchKey(r.key) {
|
|
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)
|
|
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},
|
|
}
|
|
dispFd := &pollfds[0]
|
|
// Plenty of room for a backlog of notifications.
|
|
var buf = make([]byte, 100)
|
|
loop:
|
|
for {
|
|
C.wl_display_dispatch_pending(conn.disp)
|
|
dispFd.Events &^= syscall.POLLOUT
|
|
if _, err := C.wl_display_flush(conn.disp); err != nil {
|
|
if err != syscall.EAGAIN {
|
|
break
|
|
}
|
|
// EAGAIN means the output buffer was full. Poll for
|
|
// POLLOUT to know when we can write again.
|
|
dispFd.Events |= syscall.POLLOUT
|
|
}
|
|
if w.dead {
|
|
w.w.Event(system.DestroyEvent{Err: w.pendingErr})
|
|
break
|
|
}
|
|
// Clear poll events.
|
|
dispFd.Revents = 0
|
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
|
panic(fmt.Errorf("poll 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 dispFd.Revents&syscall.POLLIN != 0:
|
|
if ret := C.wl_display_dispatch(conn.disp); ret < 0 {
|
|
break loop
|
|
}
|
|
case dispFd.Revents&(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(uint32(depressed), uint32(latched), uint32(locked), uint32(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(system.StagePaused)
|
|
} else {
|
|
w.setStage(system.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(FrameEvent{
|
|
FrameEvent: system.FrameEvent{
|
|
Size: image.Point{
|
|
X: width,
|
|
Y: height,
|
|
},
|
|
Config: &cfg,
|
|
},
|
|
Sync: sync,
|
|
})
|
|
}
|
|
|
|
func (w *window) setStage(s system.Stage) {
|
|
if s == w.stage {
|
|
return
|
|
}
|
|
w.stage = s
|
|
w.w.Event(system.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 the system font scale, or monitorScale
|
|
// 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 monitorScale
|
|
}
|
|
scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32)
|
|
if err != nil {
|
|
return monitorScale
|
|
}
|
|
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)
|
|
}
|