1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933 |
- // SPDX-License-Identifier: Unlicense OR MIT
- //go:build ((linux && !android) || freebsd) && !nowayland
- // +build linux,!android freebsd
- // +build !nowayland
- package app
- import (
- "bytes"
- "errors"
- "fmt"
- "image"
- "io"
- "math"
- "os"
- "os/exec"
- "runtime"
- "strconv"
- "sync"
- "time"
- "unsafe"
- syscall "golang.org/x/sys/unix"
- "gioui.org/app/internal/xkb"
- "gioui.org/f32"
- "gioui.org/internal/fling"
- "gioui.org/io/event"
- "gioui.org/io/key"
- "gioui.org/io/pointer"
- "gioui.org/io/system"
- "gioui.org/io/transfer"
- "gioui.org/op"
- "gioui.org/unit"
- )
- // Use wayland-scanner to generate glue code for the xdg-shell and xdg-decoration extensions.
- //go:generate wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.h
- //go:generate wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.c
- //go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.h
- //go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.c
- //go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.h
- //go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.c
- //go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_xdg_shell.c
- //go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_xdg_decoration.c
- //go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_text_input.c
- /*
- #cgo linux pkg-config: wayland-client wayland-cursor
- #cgo freebsd openbsd LDFLAGS: -lwayland-client -lwayland-cursor
- #cgo freebsd CFLAGS: -I/usr/local/include
- #cgo freebsd LDFLAGS: -L/usr/local/lib
- #include <stdlib.h>
- #include <wayland-client.h>
- #include <wayland-cursor.h>
- #include "wayland_text_input.h"
- #include "wayland_xdg_shell.h"
- #include "wayland_xdg_decoration.h"
- extern const struct wl_registry_listener gio_registry_listener;
- extern const struct wl_surface_listener gio_surface_listener;
- extern const struct xdg_surface_listener gio_xdg_surface_listener;
- extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener;
- extern const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_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
- poller poller
- readClipClose chan struct{}
- }
- 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 *window
- stopC chan struct{}
- start time.Duration
- last time.Duration
- mu sync.Mutex
- now time.Duration
- }
- type window struct {
- w *callbacks
- disp *wlDisplay
- seat *wlSeat
- 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
- // system is the active cursor for system gestures
- // such as border resizes and window moves. It
- // is nil if the pointer is not in a system gesture
- // area.
- system *C.struct_wl_cursor
- surf *C.struct_wl_surface
- cursors struct {
- pointer *C.struct_wl_cursor
- resizeNorth *C.struct_wl_cursor
- resizeSouth *C.struct_wl_cursor
- resizeWest *C.struct_wl_cursor
- resizeEast *C.struct_wl_cursor
- resizeNorthWest *C.struct_wl_cursor
- resizeNorthEast *C.struct_wl_cursor
- resizeSouthWest *C.struct_wl_cursor
- resizeSouthEast *C.struct_wl_cursor
- }
- }
- fling struct {
- yExtrapolation fling.Extrapolation
- xExtrapolation fling.Extrapolation
- anim fling.Animation
- start bool
- dir f32.Point
- }
- configured bool
- lastFrameCallback *C.struct_wl_callback
- animating bool
- // The most recent configure serial waiting to be ack'ed.
- serial C.uint32_t
- scale int
- // size is the unscaled window size (unlike config.Size which is scaled).
- size image.Point
- config Config
- wsize image.Point // window config size before going fullscreen or maximized
- inCompositor bool // window is moving or being resized
- clipReads chan transfer.DataEvent
- wakeups chan struct{}
- closing bool
- }
- 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 Wayland client API
- // forces the use of callbacks and storing pointers to Go values
- // in C is forbidden.
- var callbackMap sync.Map
- // clipboardMimeTypes is a list of supported clipboard mime types, in
- // order of preference.
- var clipboardMimeTypes = []string{"text/plain;charset=utf8", "UTF8_STRING", "text/plain", "TEXT", "STRING"}
- var (
- newWaylandEGLContext func(w *window) (context, error)
- newWaylandVulkanContext func(w *window) (context, error)
- )
- func init() {
- wlDriver = newWLWindow
- }
- func newWLWindow(callbacks *callbacks, options []Option) error {
- d, err := newWLDisplay()
- if err != nil {
- return err
- }
- w, err := d.createNativeWindow(options)
- if err != nil {
- d.destroy()
- return err
- }
- w.w = callbacks
- w.w.SetDriver(w)
- // Finish and commit setup from createNativeWindow.
- w.Configure(options)
- w.draw(true)
- C.wl_surface_commit(w.surf)
- w.ProcessEvent(WaylandViewEvent{
- Display: unsafe.Pointer(w.display()),
- Surface: unsafe.Pointer(w.surf),
- })
- return nil
- }
- func (d *wlDisplay) writeClipboard(content []byte) error {
- s := d.seat
- if s == nil {
- return nil
- }
- // Clear old offer.
- if s.source != nil {
- C.wl_data_source_destroy(s.source)
- s.source = nil
- s.content = nil
- }
- if d.dataDeviceManager == nil || s.dataDev == nil {
- return nil
- }
- s.content = content
- s.source = C.wl_data_device_manager_create_data_source(d.dataDeviceManager)
- C.wl_data_source_add_listener(s.source, &C.gio_data_source_listener, unsafe.Pointer(s.seat))
- for _, mime := range clipboardMimeTypes {
- C.wl_data_source_offer(s.source, C.CString(mime))
- }
- C.wl_data_device_set_selection(s.dataDev, s.source, s.serial)
- return nil
- }
- func (d *wlDisplay) readClipboard() (io.ReadCloser, error) {
- s := d.seat
- if s == nil {
- return nil, nil
- }
- if s.clipboard == nil {
- return nil, nil
- }
- r, w, err := os.Pipe()
- if err != nil {
- return nil, err
- }
- // wl_data_offer_receive performs and implicit dup(2) of the write end
- // of the pipe. Close our version.
- defer w.Close()
- cmimeType := C.CString(s.mimeType)
- defer C.free(unsafe.Pointer(cmimeType))
- C.wl_data_offer_receive(s.clipboard, cmimeType, C.int(w.Fd()))
- return r, nil
- }
- func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
- if d.compositor == nil {
- return nil, errors.New("wayland: no compositor available")
- }
- if d.wm == nil {
- return nil, errors.New("wayland: no xdg_wm_base available")
- }
- if d.shm == nil {
- return nil, errors.New("wayland: no wl_shm available")
- }
- if len(d.outputMap) == 0 {
- return nil, errors.New("wayland: no outputs available")
- }
- var scale int
- for _, conf := range d.outputConfig {
- if s := conf.scale; s > scale {
- scale = s
- }
- }
- ppdp := detectUIScale()
- w := &window{
- disp: d,
- scale: scale,
- ppdp: ppdp,
- ppsp: ppdp,
- wakeups: make(chan struct{}, 1),
- clipReads: make(chan transfer.DataEvent, 1),
- }
- w.surf = C.wl_compositor_create_surface(d.compositor)
- if w.surf == nil {
- w.destroy()
- return nil, errors.New("wayland: wl_compositor_create_surface failed")
- }
- C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
- 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")
- }
- id := C.CString(ID)
- defer C.free(unsafe.Pointer(id))
- C.xdg_toplevel_set_app_id(w.topLvl, id)
- cursorTheme := C.CString(os.Getenv("XCURSOR_THEME"))
- defer C.free(unsafe.Pointer(cursorTheme))
- cursorSize := 32
- if envSize, ok := os.LookupEnv("XCURSOR_SIZE"); ok && envSize != "" {
- size, err := strconv.Atoi(envSize)
- if err == nil {
- cursorSize = size
- }
- }
- w.cursor.theme = C.wl_cursor_theme_load(cursorTheme, C.int(cursorSize*w.scale), d.shm)
- if w.cursor.theme == nil {
- w.destroy()
- return nil, errors.New("wayland: wl_cursor_theme_load failed")
- }
- w.loadCursors()
- w.cursor.cursor = w.cursor.cursors.pointer
- 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.wl_surface_set_buffer_scale(w.cursor.surf, C.int32_t(w.scale))
- C.xdg_wm_base_add_listener(d.wm, &C.gio_xdg_wm_base_listener, unsafe.Pointer(w.surf))
- C.wl_surface_add_listener(w.surf, &C.gio_surface_listener, unsafe.Pointer(w.surf))
- C.xdg_surface_add_listener(w.wmSurf, &C.gio_xdg_surface_listener, unsafe.Pointer(w.surf))
- C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))
- if d.decor != nil {
- w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl)
- C.zxdg_toplevel_decoration_v1_add_listener(w.decor, &C.gio_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.surf))
- }
- w.updateOpaqueRegion()
- return w, nil
- }
- func (w *window) loadCursors() {
- w.cursor.cursors.pointer = w.loadCursor(pointer.CursorDefault)
- w.cursor.cursors.resizeNorth = w.loadCursor(pointer.CursorNorthResize)
- w.cursor.cursors.resizeSouth = w.loadCursor(pointer.CursorSouthResize)
- w.cursor.cursors.resizeWest = w.loadCursor(pointer.CursorWestResize)
- w.cursor.cursors.resizeEast = w.loadCursor(pointer.CursorEastResize)
- w.cursor.cursors.resizeSouthWest = w.loadCursor(pointer.CursorSouthWestResize)
- w.cursor.cursors.resizeSouthEast = w.loadCursor(pointer.CursorSouthEastResize)
- w.cursor.cursors.resizeNorthWest = w.loadCursor(pointer.CursorNorthWestResize)
- w.cursor.cursors.resizeNorthEast = w.loadCursor(pointer.CursorNorthEastResize)
- }
- func (w *window) loadCursor(name pointer.Cursor) *C.struct_wl_cursor {
- if name == pointer.CursorNone {
- return nil
- }
- xcursor := xCursor[name]
- cname := C.CString(xcursor)
- defer C.free(unsafe.Pointer(cname))
- c := C.wl_cursor_theme_get_cursor(w.cursor.theme, cname)
- if c == nil {
- // Fall back to default cursor.
- c = w.cursor.cursors.pointer
- }
- return c
- }
- func callbackDelete(k unsafe.Pointer) {
- callbackMap.Delete(k)
- }
- func callbackStore(k unsafe.Pointer, v interface{}) {
- callbackMap.Store(k, v)
- }
- func callbackLoad(k unsafe.Pointer) interface{} {
- v, exists := callbackMap.Load(k)
- if !exists {
- panic("missing callback entry")
- }
- return v
- }
- //export gio_onSeatCapabilities
- func gio_onSeatCapabilities(data unsafe.Pointer, seat *C.struct_wl_seat, caps C.uint32_t) {
- s := callbackLoad(data).(*wlSeat)
- s.updateCaps(caps)
- }
- // flushOffers remove all wl_data_offers that isn't the clipboard
- // content.
- func (s *wlSeat) flushOffers() {
- for o := range s.offers {
- if o == s.clipboard {
- continue
- }
- // We're only interested in clipboard offers.
- delete(s.offers, o)
- callbackDelete(unsafe.Pointer(o))
- C.wl_data_offer_destroy(o)
- }
- }
- func (s *wlSeat) destroy() {
- if s.source != nil {
- C.wl_data_source_destroy(s.source)
- s.source = nil
- }
- if s.im != nil {
- C.zwp_text_input_v3_destroy(s.im)
- s.im = nil
- }
- if s.pointer != nil {
- C.wl_pointer_release(s.pointer)
- }
- if s.touch != nil {
- C.wl_touch_release(s.touch)
- }
- if s.keyboard != nil {
- C.wl_keyboard_release(s.keyboard)
- }
- s.clipboard = nil
- s.flushOffers()
- if s.dataDev != nil {
- C.wl_data_device_release(s.dataDev)
- }
- if s.seat != nil {
- callbackDelete(unsafe.Pointer(s.seat))
- C.wl_seat_release(s.seat)
- }
- }
- func (s *wlSeat) updateCaps(caps C.uint32_t) {
- if s.im == nil && s.disp.imm != nil {
- s.im = C.zwp_text_input_manager_v3_get_text_input(s.disp.imm, s.seat)
- C.zwp_text_input_v3_add_listener(s.im, &C.gio_zwp_text_input_v3_listener, unsafe.Pointer(s.seat))
- }
- switch {
- case s.pointer == nil && caps&C.WL_SEAT_CAPABILITY_POINTER != 0:
- s.pointer = C.wl_seat_get_pointer(s.seat)
- C.wl_pointer_add_listener(s.pointer, &C.gio_pointer_listener, unsafe.Pointer(s.seat))
- case s.pointer != nil && caps&C.WL_SEAT_CAPABILITY_POINTER == 0:
- C.wl_pointer_release(s.pointer)
- s.pointer = nil
- }
- switch {
- case s.touch == nil && caps&C.WL_SEAT_CAPABILITY_TOUCH != 0:
- s.touch = C.wl_seat_get_touch(s.seat)
- C.wl_touch_add_listener(s.touch, &C.gio_touch_listener, unsafe.Pointer(s.seat))
- case s.touch != nil && caps&C.WL_SEAT_CAPABILITY_TOUCH == 0:
- C.wl_touch_release(s.touch)
- s.touch = nil
- }
- switch {
- case s.keyboard == nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD != 0:
- s.keyboard = C.wl_seat_get_keyboard(s.seat)
- C.wl_keyboard_add_listener(s.keyboard, &C.gio_keyboard_listener, unsafe.Pointer(s.seat))
- case s.keyboard != nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD == 0:
- C.wl_keyboard_release(s.keyboard)
- s.keyboard = nil
- }
- }
- //export gio_onSeatName
- func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
- }
- //export gio_onXdgSurfaceConfigure
- func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
- w := callbackLoad(data).(*window)
- w.serial = serial
- C.xdg_surface_ack_configure(wmSurf, serial)
- w.configured = true
- w.draw(true)
- }
- //export gio_onToplevelClose
- func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
- w := callbackLoad(data).(*window)
- w.closing = true
- }
- //export gio_onToplevelConfigure
- func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel, width, height C.int32_t, states *C.struct_wl_array) {
- w := callbackLoad(data).(*window)
- if width != 0 && height != 0 {
- w.size = image.Pt(int(width), int(height))
- w.updateOpaqueRegion()
- }
- }
- //export gio_onToplevelDecorationConfigure
- func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_toplevel_decoration_v1, mode C.uint32_t) {
- w := callbackLoad(data).(*window)
- decorated := w.config.Decorated
- switch mode {
- case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
- w.config.Decorated = false
- case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
- w.config.Decorated = true
- }
- if decorated != w.config.Decorated {
- w.setWindowConstraints()
- if w.config.Decorated {
- w.size.Y -= int(w.config.decoHeight)
- } else {
- w.size.Y += int(w.config.decoHeight)
- }
- w.ProcessEvent(ConfigEvent{Config: w.config})
- w.draw(true)
- }
- }
- //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.updateOutputs()
- }
- }
- //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()
- if w.config.Mode == Minimized {
- // Minimized window got brought back up: it is no longer so.
- w.config.Mode = Windowed
- w.ProcessEvent(ConfigEvent{Config: w.config})
- }
- }
- //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))
- d.bindDataDevice()
- 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))
- d.bindDataDevice()
- }
- }
- //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.ProcessEvent(pointer.Event{
- Kind: 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.ProcessEvent(pointer.Event{
- Kind: 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.ProcessEvent(pointer.Event{
- Kind: 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.ProcessEvent(pointer.Event{
- Kind: 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)
- w.seat = s
- 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, surf *C.struct_wl_surface) {
- w := callbackLoad(unsafe.Pointer(surf)).(*window)
- w.seat = nil
- s := callbackLoad(data).(*wlSeat)
- s.serial = serial
- if w.inCompositor {
- w.inCompositor = false
- w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
- }
- }
- //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
- }
- if state == 1 && btn == pointer.ButtonPrimary {
- if _, edge := w.systemGesture(); edge != 0 {
- w.resize(serial, edge)
- return
- }
- act, ok := w.w.ActionAt(w.lastPos)
- if ok && w.config.Mode == Windowed {
- switch act {
- case system.ActionMove:
- w.move(serial)
- return
- }
- }
- }
- var kind pointer.Kind
- switch state {
- case 0:
- w.pointerBtns &^= btn
- kind = pointer.Release
- // Move or resize gestures no longer applies.
- w.inCompositor = false
- case 1:
- w.pointerBtns |= btn
- kind = pointer.Press
- }
- w.flushScroll()
- w.resetFling()
- w.ProcessEvent(pointer.Event{
- Kind: kind,
- 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:
- // horizontal scroll if shift + mousewheel(up/down) pressed.
- if w.disp.xkb.Modifiers() == key.ModShift {
- w.scroll.dist.X += v
- } else {
- w.scroll.dist.Y += v
- }
- }
- }
- //export gio_onPointerFrame
- func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) {
- s := callbackLoad(data).(*wlSeat)
- w := s.pointerFocus
- w.flushScroll()
- w.flushFling()
- }
- func (w *window) flushFling() {
- if !w.fling.start {
- return
- }
- w.fling.start = false
- estx, esty := w.fling.xExtrapolation.Estimate(), w.fling.yExtrapolation.Estimate()
- w.fling.xExtrapolation = fling.Extrapolation{}
- w.fling.yExtrapolation = fling.Extrapolation{}
- vel := float32(math.Sqrt(float64(estx.Velocity*estx.Velocity + esty.Velocity*esty.Velocity)))
- _, c := w.getConfig()
- if !w.fling.anim.Start(c, time.Now(), vel) {
- return
- }
- invDist := 1 / vel
- w.fling.dir.X = estx.Velocity * invDist
- w.fling.dir.Y = esty.Velocity * invDist
- }
- //export gio_onPointerAxisSource
- func gio_onPointerAxisSource(data unsafe.Pointer, pointer *C.struct_wl_pointer, source C.uint32_t) {
- }
- //export gio_onPointerAxisStop
- func gio_onPointerAxisStop(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.uint32_t) {
- s := callbackLoad(data).(*wlSeat)
- w := s.pointerFocus
- w.fling.start = true
- }
- //export gio_onPointerAxisDiscrete
- func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) {
- s := callbackLoad(data).(*wlSeat)
- w := s.pointerFocus
- w.resetFling()
- switch axis {
- case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL:
- w.scroll.steps.X += int(discrete)
- case C.WL_POINTER_AXIS_VERTICAL_SCROLL:
- // horizontal scroll if shift + mousewheel(up/down) pressed.
- if w.disp.xkb.Modifiers() == key.ModShift {
- w.scroll.steps.X += int(discrete)
- } else {
- w.scroll.steps.Y += int(discrete)
- }
- }
- }
- func (w *window) ReadClipboard() {
- if w.disp.readClipClose != nil {
- return
- }
- w.disp.readClipClose = make(chan struct{})
- r, err := w.disp.readClipboard()
- if r == nil || err != nil {
- return
- }
- // Don't let slow clipboard transfers block event loop.
- go func() {
- defer r.Close()
- data, _ := io.ReadAll(r)
- e := transfer.DataEvent{
- Type: "application/text",
- Open: func() io.ReadCloser {
- return io.NopCloser(bytes.NewReader(data))
- },
- }
- select {
- case w.clipReads <- e:
- w.disp.wakeup()
- case <-w.disp.readClipClose:
- }
- }()
- }
- func (w *window) WriteClipboard(mime string, s []byte) {
- w.disp.writeClipboard(s)
- }
- func (w *window) Configure(options []Option) {
- _, cfg := w.getConfig()
- prev := w.config
- cnf := w.config
- cnf.apply(cfg, options)
- w.config.decoHeight = cnf.decoHeight
- switch cnf.Mode {
- case Fullscreen:
- switch prev.Mode {
- case Minimized, Fullscreen:
- default:
- w.config.Mode = Fullscreen
- w.wsize = w.config.Size
- C.xdg_toplevel_set_fullscreen(w.topLvl, nil)
- }
- case Minimized:
- switch prev.Mode {
- case Minimized, Fullscreen:
- default:
- w.config.Mode = Minimized
- C.xdg_toplevel_set_minimized(w.topLvl)
- }
- case Maximized:
- switch prev.Mode {
- case Minimized, Fullscreen:
- default:
- w.config.Mode = Maximized
- w.wsize = w.config.Size
- C.xdg_toplevel_set_maximized(w.topLvl)
- w.setTitle(prev, cnf)
- }
- case Windowed:
- switch prev.Mode {
- case Fullscreen:
- w.config.Mode = Windowed
- w.size = w.wsize.Div(w.scale)
- C.xdg_toplevel_unset_fullscreen(w.topLvl)
- case Minimized:
- w.config.Mode = Windowed
- case Maximized:
- w.config.Mode = Windowed
- w.size = w.wsize.Div(w.scale)
- C.xdg_toplevel_unset_maximized(w.topLvl)
- }
- w.setTitle(prev, cnf)
- if prev.Size != cnf.Size {
- w.config.Size = cnf.Size
- w.config.Size.Y += int(w.decoHeight()) * w.scale
- w.size = w.config.Size.Div(w.scale)
- }
- w.config.MinSize = cnf.MinSize
- w.config.MaxSize = cnf.MaxSize
- w.setWindowConstraints()
- }
- w.ProcessEvent(ConfigEvent{Config: w.config})
- }
- func (w *window) setWindowConstraints() {
- decoHeight := w.decoHeight()
- if scaled := w.config.MinSize.Div(w.scale); scaled != (image.Point{}) {
- C.xdg_toplevel_set_min_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight))
- }
- if scaled := w.config.MaxSize.Div(w.scale); scaled != (image.Point{}) {
- C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight))
- }
- }
- // decoHeight returns the adjustment for client-side decorations, if applicable.
- // The unit is in surface-local coordinates.
- func (w *window) decoHeight() int {
- if !w.config.Decorated {
- return int(w.config.decoHeight)
- }
- return 0
- }
- func (w *window) setTitle(prev, cnf Config) {
- if prev.Title != cnf.Title {
- w.config.Title = cnf.Title
- title := C.CString(cnf.Title)
- C.xdg_toplevel_set_title(w.topLvl, title)
- C.free(unsafe.Pointer(title))
- }
- }
- func (w *window) Perform(actions system.Action) {
- // NB. there is no way for a minimized window to be unminimized.
- // https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized
- walkActions(actions, func(action system.Action) {
- switch action {
- case system.ActionClose:
- w.closing = true
- }
- })
- }
- func (w *window) move(serial C.uint32_t) {
- s := w.seat
- if !w.inCompositor && s != nil {
- w.inCompositor = true
- C.xdg_toplevel_move(w.topLvl, s.seat, serial)
- }
- }
- func (w *window) resize(serial, edge C.uint32_t) {
- s := w.seat
- if w.inCompositor || s == nil {
- return
- }
- w.inCompositor = true
- C.xdg_toplevel_resize(w.topLvl, s.seat, serial, edge)
- }
- func (w *window) SetCursor(cursor pointer.Cursor) {
- w.cursor.cursor = w.loadCursor(cursor)
- w.updateCursor()
- }
- func (w *window) updateCursor() {
- ptr := w.disp.seat.pointer
- if ptr == nil {
- return
- }
- w.setCursor(ptr, w.serial)
- }
- func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
- c := w.cursor.system
- if c == nil {
- c = w.cursor.cursor
- }
- if c == nil {
- C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
- return
- }
- // Get images[0].
- img := *c.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.uint(w.scale)), C.int32_t(img.hotspot_y/C.uint(w.scale)))
- 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.config.Focused = true
- w.ProcessEvent(ConfigEvent{Config: w.config})
- }
- //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.config.Focused = false
- w.ProcessEvent(ConfigEvent{Config: w.config})
- }
- //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) {
- if ee, ok := e.(key.EditEvent); ok {
- // There's no support for IME yet.
- w.w.EditorInsert(ee.Text)
- } else {
- w.ProcessEvent(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
- 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) {
- if ee, ok := e.(key.EditEvent); ok {
- // There's no support for IME yet.
- r.win.w.EditorInsert(ee.Text)
- } else {
- r.win.ProcessEvent(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) close(err error) {
- w.ProcessEvent(WaylandViewEvent{})
- w.ProcessEvent(DestroyEvent{Err: err})
- w.destroy()
- w.disp.destroy()
- w.disp = nil
- }
- func (w *window) dispatch() {
- if w.disp == nil {
- <-w.wakeups
- w.w.Invalidate()
- return
- }
- if err := w.disp.dispatch(); err != nil || w.closing {
- w.close(err)
- return
- }
- select {
- case e := <-w.clipReads:
- w.disp.readClipClose = nil
- w.ProcessEvent(e)
- case <-w.wakeups:
- w.w.Invalidate()
- default:
- }
- }
- func (w *window) ProcessEvent(e event.Event) {
- w.w.ProcessEvent(e)
- }
- func (w *window) Event() event.Event {
- for {
- evt, ok := w.w.nextEvent()
- if !ok {
- w.dispatch()
- continue
- }
- return evt
- }
- }
- func (w *window) Invalidate() {
- select {
- case w.wakeups <- struct{}{}:
- default:
- return
- }
- w.disp.wakeup()
- }
- func (w *window) Run(f func()) {
- f()
- }
- func (w *window) Frame(frame *op.Ops) {
- w.w.ProcessFrame(frame, nil)
- }
- // bindDataDevice initializes the dataDev field if and only if both
- // the seat and dataDeviceManager fields are initialized.
- func (d *wlDisplay) bindDataDevice() {
- if d.seat != nil && d.dataDeviceManager != nil {
- d.seat.dataDev = C.wl_data_device_manager_get_data_device(d.dataDeviceManager, d.seat.seat)
- if d.seat.dataDev == nil {
- return
- }
- 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))
- }
- }
- func (d *wlDisplay) dispatch() error {
- // wl_display_prepare_read records the current thread for
- // use in wl_display_read_events or wl_display_cancel_events.
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- dispfd := C.wl_display_get_fd(d.disp)
- // Poll for events and notifications.
- pollfds := append(d.poller.pollfds[:0],
- syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
- syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
- )
- for C.wl_display_prepare_read(d.disp) != 0 {
- C.wl_display_dispatch_pending(d.disp)
- }
- 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 {
- C.wl_display_cancel_read(d.disp)
- return fmt.Errorf("wayland: poll failed: %v", err)
- }
- if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
- C.wl_display_cancel_read(d.disp)
- return errors.New("wayland: display file descriptor gone")
- }
- // Handle events.
- if dispFd.Revents&syscall.POLLIN != 0 {
- if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
- return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
- }
- C.wl_display_dispatch_pending(d.disp)
- } else {
- C.wl_display_cancel_read(d.disp)
- }
- // Clear notifications.
- for {
- _, err := syscall.Read(d.notify.read, d.poller.buf[:])
- if err == syscall.EAGAIN {
- break
- }
- if err != nil {
- return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
- }
- }
- d.repeat.Repeat(d)
- return nil
- }
- func (w *window) SetAnimating(anim bool) {
- w.animating = anim
- if anim {
- w.draw(false)
- }
- }
- // 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.lastFrameCallback != nil {
- C.wl_callback_destroy(w.lastFrameCallback)
- w.lastFrameCallback = nil
- }
- 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
- }
- 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)
- }
- // Zero scroll distance prior to calling ProcessEvent, otherwise we may recursively
- // re-process the scroll distance.
- w.scroll.dist = f32.Point{}
- w.scroll.steps = image.Point{}
- w.ProcessEvent(pointer.Event{
- Kind: pointer.Scroll,
- Source: pointer.Mouse,
- Buttons: w.pointerBtns,
- Position: w.lastPos,
- Scroll: total,
- Time: w.scroll.time,
- Modifiers: w.disp.xkb.Modifiers(),
- })
- }
- 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.ProcessEvent(pointer.Event{
- Kind: pointer.Move,
- Position: w.lastPos,
- Buttons: w.pointerBtns,
- Source: pointer.Mouse,
- Time: time.Duration(t) * time.Millisecond,
- Modifiers: w.disp.xkb.Modifiers(),
- })
- c, _ := w.systemGesture()
- if c != w.cursor.system {
- w.cursor.system = c
- w.updateCursor()
- }
- }
- // updateCursor updates the system gesture cursor according to the pointer
- // position.
- func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) {
- if w.config.Mode != Windowed || w.config.Decorated {
- return nil, 0
- }
- _, cfg := w.getConfig()
- border := cfg.Dp(3)
- x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
- north := y <= border
- south := y >= size.Y-border
- west := x <= border
- east := x >= size.X-border
- switch {
- default:
- fallthrough
- case !north && !south && !west && !east:
- return nil, 0
- case north && west:
- return w.cursor.cursors.resizeNorthWest, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT
- case north && east:
- return w.cursor.cursors.resizeNorthEast, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT
- case south && west:
- return w.cursor.cursors.resizeSouthWest, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT
- case south && east:
- return w.cursor.cursors.resizeSouthEast, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT
- case north:
- return w.cursor.cursors.resizeNorth, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP
- case south:
- return w.cursor.cursors.resizeSouth, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM
- case west:
- return w.cursor.cursors.resizeWest, C.XDG_TOPLEVEL_RESIZE_EDGE_LEFT
- case east:
- return w.cursor.cursors.resizeEast, C.XDG_TOPLEVEL_RESIZE_EDGE_RIGHT
- }
- }
- func (w *window) updateOpaqueRegion() {
- reg := C.wl_compositor_create_region(w.disp.compositor)
- C.wl_region_add(reg, 0, 0, C.int32_t(w.size.X), C.int32_t(w.size.Y))
- C.wl_surface_set_opaque_region(w.surf, reg)
- C.wl_region_destroy(reg)
- }
- func (w *window) updateOutputs() {
- scale := 1
- var found bool
- for _, conf := range w.disp.outputConfig {
- for _, w2 := range conf.windows {
- if w2 == w {
- found = true
- if conf.scale > scale {
- scale = conf.scale
- }
- }
- }
- }
- if found && scale != w.scale {
- w.scale = scale
- C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
- w.draw(true)
- }
- if found {
- w.draw(true)
- }
- }
- func (w *window) getConfig() (image.Point, unit.Metric) {
- size := w.size.Mul(w.scale)
- return size, unit.Metric{
- PxPerDp: w.ppdp * float32(w.scale),
- PxPerSp: w.ppsp * float32(w.scale),
- }
- }
- func (w *window) draw(sync bool) {
- if !w.configured {
- return
- }
- w.flushScroll()
- size, cfg := w.getConfig()
- if cfg == (unit.Metric{}) {
- return
- }
- if size != w.config.Size {
- w.config.Size = size
- w.ProcessEvent(ConfigEvent{Config: w.config})
- }
- anim := w.animating || w.fling.anim.Active()
- // Draw animation only when not waiting for frame callback.
- redrawAnim := anim && w.lastFrameCallback == nil
- if !redrawAnim && !sync {
- return
- }
- if anim {
- 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.ProcessEvent(frameEvent{
- FrameEvent: FrameEvent{
- Now: time.Now(),
- Size: w.config.Size,
- Metric: cfg,
- },
- Sync: sync,
- })
- }
- func (w *window) display() *C.struct_wl_display {
- return w.disp.disp
- }
- func (w *window) surface() (*C.struct_wl_surface, int, int) {
- sz, _ := w.getConfig()
- return w.surf, sz.X, sz.Y
- }
- func (w *window) ShowTextInput(show bool) {}
- func (w *window) SetInputHint(_ key.InputHint) {}
- func (w *window) EditorStateChanged(old, new editorState) {}
- func (w *window) NewContext() (context, error) {
- var firstErr error
- if f := newWaylandEGLContext; f != nil {
- c, err := f(w)
- if err == nil {
- return c, nil
- }
- firstErr = err
- }
- if f := newWaylandVulkanContext; f != nil {
- c, err := f(w)
- if err == nil {
- return c, nil
- }
- firstErr = err
- }
- if firstErr != nil {
- return nil, firstErr
- }
- return nil, errors.New("wayland: no available GPU backends")
- }
- // detectUIScale reports the system UI scale, or 1.0 if it fails.
- func detectUIScale() float32 {
- // TODO: What about other window environments?
- out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output()
- if err != nil {
- return 1.0
- }
- scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32)
- if err != nil {
- return 1.0
- }
- return float32(scale)
- }
- func newWLDisplay() (*wlDisplay, error) {
- d := &wlDisplay{
- outputMap: make(map[C.uint32_t]*C.struct_wl_output),
- outputConfig: make(map[*C.struct_wl_output]*wlOutput),
- }
- pipe := make([]int, 2)
- if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
- return nil, fmt.Errorf("wayland: failed to create pipe: %v", err)
- }
- d.notify.read = pipe[0]
- d.notify.write = pipe[1]
- xkb, err := xkb.New()
- if err != nil {
- d.destroy()
- return nil, fmt.Errorf("wayland: %v", err)
- }
- d.xkb = xkb
- d.disp, err = C.wl_display_connect(nil)
- if d.disp == nil {
- d.destroy()
- return nil, fmt.Errorf("wayland: wl_display_connect failed: %v", err)
- }
- callbackMap.Store(unsafe.Pointer(d.disp), d)
- d.reg = C.wl_display_get_registry(d.disp)
- if d.reg == nil {
- d.destroy()
- return nil, errors.New("wayland: wl_display_get_registry failed")
- }
- C.wl_registry_add_listener(d.reg, &C.gio_registry_listener, unsafe.Pointer(d.disp))
- // Wait for the server to register all its globals to the
- // registry listener (gio_onRegistryGlobal).
- C.wl_display_roundtrip(d.disp)
- // Configuration listeners are added to outputs by gio_onRegistryGlobal.
- // We need another roundtrip to get the initial output configurations
- // through the gio_onOutput* callbacks.
- C.wl_display_roundtrip(d.disp)
- return d, nil
- }
- func (d *wlDisplay) destroy() {
- if d.readClipClose != nil {
- close(d.readClipClose)
- d.readClipClose = nil
- }
- 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))
- d.disp = nil
- }
- }
- // 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)
- }
|