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