github.com/Seikaijyu/gio@v0.0.1/app/os_wayland.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 //go:build ((linux && !android) || freebsd) && !nowayland 4 // +build linux,!android freebsd 5 // +build !nowayland 6 7 package app 8 9 import ( 10 "bytes" 11 "errors" 12 "fmt" 13 "image" 14 "io" 15 "math" 16 "os" 17 "os/exec" 18 "strconv" 19 "sync" 20 "time" 21 "unsafe" 22 23 syscall "golang.org/x/sys/unix" 24 25 "github.com/Seikaijyu/gio/app/internal/xkb" 26 "github.com/Seikaijyu/gio/f32" 27 "github.com/Seikaijyu/gio/internal/fling" 28 "github.com/Seikaijyu/gio/io/clipboard" 29 "github.com/Seikaijyu/gio/io/key" 30 "github.com/Seikaijyu/gio/io/pointer" 31 "github.com/Seikaijyu/gio/io/system" 32 "github.com/Seikaijyu/gio/unit" 33 ) 34 35 // Use wayland-scanner to generate glue code for the xdg-shell and xdg-decoration extensions. 36 //go:generate wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.h 37 //go:generate wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.c 38 39 //go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.h 40 //go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.c 41 42 //go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.h 43 //go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.c 44 45 //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 46 //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 47 //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 48 49 /* 50 #cgo linux pkg-config: wayland-client wayland-cursor 51 #cgo freebsd openbsd LDFLAGS: -lwayland-client -lwayland-cursor 52 #cgo freebsd CFLAGS: -I/usr/local/include 53 #cgo freebsd LDFLAGS: -L/usr/local/lib 54 55 #include <stdlib.h> 56 #include <wayland-client.h> 57 #include <wayland-cursor.h> 58 #include "wayland_text_input.h" 59 #include "wayland_xdg_shell.h" 60 #include "wayland_xdg_decoration.h" 61 62 extern const struct wl_registry_listener gio_registry_listener; 63 extern const struct wl_surface_listener gio_surface_listener; 64 extern const struct xdg_surface_listener gio_xdg_surface_listener; 65 extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener; 66 extern const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener; 67 extern const struct xdg_wm_base_listener gio_xdg_wm_base_listener; 68 extern const struct wl_callback_listener gio_callback_listener; 69 extern const struct wl_output_listener gio_output_listener; 70 extern const struct wl_seat_listener gio_seat_listener; 71 extern const struct wl_pointer_listener gio_pointer_listener; 72 extern const struct wl_touch_listener gio_touch_listener; 73 extern const struct wl_keyboard_listener gio_keyboard_listener; 74 extern const struct zwp_text_input_v3_listener gio_zwp_text_input_v3_listener; 75 extern const struct wl_data_device_listener gio_data_device_listener; 76 extern const struct wl_data_offer_listener gio_data_offer_listener; 77 extern const struct wl_data_source_listener gio_data_source_listener; 78 */ 79 import "C" 80 81 type wlDisplay struct { 82 disp *C.struct_wl_display 83 reg *C.struct_wl_registry 84 compositor *C.struct_wl_compositor 85 wm *C.struct_xdg_wm_base 86 imm *C.struct_zwp_text_input_manager_v3 87 shm *C.struct_wl_shm 88 dataDeviceManager *C.struct_wl_data_device_manager 89 decor *C.struct_zxdg_decoration_manager_v1 90 seat *wlSeat 91 xkb *xkb.Context 92 outputMap map[C.uint32_t]*C.struct_wl_output 93 outputConfig map[*C.struct_wl_output]*wlOutput 94 95 // Notification pipe fds. 96 notify struct { 97 read, write int 98 } 99 100 repeat repeatState 101 } 102 103 type wlSeat struct { 104 disp *wlDisplay 105 seat *C.struct_wl_seat 106 name C.uint32_t 107 pointer *C.struct_wl_pointer 108 touch *C.struct_wl_touch 109 keyboard *C.struct_wl_keyboard 110 im *C.struct_zwp_text_input_v3 111 112 // The most recent input serial. 113 serial C.uint32_t 114 115 pointerFocus *window 116 keyboardFocus *window 117 touchFoci map[C.int32_t]*window 118 119 // Clipboard support. 120 dataDev *C.struct_wl_data_device 121 // offers is a map from active wl_data_offers to 122 // the list of mime types they support. 123 offers map[*C.struct_wl_data_offer][]string 124 // clipboard is the wl_data_offer for the clipboard. 125 clipboard *C.struct_wl_data_offer 126 // mimeType is the chosen mime type of clipboard. 127 mimeType string 128 // source represents the clipboard content of the most recent 129 // clipboard write, if any. 130 source *C.struct_wl_data_source 131 // content is the data belonging to source. 132 content []byte 133 } 134 135 type repeatState struct { 136 rate int 137 delay time.Duration 138 139 key uint32 140 win *callbacks 141 stopC chan struct{} 142 143 start time.Duration 144 last time.Duration 145 mu sync.Mutex 146 now time.Duration 147 } 148 149 type window struct { 150 w *callbacks 151 disp *wlDisplay 152 seat *wlSeat 153 surf *C.struct_wl_surface 154 wmSurf *C.struct_xdg_surface 155 topLvl *C.struct_xdg_toplevel 156 decor *C.struct_zxdg_toplevel_decoration_v1 157 ppdp, ppsp float32 158 scroll struct { 159 time time.Duration 160 steps image.Point 161 dist f32.Point 162 } 163 pointerBtns pointer.Buttons 164 lastPos f32.Point 165 lastTouch f32.Point 166 167 cursor struct { 168 theme *C.struct_wl_cursor_theme 169 cursor *C.struct_wl_cursor 170 // system is the active cursor for system gestures 171 // such as border resizes and window moves. It 172 // is nil if the pointer is not in a system gesture 173 // area. 174 system *C.struct_wl_cursor 175 surf *C.struct_wl_surface 176 cursors struct { 177 pointer *C.struct_wl_cursor 178 resizeNorth *C.struct_wl_cursor 179 resizeSouth *C.struct_wl_cursor 180 resizeWest *C.struct_wl_cursor 181 resizeEast *C.struct_wl_cursor 182 resizeNorthWest *C.struct_wl_cursor 183 resizeNorthEast *C.struct_wl_cursor 184 resizeSouthWest *C.struct_wl_cursor 185 resizeSouthEast *C.struct_wl_cursor 186 } 187 } 188 189 fling struct { 190 yExtrapolation fling.Extrapolation 191 xExtrapolation fling.Extrapolation 192 anim fling.Animation 193 start bool 194 dir f32.Point 195 } 196 197 stage system.Stage 198 dead bool 199 lastFrameCallback *C.struct_wl_callback 200 201 animating bool 202 redraw bool 203 // The most recent configure serial waiting to be ack'ed. 204 serial C.uint32_t 205 scale int 206 // size is the unscaled window size (unlike config.Size which is scaled). 207 size image.Point 208 config Config 209 wsize image.Point // window config size before going fullscreen or maximized 210 inCompositor bool // window is moving or being resized 211 212 clipReads chan clipboard.Event 213 214 wakeups chan struct{} 215 } 216 217 type poller struct { 218 pollfds [2]syscall.PollFd 219 // buf is scratch space for draining the notification pipe. 220 buf [100]byte 221 } 222 223 type wlOutput struct { 224 width int 225 height int 226 physWidth int 227 physHeight int 228 transform C.int32_t 229 scale int 230 windows []*window 231 } 232 233 // callbackMap maps Wayland native handles to corresponding Go 234 // references. It is necessary because the Wayland client API 235 // forces the use of callbacks and storing pointers to Go values 236 // in C is forbidden. 237 var callbackMap sync.Map 238 239 // clipboardMimeTypes is a list of supported clipboard mime types, in 240 // order of preference. 241 var clipboardMimeTypes = []string{"text/plain;charset=utf8", "UTF8_STRING", "text/plain", "TEXT", "STRING"} 242 243 var ( 244 newWaylandEGLContext func(w *window) (context, error) 245 newWaylandVulkanContext func(w *window) (context, error) 246 ) 247 248 func init() { 249 wlDriver = newWLWindow 250 } 251 252 func newWLWindow(callbacks *callbacks, options []Option) error { 253 d, err := newWLDisplay() 254 if err != nil { 255 return err 256 } 257 w, err := d.createNativeWindow(options) 258 if err != nil { 259 d.destroy() 260 return err 261 } 262 w.w = callbacks 263 go func() { 264 defer d.destroy() 265 defer w.destroy() 266 267 w.w.SetDriver(w) 268 269 // Finish and commit setup from createNativeWindow. 270 w.Configure(options) 271 C.wl_surface_commit(w.surf) 272 273 w.w.Event(WaylandViewEvent{ 274 Display: unsafe.Pointer(w.display()), 275 Surface: unsafe.Pointer(w.surf), 276 }) 277 278 err := w.loop() 279 w.w.Event(WaylandViewEvent{}) 280 w.w.Event(system.DestroyEvent{Err: err}) 281 }() 282 return nil 283 } 284 285 func (d *wlDisplay) writeClipboard(content []byte) error { 286 s := d.seat 287 if s == nil { 288 return nil 289 } 290 // Clear old offer. 291 if s.source != nil { 292 C.wl_data_source_destroy(s.source) 293 s.source = nil 294 s.content = nil 295 } 296 if d.dataDeviceManager == nil || s.dataDev == nil { 297 return nil 298 } 299 s.content = content 300 s.source = C.wl_data_device_manager_create_data_source(d.dataDeviceManager) 301 C.wl_data_source_add_listener(s.source, &C.gio_data_source_listener, unsafe.Pointer(s.seat)) 302 for _, mime := range clipboardMimeTypes { 303 C.wl_data_source_offer(s.source, C.CString(mime)) 304 } 305 C.wl_data_device_set_selection(s.dataDev, s.source, s.serial) 306 return nil 307 } 308 309 func (d *wlDisplay) readClipboard() (io.ReadCloser, error) { 310 s := d.seat 311 if s == nil { 312 return nil, nil 313 } 314 if s.clipboard == nil { 315 return nil, nil 316 } 317 r, w, err := os.Pipe() 318 if err != nil { 319 return nil, err 320 } 321 // wl_data_offer_receive performs and implicit dup(2) of the write end 322 // of the pipe. Close our version. 323 defer w.Close() 324 cmimeType := C.CString(s.mimeType) 325 defer C.free(unsafe.Pointer(cmimeType)) 326 C.wl_data_offer_receive(s.clipboard, cmimeType, C.int(w.Fd())) 327 return r, nil 328 } 329 330 func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) { 331 if d.compositor == nil { 332 return nil, errors.New("wayland: no compositor available") 333 } 334 if d.wm == nil { 335 return nil, errors.New("wayland: no xdg_wm_base available") 336 } 337 if d.shm == nil { 338 return nil, errors.New("wayland: no wl_shm available") 339 } 340 if len(d.outputMap) == 0 { 341 return nil, errors.New("wayland: no outputs available") 342 } 343 var scale int 344 for _, conf := range d.outputConfig { 345 if s := conf.scale; s > scale { 346 scale = s 347 } 348 } 349 ppdp := detectUIScale() 350 351 w := &window{ 352 disp: d, 353 scale: scale, 354 ppdp: ppdp, 355 ppsp: ppdp, 356 wakeups: make(chan struct{}, 1), 357 clipReads: make(chan clipboard.Event, 1), 358 } 359 w.surf = C.wl_compositor_create_surface(d.compositor) 360 if w.surf == nil { 361 w.destroy() 362 return nil, errors.New("wayland: wl_compositor_create_surface failed") 363 } 364 C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale)) 365 callbackStore(unsafe.Pointer(w.surf), w) 366 w.wmSurf = C.xdg_wm_base_get_xdg_surface(d.wm, w.surf) 367 if w.wmSurf == nil { 368 w.destroy() 369 return nil, errors.New("wayland: xdg_wm_base_get_xdg_surface failed") 370 } 371 w.topLvl = C.xdg_surface_get_toplevel(w.wmSurf) 372 if w.topLvl == nil { 373 w.destroy() 374 return nil, errors.New("wayland: xdg_surface_get_toplevel failed") 375 } 376 377 id := C.CString(ID) 378 defer C.free(unsafe.Pointer(id)) 379 C.xdg_toplevel_set_app_id(w.topLvl, id) 380 381 cursorTheme := C.CString(os.Getenv("XCURSOR_THEME")) 382 defer C.free(unsafe.Pointer(cursorTheme)) 383 cursorSize := 32 384 if envSize, ok := os.LookupEnv("XCURSOR_SIZE"); ok && envSize != "" { 385 size, err := strconv.Atoi(envSize) 386 if err == nil { 387 cursorSize = size 388 } 389 } 390 391 w.cursor.theme = C.wl_cursor_theme_load(cursorTheme, C.int(cursorSize*w.scale), d.shm) 392 if w.cursor.theme == nil { 393 w.destroy() 394 return nil, errors.New("wayland: wl_cursor_theme_load failed") 395 } 396 w.loadCursors() 397 w.cursor.cursor = w.cursor.cursors.pointer 398 if w.cursor.cursor == nil { 399 w.destroy() 400 return nil, errors.New("wayland: wl_cursor_theme_get_cursor failed") 401 } 402 w.cursor.surf = C.wl_compositor_create_surface(d.compositor) 403 if w.cursor.surf == nil { 404 w.destroy() 405 return nil, errors.New("wayland: wl_compositor_create_surface failed") 406 } 407 C.wl_surface_set_buffer_scale(w.cursor.surf, C.int32_t(w.scale)) 408 C.xdg_wm_base_add_listener(d.wm, &C.gio_xdg_wm_base_listener, unsafe.Pointer(w.surf)) 409 C.wl_surface_add_listener(w.surf, &C.gio_surface_listener, unsafe.Pointer(w.surf)) 410 C.xdg_surface_add_listener(w.wmSurf, &C.gio_xdg_surface_listener, unsafe.Pointer(w.surf)) 411 C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf)) 412 413 if d.decor != nil { 414 w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl) 415 C.zxdg_toplevel_decoration_v1_add_listener(w.decor, &C.gio_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.surf)) 416 } 417 w.updateOpaqueRegion() 418 return w, nil 419 } 420 421 func (w *window) loadCursors() { 422 w.cursor.cursors.pointer = w.loadCursor(pointer.CursorDefault) 423 w.cursor.cursors.resizeNorth = w.loadCursor(pointer.CursorNorthResize) 424 w.cursor.cursors.resizeSouth = w.loadCursor(pointer.CursorSouthResize) 425 w.cursor.cursors.resizeWest = w.loadCursor(pointer.CursorWestResize) 426 w.cursor.cursors.resizeEast = w.loadCursor(pointer.CursorEastResize) 427 w.cursor.cursors.resizeSouthWest = w.loadCursor(pointer.CursorSouthWestResize) 428 w.cursor.cursors.resizeSouthEast = w.loadCursor(pointer.CursorSouthEastResize) 429 w.cursor.cursors.resizeNorthWest = w.loadCursor(pointer.CursorNorthWestResize) 430 w.cursor.cursors.resizeNorthEast = w.loadCursor(pointer.CursorNorthEastResize) 431 } 432 433 func (w *window) loadCursor(name pointer.Cursor) *C.struct_wl_cursor { 434 if name == pointer.CursorNone { 435 return nil 436 } 437 xcursor := xCursor[name] 438 cname := C.CString(xcursor) 439 defer C.free(unsafe.Pointer(cname)) 440 c := C.wl_cursor_theme_get_cursor(w.cursor.theme, cname) 441 if c == nil { 442 // Fall back to default cursor. 443 c = w.cursor.cursors.pointer 444 } 445 return c 446 } 447 448 func callbackDelete(k unsafe.Pointer) { 449 callbackMap.Delete(k) 450 } 451 452 func callbackStore(k unsafe.Pointer, v interface{}) { 453 callbackMap.Store(k, v) 454 } 455 456 func callbackLoad(k unsafe.Pointer) interface{} { 457 v, exists := callbackMap.Load(k) 458 if !exists { 459 panic("missing callback entry") 460 } 461 return v 462 } 463 464 //export gio_onSeatCapabilities 465 func gio_onSeatCapabilities(data unsafe.Pointer, seat *C.struct_wl_seat, caps C.uint32_t) { 466 s := callbackLoad(data).(*wlSeat) 467 s.updateCaps(caps) 468 } 469 470 // flushOffers remove all wl_data_offers that isn't the clipboard 471 // content. 472 func (s *wlSeat) flushOffers() { 473 for o := range s.offers { 474 if o == s.clipboard { 475 continue 476 } 477 // We're only interested in clipboard offers. 478 delete(s.offers, o) 479 callbackDelete(unsafe.Pointer(o)) 480 C.wl_data_offer_destroy(o) 481 } 482 } 483 484 func (s *wlSeat) destroy() { 485 if s.source != nil { 486 C.wl_data_source_destroy(s.source) 487 s.source = nil 488 } 489 if s.im != nil { 490 C.zwp_text_input_v3_destroy(s.im) 491 s.im = nil 492 } 493 if s.pointer != nil { 494 C.wl_pointer_release(s.pointer) 495 } 496 if s.touch != nil { 497 C.wl_touch_release(s.touch) 498 } 499 if s.keyboard != nil { 500 C.wl_keyboard_release(s.keyboard) 501 } 502 s.clipboard = nil 503 s.flushOffers() 504 if s.dataDev != nil { 505 C.wl_data_device_release(s.dataDev) 506 } 507 if s.seat != nil { 508 callbackDelete(unsafe.Pointer(s.seat)) 509 C.wl_seat_release(s.seat) 510 } 511 } 512 513 func (s *wlSeat) updateCaps(caps C.uint32_t) { 514 if s.im == nil && s.disp.imm != nil { 515 s.im = C.zwp_text_input_manager_v3_get_text_input(s.disp.imm, s.seat) 516 C.zwp_text_input_v3_add_listener(s.im, &C.gio_zwp_text_input_v3_listener, unsafe.Pointer(s.seat)) 517 } 518 switch { 519 case s.pointer == nil && caps&C.WL_SEAT_CAPABILITY_POINTER != 0: 520 s.pointer = C.wl_seat_get_pointer(s.seat) 521 C.wl_pointer_add_listener(s.pointer, &C.gio_pointer_listener, unsafe.Pointer(s.seat)) 522 case s.pointer != nil && caps&C.WL_SEAT_CAPABILITY_POINTER == 0: 523 C.wl_pointer_release(s.pointer) 524 s.pointer = nil 525 } 526 switch { 527 case s.touch == nil && caps&C.WL_SEAT_CAPABILITY_TOUCH != 0: 528 s.touch = C.wl_seat_get_touch(s.seat) 529 C.wl_touch_add_listener(s.touch, &C.gio_touch_listener, unsafe.Pointer(s.seat)) 530 case s.touch != nil && caps&C.WL_SEAT_CAPABILITY_TOUCH == 0: 531 C.wl_touch_release(s.touch) 532 s.touch = nil 533 } 534 switch { 535 case s.keyboard == nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD != 0: 536 s.keyboard = C.wl_seat_get_keyboard(s.seat) 537 C.wl_keyboard_add_listener(s.keyboard, &C.gio_keyboard_listener, unsafe.Pointer(s.seat)) 538 case s.keyboard != nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD == 0: 539 C.wl_keyboard_release(s.keyboard) 540 s.keyboard = nil 541 } 542 } 543 544 //export gio_onSeatName 545 func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) { 546 } 547 548 //export gio_onXdgSurfaceConfigure 549 func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) { 550 w := callbackLoad(data).(*window) 551 w.serial = serial 552 w.redraw = true 553 C.xdg_surface_ack_configure(wmSurf, serial) 554 w.setStage(system.StageRunning) 555 } 556 557 //export gio_onToplevelClose 558 func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) { 559 w := callbackLoad(data).(*window) 560 w.dead = true 561 } 562 563 //export gio_onToplevelConfigure 564 func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel, width, height C.int32_t, states *C.struct_wl_array) { 565 w := callbackLoad(data).(*window) 566 if width != 0 && height != 0 { 567 w.size = image.Pt(int(width), int(height)) 568 w.updateOpaqueRegion() 569 } 570 } 571 572 //export gio_onToplevelDecorationConfigure 573 func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_toplevel_decoration_v1, mode C.uint32_t) { 574 w := callbackLoad(data).(*window) 575 decorated := w.config.Decorated 576 switch mode { 577 case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: 578 w.config.Decorated = false 579 case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: 580 w.config.Decorated = true 581 } 582 if decorated != w.config.Decorated { 583 w.setWindowConstraints() 584 if w.config.Decorated { 585 w.size.Y -= int(w.config.decoHeight) 586 } else { 587 w.size.Y += int(w.config.decoHeight) 588 } 589 w.w.Event(ConfigEvent{Config: w.config}) 590 w.redraw = true 591 } 592 } 593 594 //export gio_onOutputMode 595 func gio_onOutputMode(data unsafe.Pointer, output *C.struct_wl_output, flags C.uint32_t, width, height, refresh C.int32_t) { 596 if flags&C.WL_OUTPUT_MODE_CURRENT == 0 { 597 return 598 } 599 d := callbackLoad(data).(*wlDisplay) 600 c := d.outputConfig[output] 601 c.width = int(width) 602 c.height = int(height) 603 } 604 605 //export gio_onOutputGeometry 606 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) { 607 d := callbackLoad(data).(*wlDisplay) 608 c := d.outputConfig[output] 609 c.transform = transform 610 c.physWidth = int(physWidth) 611 c.physHeight = int(physHeight) 612 } 613 614 //export gio_onOutputScale 615 func gio_onOutputScale(data unsafe.Pointer, output *C.struct_wl_output, scale C.int32_t) { 616 d := callbackLoad(data).(*wlDisplay) 617 c := d.outputConfig[output] 618 c.scale = int(scale) 619 } 620 621 //export gio_onOutputDone 622 func gio_onOutputDone(data unsafe.Pointer, output *C.struct_wl_output) { 623 d := callbackLoad(data).(*wlDisplay) 624 conf := d.outputConfig[output] 625 for _, w := range conf.windows { 626 w.updateOutputs() 627 } 628 } 629 630 //export gio_onSurfaceEnter 631 func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) { 632 w := callbackLoad(data).(*window) 633 conf := w.disp.outputConfig[output] 634 var found bool 635 for _, w2 := range conf.windows { 636 if w2 == w { 637 found = true 638 break 639 } 640 } 641 if !found { 642 conf.windows = append(conf.windows, w) 643 } 644 w.updateOutputs() 645 if w.config.Mode == Minimized { 646 // Minimized window got brought back up: it is no longer so. 647 w.config.Mode = Windowed 648 w.w.Event(ConfigEvent{Config: w.config}) 649 } 650 } 651 652 //export gio_onSurfaceLeave 653 func gio_onSurfaceLeave(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) { 654 w := callbackLoad(data).(*window) 655 conf := w.disp.outputConfig[output] 656 for i, w2 := range conf.windows { 657 if w2 == w { 658 conf.windows = append(conf.windows[:i], conf.windows[i+1:]...) 659 break 660 } 661 } 662 w.updateOutputs() 663 } 664 665 //export gio_onRegistryGlobal 666 func gio_onRegistryGlobal(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t, cintf *C.char, version C.uint32_t) { 667 d := callbackLoad(data).(*wlDisplay) 668 switch C.GoString(cintf) { 669 case "wl_compositor": 670 d.compositor = (*C.struct_wl_compositor)(C.wl_registry_bind(reg, name, &C.wl_compositor_interface, 3)) 671 case "wl_output": 672 output := (*C.struct_wl_output)(C.wl_registry_bind(reg, name, &C.wl_output_interface, 2)) 673 C.wl_output_add_listener(output, &C.gio_output_listener, unsafe.Pointer(d.disp)) 674 d.outputMap[name] = output 675 d.outputConfig[output] = new(wlOutput) 676 case "wl_seat": 677 if d.seat != nil { 678 break 679 } 680 s := (*C.struct_wl_seat)(C.wl_registry_bind(reg, name, &C.wl_seat_interface, 5)) 681 if s == nil { 682 // No support for v5 protocol. 683 break 684 } 685 d.seat = &wlSeat{ 686 disp: d, 687 name: name, 688 seat: s, 689 offers: make(map[*C.struct_wl_data_offer][]string), 690 touchFoci: make(map[C.int32_t]*window), 691 } 692 callbackStore(unsafe.Pointer(s), d.seat) 693 C.wl_seat_add_listener(s, &C.gio_seat_listener, unsafe.Pointer(s)) 694 d.bindDataDevice() 695 case "wl_shm": 696 d.shm = (*C.struct_wl_shm)(C.wl_registry_bind(reg, name, &C.wl_shm_interface, 1)) 697 case "xdg_wm_base": 698 d.wm = (*C.struct_xdg_wm_base)(C.wl_registry_bind(reg, name, &C.xdg_wm_base_interface, 1)) 699 case "zxdg_decoration_manager_v1": 700 d.decor = (*C.struct_zxdg_decoration_manager_v1)(C.wl_registry_bind(reg, name, &C.zxdg_decoration_manager_v1_interface, 1)) 701 // TODO: Implement and test text-input support. 702 /*case "zwp_text_input_manager_v3": 703 d.imm = (*C.struct_zwp_text_input_manager_v3)(C.wl_registry_bind(reg, name, &C.zwp_text_input_manager_v3_interface, 1))*/ 704 case "wl_data_device_manager": 705 d.dataDeviceManager = (*C.struct_wl_data_device_manager)(C.wl_registry_bind(reg, name, &C.wl_data_device_manager_interface, 3)) 706 d.bindDataDevice() 707 } 708 } 709 710 //export gio_onDataOfferOffer 711 func gio_onDataOfferOffer(data unsafe.Pointer, offer *C.struct_wl_data_offer, mime *C.char) { 712 s := callbackLoad(data).(*wlSeat) 713 s.offers[offer] = append(s.offers[offer], C.GoString(mime)) 714 } 715 716 //export gio_onDataOfferSourceActions 717 func gio_onDataOfferSourceActions(data unsafe.Pointer, offer *C.struct_wl_data_offer, acts C.uint32_t) { 718 } 719 720 //export gio_onDataOfferAction 721 func gio_onDataOfferAction(data unsafe.Pointer, offer *C.struct_wl_data_offer, act C.uint32_t) { 722 } 723 724 //export gio_onDataDeviceOffer 725 func gio_onDataDeviceOffer(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) { 726 s := callbackLoad(data).(*wlSeat) 727 callbackStore(unsafe.Pointer(id), s) 728 C.wl_data_offer_add_listener(id, &C.gio_data_offer_listener, unsafe.Pointer(id)) 729 s.offers[id] = nil 730 } 731 732 //export gio_onDataDeviceEnter 733 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) { 734 s := callbackLoad(data).(*wlSeat) 735 s.serial = serial 736 s.flushOffers() 737 } 738 739 //export gio_onDataDeviceLeave 740 func gio_onDataDeviceLeave(data unsafe.Pointer, dataDev *C.struct_wl_data_device) { 741 } 742 743 //export gio_onDataDeviceMotion 744 func gio_onDataDeviceMotion(data unsafe.Pointer, dataDev *C.struct_wl_data_device, t C.uint32_t, x, y C.wl_fixed_t) { 745 } 746 747 //export gio_onDataDeviceDrop 748 func gio_onDataDeviceDrop(data unsafe.Pointer, dataDev *C.struct_wl_data_device) { 749 } 750 751 //export gio_onDataDeviceSelection 752 func gio_onDataDeviceSelection(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) { 753 s := callbackLoad(data).(*wlSeat) 754 defer s.flushOffers() 755 s.clipboard = nil 756 loop: 757 for _, want := range clipboardMimeTypes { 758 for _, got := range s.offers[id] { 759 if want != got { 760 continue 761 } 762 s.clipboard = id 763 s.mimeType = got 764 break loop 765 } 766 } 767 } 768 769 //export gio_onRegistryGlobalRemove 770 func gio_onRegistryGlobalRemove(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t) { 771 d := callbackLoad(data).(*wlDisplay) 772 if s := d.seat; s != nil && name == s.name { 773 s.destroy() 774 d.seat = nil 775 } 776 if output, exists := d.outputMap[name]; exists { 777 C.wl_output_destroy(output) 778 delete(d.outputMap, name) 779 delete(d.outputConfig, output) 780 } 781 } 782 783 //export gio_onTouchDown 784 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) { 785 s := callbackLoad(data).(*wlSeat) 786 s.serial = serial 787 w := callbackLoad(unsafe.Pointer(surf)).(*window) 788 s.touchFoci[id] = w 789 w.lastTouch = f32.Point{ 790 X: fromFixed(x) * float32(w.scale), 791 Y: fromFixed(y) * float32(w.scale), 792 } 793 w.w.Event(pointer.Event{ 794 Kind: pointer.Press, 795 Source: pointer.Touch, 796 Position: w.lastTouch, 797 PointerID: pointer.ID(id), 798 Time: time.Duration(t) * time.Millisecond, 799 Modifiers: w.disp.xkb.Modifiers(), 800 }) 801 } 802 803 //export gio_onTouchUp 804 func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, id C.int32_t) { 805 s := callbackLoad(data).(*wlSeat) 806 s.serial = serial 807 w := s.touchFoci[id] 808 delete(s.touchFoci, id) 809 w.w.Event(pointer.Event{ 810 Kind: pointer.Release, 811 Source: pointer.Touch, 812 Position: w.lastTouch, 813 PointerID: pointer.ID(id), 814 Time: time.Duration(t) * time.Millisecond, 815 Modifiers: w.disp.xkb.Modifiers(), 816 }) 817 } 818 819 //export gio_onTouchMotion 820 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) { 821 s := callbackLoad(data).(*wlSeat) 822 w := s.touchFoci[id] 823 w.lastTouch = f32.Point{ 824 X: fromFixed(x) * float32(w.scale), 825 Y: fromFixed(y) * float32(w.scale), 826 } 827 w.w.Event(pointer.Event{ 828 Kind: pointer.Move, 829 Position: w.lastTouch, 830 Source: pointer.Touch, 831 PointerID: pointer.ID(id), 832 Time: time.Duration(t) * time.Millisecond, 833 Modifiers: w.disp.xkb.Modifiers(), 834 }) 835 } 836 837 //export gio_onTouchFrame 838 func gio_onTouchFrame(data unsafe.Pointer, touch *C.struct_wl_touch) { 839 } 840 841 //export gio_onTouchCancel 842 func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) { 843 s := callbackLoad(data).(*wlSeat) 844 for id, w := range s.touchFoci { 845 delete(s.touchFoci, id) 846 w.w.Event(pointer.Event{ 847 Kind: pointer.Cancel, 848 Source: pointer.Touch, 849 }) 850 } 851 } 852 853 //export gio_onPointerEnter 854 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) { 855 s := callbackLoad(data).(*wlSeat) 856 s.serial = serial 857 w := callbackLoad(unsafe.Pointer(surf)).(*window) 858 w.seat = s 859 s.pointerFocus = w 860 w.setCursor(pointer, serial) 861 w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)} 862 } 863 864 //export gio_onPointerLeave 865 func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) { 866 w := callbackLoad(unsafe.Pointer(surf)).(*window) 867 w.seat = nil 868 s := callbackLoad(data).(*wlSeat) 869 s.serial = serial 870 if w.inCompositor { 871 w.inCompositor = false 872 w.w.Event(pointer.Event{Kind: pointer.Cancel}) 873 } 874 } 875 876 //export gio_onPointerMotion 877 func gio_onPointerMotion(data unsafe.Pointer, p *C.struct_wl_pointer, t C.uint32_t, x, y C.wl_fixed_t) { 878 s := callbackLoad(data).(*wlSeat) 879 w := s.pointerFocus 880 w.resetFling() 881 w.onPointerMotion(x, y, t) 882 } 883 884 //export gio_onPointerButton 885 func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t, wbtn, state C.uint32_t) { 886 s := callbackLoad(data).(*wlSeat) 887 s.serial = serial 888 w := s.pointerFocus 889 // From linux-event-codes.h. 890 const ( 891 BTN_LEFT = 0x110 892 BTN_RIGHT = 0x111 893 BTN_MIDDLE = 0x112 894 ) 895 var btn pointer.Buttons 896 switch wbtn { 897 case BTN_LEFT: 898 btn = pointer.ButtonPrimary 899 case BTN_RIGHT: 900 btn = pointer.ButtonSecondary 901 case BTN_MIDDLE: 902 btn = pointer.ButtonTertiary 903 default: 904 return 905 } 906 if state == 1 && btn == pointer.ButtonPrimary { 907 if _, edge := w.systemGesture(); edge != 0 { 908 w.resize(serial, edge) 909 return 910 } 911 act, ok := w.w.ActionAt(w.lastPos) 912 if ok && w.config.Mode == Windowed { 913 switch act { 914 case system.ActionMove: 915 w.move(serial) 916 return 917 } 918 } 919 } 920 var kind pointer.Kind 921 switch state { 922 case 0: 923 w.pointerBtns &^= btn 924 kind = pointer.Release 925 // Move or resize gestures no longer applies. 926 w.inCompositor = false 927 case 1: 928 w.pointerBtns |= btn 929 kind = pointer.Press 930 } 931 w.flushScroll() 932 w.resetFling() 933 w.w.Event(pointer.Event{ 934 Kind: kind, 935 Source: pointer.Mouse, 936 Buttons: w.pointerBtns, 937 Position: w.lastPos, 938 Time: time.Duration(t) * time.Millisecond, 939 Modifiers: w.disp.xkb.Modifiers(), 940 }) 941 } 942 943 //export gio_onPointerAxis 944 func gio_onPointerAxis(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.uint32_t, value C.wl_fixed_t) { 945 s := callbackLoad(data).(*wlSeat) 946 w := s.pointerFocus 947 v := fromFixed(value) 948 w.resetFling() 949 if w.scroll.dist == (f32.Point{}) { 950 w.scroll.time = time.Duration(t) * time.Millisecond 951 } 952 switch axis { 953 case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL: 954 w.scroll.dist.X += v 955 case C.WL_POINTER_AXIS_VERTICAL_SCROLL: 956 // horizontal scroll if shift + mousewheel(up/down) pressed. 957 if w.disp.xkb.Modifiers() == key.ModShift { 958 w.scroll.dist.X += v 959 } else { 960 w.scroll.dist.Y += v 961 } 962 } 963 } 964 965 //export gio_onPointerFrame 966 func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) { 967 s := callbackLoad(data).(*wlSeat) 968 w := s.pointerFocus 969 w.flushScroll() 970 w.flushFling() 971 } 972 973 func (w *window) flushFling() { 974 if !w.fling.start { 975 return 976 } 977 w.fling.start = false 978 estx, esty := w.fling.xExtrapolation.Estimate(), w.fling.yExtrapolation.Estimate() 979 w.fling.xExtrapolation = fling.Extrapolation{} 980 w.fling.yExtrapolation = fling.Extrapolation{} 981 vel := float32(math.Sqrt(float64(estx.Velocity*estx.Velocity + esty.Velocity*esty.Velocity))) 982 _, c := w.getConfig() 983 if !w.fling.anim.Start(c, time.Now(), vel) { 984 return 985 } 986 invDist := 1 / vel 987 w.fling.dir.X = estx.Velocity * invDist 988 w.fling.dir.Y = esty.Velocity * invDist 989 } 990 991 //export gio_onPointerAxisSource 992 func gio_onPointerAxisSource(data unsafe.Pointer, pointer *C.struct_wl_pointer, source C.uint32_t) { 993 } 994 995 //export gio_onPointerAxisStop 996 func gio_onPointerAxisStop(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.uint32_t) { 997 s := callbackLoad(data).(*wlSeat) 998 w := s.pointerFocus 999 w.fling.start = true 1000 } 1001 1002 //export gio_onPointerAxisDiscrete 1003 func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) { 1004 s := callbackLoad(data).(*wlSeat) 1005 w := s.pointerFocus 1006 w.resetFling() 1007 switch axis { 1008 case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL: 1009 w.scroll.steps.X += int(discrete) 1010 case C.WL_POINTER_AXIS_VERTICAL_SCROLL: 1011 // horizontal scroll if shift + mousewheel(up/down) pressed. 1012 if w.disp.xkb.Modifiers() == key.ModShift { 1013 w.scroll.steps.X += int(discrete) 1014 } else { 1015 w.scroll.steps.Y += int(discrete) 1016 } 1017 } 1018 } 1019 1020 func (w *window) ReadClipboard() { 1021 r, err := w.disp.readClipboard() 1022 // Send empty responses on unavailable clipboards or errors. 1023 if r == nil || err != nil { 1024 w.w.Event(clipboard.Event{}) 1025 return 1026 } 1027 // Don't let slow clipboard transfers block event loop. 1028 go func() { 1029 defer r.Close() 1030 data, _ := io.ReadAll(r) 1031 w.clipReads <- clipboard.Event{Text: string(data)} 1032 w.Wakeup() 1033 }() 1034 } 1035 1036 func (w *window) WriteClipboard(s string) { 1037 w.disp.writeClipboard([]byte(s)) 1038 } 1039 1040 func (w *window) Configure(options []Option) { 1041 _, cfg := w.getConfig() 1042 prev := w.config 1043 cnf := w.config 1044 cnf.apply(cfg, options) 1045 w.config.decoHeight = cnf.decoHeight 1046 1047 switch cnf.Mode { 1048 case Fullscreen: 1049 switch prev.Mode { 1050 case Minimized, Fullscreen: 1051 default: 1052 w.config.Mode = Fullscreen 1053 w.wsize = w.config.Size 1054 C.xdg_toplevel_set_fullscreen(w.topLvl, nil) 1055 } 1056 case Minimized: 1057 switch prev.Mode { 1058 case Minimized, Fullscreen: 1059 default: 1060 w.config.Mode = Minimized 1061 C.xdg_toplevel_set_minimized(w.topLvl) 1062 } 1063 case Maximized: 1064 switch prev.Mode { 1065 case Minimized, Fullscreen: 1066 default: 1067 w.config.Mode = Maximized 1068 w.wsize = w.config.Size 1069 C.xdg_toplevel_set_maximized(w.topLvl) 1070 w.setTitle(prev, cnf) 1071 } 1072 case Windowed: 1073 switch prev.Mode { 1074 case Fullscreen: 1075 w.config.Mode = Windowed 1076 w.size = w.wsize.Div(w.scale) 1077 C.xdg_toplevel_unset_fullscreen(w.topLvl) 1078 case Minimized: 1079 w.config.Mode = Windowed 1080 case Maximized: 1081 w.config.Mode = Windowed 1082 w.size = w.wsize.Div(w.scale) 1083 C.xdg_toplevel_unset_maximized(w.topLvl) 1084 } 1085 w.setTitle(prev, cnf) 1086 if prev.Size != cnf.Size { 1087 w.config.Size = cnf.Size 1088 w.config.Size.Y += int(w.decoHeight()) * w.scale 1089 w.size = w.config.Size.Div(w.scale) 1090 } 1091 w.config.MinSize = cnf.MinSize 1092 w.config.MaxSize = cnf.MaxSize 1093 w.setWindowConstraints() 1094 } 1095 w.w.Event(ConfigEvent{Config: w.config}) 1096 w.redraw = true 1097 } 1098 1099 func (w *window) setWindowConstraints() { 1100 decoHeight := w.decoHeight() 1101 if scaled := w.config.MinSize.Div(w.scale); scaled != (image.Point{}) { 1102 C.xdg_toplevel_set_min_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight)) 1103 } 1104 if scaled := w.config.MaxSize.Div(w.scale); scaled != (image.Point{}) { 1105 C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight)) 1106 } 1107 } 1108 1109 // decoHeight returns the adjustment for client-side decorations, if applicable. 1110 // The unit is in surface-local coordinates. 1111 func (w *window) decoHeight() int { 1112 if !w.config.Decorated { 1113 return int(w.config.decoHeight) 1114 } 1115 return 0 1116 } 1117 1118 func (w *window) setTitle(prev, cnf Config) { 1119 if prev.Title != cnf.Title { 1120 w.config.Title = cnf.Title 1121 title := C.CString(cnf.Title) 1122 C.xdg_toplevel_set_title(w.topLvl, title) 1123 C.free(unsafe.Pointer(title)) 1124 } 1125 } 1126 1127 func (w *window) Perform(actions system.Action) { 1128 // NB. there is no way for a minimized window to be unminimized. 1129 // https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized 1130 walkActions(actions, func(action system.Action) { 1131 switch action { 1132 case system.ActionClose: 1133 w.dead = true 1134 } 1135 }) 1136 } 1137 1138 func (w *window) move(serial C.uint32_t) { 1139 s := w.seat 1140 if !w.inCompositor && s != nil { 1141 w.inCompositor = true 1142 C.xdg_toplevel_move(w.topLvl, s.seat, serial) 1143 } 1144 } 1145 1146 func (w *window) resize(serial, edge C.uint32_t) { 1147 s := w.seat 1148 if w.inCompositor || s == nil { 1149 return 1150 } 1151 w.inCompositor = true 1152 C.xdg_toplevel_resize(w.topLvl, s.seat, serial, edge) 1153 } 1154 1155 func (w *window) SetCursor(cursor pointer.Cursor) { 1156 w.cursor.cursor = w.loadCursor(cursor) 1157 w.updateCursor() 1158 } 1159 1160 func (w *window) updateCursor() { 1161 ptr := w.disp.seat.pointer 1162 if ptr == nil { 1163 return 1164 } 1165 w.setCursor(ptr, w.serial) 1166 } 1167 1168 func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) { 1169 c := w.cursor.system 1170 if c == nil { 1171 c = w.cursor.cursor 1172 } 1173 if c == nil { 1174 C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0) 1175 return 1176 } 1177 // Get images[0]. 1178 img := *c.images 1179 buf := C.wl_cursor_image_get_buffer(img) 1180 if buf == nil { 1181 return 1182 } 1183 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))) 1184 C.wl_surface_attach(w.cursor.surf, buf, 0, 0) 1185 C.wl_surface_damage(w.cursor.surf, 0, 0, C.int32_t(img.width), C.int32_t(img.height)) 1186 C.wl_surface_commit(w.cursor.surf) 1187 } 1188 1189 func (w *window) resetFling() { 1190 w.fling.start = false 1191 w.fling.anim = fling.Animation{} 1192 } 1193 1194 //export gio_onKeyboardKeymap 1195 func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) { 1196 defer syscall.Close(int(fd)) 1197 s := callbackLoad(data).(*wlSeat) 1198 s.disp.repeat.Stop(0) 1199 s.disp.xkb.DestroyKeymapState() 1200 if format != C.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 { 1201 return 1202 } 1203 if err := s.disp.xkb.LoadKeymap(int(format), int(fd), int(size)); err != nil { 1204 // TODO: Do better. 1205 panic(err) 1206 } 1207 } 1208 1209 //export gio_onKeyboardEnter 1210 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) { 1211 s := callbackLoad(data).(*wlSeat) 1212 s.serial = serial 1213 w := callbackLoad(unsafe.Pointer(surf)).(*window) 1214 s.keyboardFocus = w 1215 s.disp.repeat.Stop(0) 1216 w.w.Event(key.FocusEvent{Focus: true}) 1217 } 1218 1219 //export gio_onKeyboardLeave 1220 func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface) { 1221 s := callbackLoad(data).(*wlSeat) 1222 s.serial = serial 1223 s.disp.repeat.Stop(0) 1224 w := s.keyboardFocus 1225 w.w.Event(key.FocusEvent{Focus: false}) 1226 } 1227 1228 //export gio_onKeyboardKey 1229 func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, timestamp, keyCode, state C.uint32_t) { 1230 s := callbackLoad(data).(*wlSeat) 1231 s.serial = serial 1232 w := s.keyboardFocus 1233 t := time.Duration(timestamp) * time.Millisecond 1234 s.disp.repeat.Stop(t) 1235 w.resetFling() 1236 kc := mapXKBKeycode(uint32(keyCode)) 1237 ks := mapXKBKeyState(uint32(state)) 1238 for _, e := range w.disp.xkb.DispatchKey(kc, ks) { 1239 if ee, ok := e.(key.EditEvent); ok { 1240 // There's no support for IME yet. 1241 w.w.EditorInsert(ee.Text) 1242 } else { 1243 w.w.Event(e) 1244 } 1245 } 1246 if state != C.WL_KEYBOARD_KEY_STATE_PRESSED { 1247 return 1248 } 1249 if w.disp.xkb.IsRepeatKey(kc) { 1250 w.disp.repeat.Start(w, kc, t) 1251 } 1252 } 1253 1254 func mapXKBKeycode(keyCode uint32) uint32 { 1255 // According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode." 1256 return keyCode + 8 1257 } 1258 1259 func mapXKBKeyState(state uint32) key.State { 1260 switch state { 1261 case C.WL_KEYBOARD_KEY_STATE_RELEASED: 1262 return key.Release 1263 default: 1264 return key.Press 1265 } 1266 } 1267 1268 func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) { 1269 if r.rate <= 0 { 1270 return 1271 } 1272 stopC := make(chan struct{}) 1273 r.start = t 1274 r.last = 0 1275 r.now = 0 1276 r.stopC = stopC 1277 r.key = keyCode 1278 r.win = w.w 1279 rate, delay := r.rate, r.delay 1280 go func() { 1281 timer := time.NewTimer(delay) 1282 for { 1283 select { 1284 case <-timer.C: 1285 case <-stopC: 1286 close(stopC) 1287 return 1288 } 1289 r.Advance(delay) 1290 w.disp.wakeup() 1291 delay = time.Second / time.Duration(rate) 1292 timer.Reset(delay) 1293 } 1294 }() 1295 } 1296 1297 func (r *repeatState) Stop(t time.Duration) { 1298 if r.stopC == nil { 1299 return 1300 } 1301 r.stopC <- struct{}{} 1302 <-r.stopC 1303 r.stopC = nil 1304 t -= r.start 1305 if r.now > t { 1306 r.now = t 1307 } 1308 } 1309 1310 func (r *repeatState) Advance(dt time.Duration) { 1311 r.mu.Lock() 1312 defer r.mu.Unlock() 1313 r.now += dt 1314 } 1315 1316 func (r *repeatState) Repeat(d *wlDisplay) { 1317 if r.rate <= 0 { 1318 return 1319 } 1320 r.mu.Lock() 1321 now := r.now 1322 r.mu.Unlock() 1323 for { 1324 var delay time.Duration 1325 if r.last < r.delay { 1326 delay = r.delay 1327 } else { 1328 delay = time.Second / time.Duration(r.rate) 1329 } 1330 if r.last+delay > now { 1331 break 1332 } 1333 for _, e := range d.xkb.DispatchKey(r.key, key.Press) { 1334 if ee, ok := e.(key.EditEvent); ok { 1335 // There's no support for IME yet. 1336 r.win.EditorInsert(ee.Text) 1337 } else { 1338 r.win.Event(e) 1339 } 1340 } 1341 r.last += delay 1342 } 1343 } 1344 1345 //export gio_onFrameDone 1346 func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.uint32_t) { 1347 C.wl_callback_destroy(callback) 1348 w := callbackLoad(data).(*window) 1349 if w.lastFrameCallback == callback { 1350 w.lastFrameCallback = nil 1351 } 1352 } 1353 1354 func (w *window) loop() error { 1355 var p poller 1356 for { 1357 if err := w.disp.dispatch(&p); err != nil { 1358 return err 1359 } 1360 select { 1361 case e := <-w.clipReads: 1362 w.w.Event(e) 1363 case <-w.wakeups: 1364 w.w.Event(wakeupEvent{}) 1365 default: 1366 } 1367 if w.dead { 1368 break 1369 } 1370 w.draw() 1371 } 1372 return nil 1373 } 1374 1375 // bindDataDevice initializes the dataDev field if and only if both 1376 // the seat and dataDeviceManager fields are initialized. 1377 func (d *wlDisplay) bindDataDevice() { 1378 if d.seat != nil && d.dataDeviceManager != nil { 1379 d.seat.dataDev = C.wl_data_device_manager_get_data_device(d.dataDeviceManager, d.seat.seat) 1380 if d.seat.dataDev == nil { 1381 return 1382 } 1383 callbackStore(unsafe.Pointer(d.seat.dataDev), d.seat) 1384 C.wl_data_device_add_listener(d.seat.dataDev, &C.gio_data_device_listener, unsafe.Pointer(d.seat.dataDev)) 1385 } 1386 } 1387 1388 func (d *wlDisplay) dispatch(p *poller) error { 1389 dispfd := C.wl_display_get_fd(d.disp) 1390 // Poll for events and notifications. 1391 pollfds := append(p.pollfds[:0], 1392 syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR}, 1393 syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR}, 1394 ) 1395 dispFd := &pollfds[0] 1396 if ret, err := C.wl_display_flush(d.disp); ret < 0 { 1397 if err != syscall.EAGAIN { 1398 return fmt.Errorf("wayland: wl_display_flush failed: %v", err) 1399 } 1400 // EAGAIN means the output buffer was full. Poll for 1401 // POLLOUT to know when we can write again. 1402 dispFd.Events |= syscall.POLLOUT 1403 } 1404 if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR { 1405 return fmt.Errorf("wayland: poll failed: %v", err) 1406 } 1407 // Clear notifications. 1408 for { 1409 _, err := syscall.Read(d.notify.read, p.buf[:]) 1410 if err == syscall.EAGAIN { 1411 break 1412 } 1413 if err != nil { 1414 return fmt.Errorf("wayland: read from notify pipe failed: %v", err) 1415 } 1416 } 1417 // Handle events 1418 switch { 1419 case dispFd.Revents&syscall.POLLIN != 0: 1420 if ret, err := C.wl_display_dispatch(d.disp); ret < 0 { 1421 return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err) 1422 } 1423 case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0: 1424 return errors.New("wayland: display file descriptor gone") 1425 } 1426 d.repeat.Repeat(d) 1427 return nil 1428 } 1429 1430 func (w *window) Wakeup() { 1431 select { 1432 case w.wakeups <- struct{}{}: 1433 default: 1434 } 1435 w.disp.wakeup() 1436 } 1437 1438 func (w *window) SetAnimating(anim bool) { 1439 w.animating = anim 1440 } 1441 1442 // Wakeup wakes up the event loop through the notification pipe. 1443 func (d *wlDisplay) wakeup() { 1444 oneByte := make([]byte, 1) 1445 if _, err := syscall.Write(d.notify.write, oneByte); err != nil && err != syscall.EAGAIN { 1446 panic(fmt.Errorf("failed to write to pipe: %v", err)) 1447 } 1448 } 1449 1450 func (w *window) destroy() { 1451 if w.cursor.surf != nil { 1452 C.wl_surface_destroy(w.cursor.surf) 1453 } 1454 if w.cursor.theme != nil { 1455 C.wl_cursor_theme_destroy(w.cursor.theme) 1456 } 1457 if w.topLvl != nil { 1458 C.xdg_toplevel_destroy(w.topLvl) 1459 } 1460 if w.surf != nil { 1461 C.wl_surface_destroy(w.surf) 1462 } 1463 if w.wmSurf != nil { 1464 C.xdg_surface_destroy(w.wmSurf) 1465 } 1466 if w.decor != nil { 1467 C.zxdg_toplevel_decoration_v1_destroy(w.decor) 1468 } 1469 callbackDelete(unsafe.Pointer(w.surf)) 1470 } 1471 1472 //export gio_onKeyboardModifiers 1473 func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, depressed, latched, locked, group C.uint32_t) { 1474 s := callbackLoad(data).(*wlSeat) 1475 s.serial = serial 1476 d := s.disp 1477 d.repeat.Stop(0) 1478 if d.xkb == nil { 1479 return 1480 } 1481 d.xkb.UpdateMask(uint32(depressed), uint32(latched), uint32(locked), uint32(group), uint32(group), uint32(group)) 1482 } 1483 1484 //export gio_onKeyboardRepeatInfo 1485 func gio_onKeyboardRepeatInfo(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, rate, delay C.int32_t) { 1486 s := callbackLoad(data).(*wlSeat) 1487 d := s.disp 1488 d.repeat.Stop(0) 1489 d.repeat.rate = int(rate) 1490 d.repeat.delay = time.Duration(delay) * time.Millisecond 1491 } 1492 1493 //export gio_onTextInputEnter 1494 func gio_onTextInputEnter(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) { 1495 } 1496 1497 //export gio_onTextInputLeave 1498 func gio_onTextInputLeave(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) { 1499 } 1500 1501 //export gio_onTextInputPreeditString 1502 func gio_onTextInputPreeditString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char, begin, end C.int32_t) { 1503 } 1504 1505 //export gio_onTextInputCommitString 1506 func gio_onTextInputCommitString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char) { 1507 } 1508 1509 //export gio_onTextInputDeleteSurroundingText 1510 func gio_onTextInputDeleteSurroundingText(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, before, after C.uint32_t) { 1511 } 1512 1513 //export gio_onTextInputDone 1514 func gio_onTextInputDone(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, serial C.uint32_t) { 1515 s := callbackLoad(data).(*wlSeat) 1516 s.serial = serial 1517 } 1518 1519 //export gio_onDataSourceTarget 1520 func gio_onDataSourceTarget(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char) { 1521 } 1522 1523 //export gio_onDataSourceSend 1524 func gio_onDataSourceSend(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char, fd C.int32_t) { 1525 s := callbackLoad(data).(*wlSeat) 1526 content := s.content 1527 go func() { 1528 defer syscall.Close(int(fd)) 1529 syscall.Write(int(fd), content) 1530 }() 1531 } 1532 1533 //export gio_onDataSourceCancelled 1534 func gio_onDataSourceCancelled(data unsafe.Pointer, source *C.struct_wl_data_source) { 1535 s := callbackLoad(data).(*wlSeat) 1536 if s.source == source { 1537 s.content = nil 1538 s.source = nil 1539 } 1540 C.wl_data_source_destroy(source) 1541 } 1542 1543 //export gio_onDataSourceDNDDropPerformed 1544 func gio_onDataSourceDNDDropPerformed(data unsafe.Pointer, source *C.struct_wl_data_source) { 1545 } 1546 1547 //export gio_onDataSourceDNDFinished 1548 func gio_onDataSourceDNDFinished(data unsafe.Pointer, source *C.struct_wl_data_source) { 1549 } 1550 1551 //export gio_onDataSourceAction 1552 func gio_onDataSourceAction(data unsafe.Pointer, source *C.struct_wl_data_source, act C.uint32_t) { 1553 } 1554 1555 func (w *window) flushScroll() { 1556 var fling f32.Point 1557 if w.fling.anim.Active() { 1558 dist := float32(w.fling.anim.Tick(time.Now())) 1559 fling = w.fling.dir.Mul(dist) 1560 } 1561 // The Wayland reported scroll distance for 1562 // discrete scroll axes is only 10 pixels, where 1563 // 100 seems more appropriate. 1564 const discreteScale = 10 1565 if w.scroll.steps.X != 0 { 1566 w.scroll.dist.X *= discreteScale 1567 } 1568 if w.scroll.steps.Y != 0 { 1569 w.scroll.dist.Y *= discreteScale 1570 } 1571 total := w.scroll.dist.Add(fling) 1572 if total == (f32.Point{}) { 1573 return 1574 } 1575 w.w.Event(pointer.Event{ 1576 Kind: pointer.Scroll, 1577 Source: pointer.Mouse, 1578 Buttons: w.pointerBtns, 1579 Position: w.lastPos, 1580 Scroll: total, 1581 Time: w.scroll.time, 1582 Modifiers: w.disp.xkb.Modifiers(), 1583 }) 1584 if w.scroll.steps == (image.Point{}) { 1585 w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X) 1586 w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y) 1587 } 1588 w.scroll.dist = f32.Point{} 1589 w.scroll.steps = image.Point{} 1590 } 1591 1592 func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) { 1593 w.flushScroll() 1594 w.lastPos = f32.Point{ 1595 X: fromFixed(x) * float32(w.scale), 1596 Y: fromFixed(y) * float32(w.scale), 1597 } 1598 w.w.Event(pointer.Event{ 1599 Kind: pointer.Move, 1600 Position: w.lastPos, 1601 Buttons: w.pointerBtns, 1602 Source: pointer.Mouse, 1603 Time: time.Duration(t) * time.Millisecond, 1604 Modifiers: w.disp.xkb.Modifiers(), 1605 }) 1606 c, _ := w.systemGesture() 1607 if c != w.cursor.system { 1608 w.cursor.system = c 1609 w.updateCursor() 1610 } 1611 } 1612 1613 // updateCursor updates the system gesture cursor according to the pointer 1614 // position. 1615 func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) { 1616 if w.config.Mode != Windowed || w.config.Decorated { 1617 return nil, 0 1618 } 1619 border := w.w.w.metric.Dp(3) 1620 x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size 1621 north := y <= border 1622 south := y >= size.Y-border 1623 west := x <= border 1624 east := x >= size.X-border 1625 1626 switch { 1627 default: 1628 fallthrough 1629 case !north && !south && !west && !east: 1630 return nil, 0 1631 case north && west: 1632 return w.cursor.cursors.resizeNorthWest, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT 1633 case north && east: 1634 return w.cursor.cursors.resizeNorthEast, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT 1635 case south && west: 1636 return w.cursor.cursors.resizeSouthWest, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT 1637 case south && east: 1638 return w.cursor.cursors.resizeSouthEast, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT 1639 case north: 1640 return w.cursor.cursors.resizeNorth, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP 1641 case south: 1642 return w.cursor.cursors.resizeSouth, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM 1643 case west: 1644 return w.cursor.cursors.resizeWest, C.XDG_TOPLEVEL_RESIZE_EDGE_LEFT 1645 case east: 1646 return w.cursor.cursors.resizeEast, C.XDG_TOPLEVEL_RESIZE_EDGE_RIGHT 1647 } 1648 } 1649 1650 func (w *window) updateOpaqueRegion() { 1651 reg := C.wl_compositor_create_region(w.disp.compositor) 1652 C.wl_region_add(reg, 0, 0, C.int32_t(w.size.X), C.int32_t(w.size.Y)) 1653 C.wl_surface_set_opaque_region(w.surf, reg) 1654 C.wl_region_destroy(reg) 1655 } 1656 1657 func (w *window) updateOutputs() { 1658 scale := 1 1659 var found bool 1660 for _, conf := range w.disp.outputConfig { 1661 for _, w2 := range conf.windows { 1662 if w2 == w { 1663 found = true 1664 if conf.scale > scale { 1665 scale = conf.scale 1666 } 1667 } 1668 } 1669 } 1670 if found && scale != w.scale { 1671 w.scale = scale 1672 C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale)) 1673 w.redraw = true 1674 } 1675 if !found { 1676 w.setStage(system.StagePaused) 1677 } else { 1678 w.setStage(system.StageRunning) 1679 w.redraw = true 1680 } 1681 } 1682 1683 func (w *window) getConfig() (image.Point, unit.Metric) { 1684 size := w.size.Mul(w.scale) 1685 return size, unit.Metric{ 1686 PxPerDp: w.ppdp * float32(w.scale), 1687 PxPerSp: w.ppsp * float32(w.scale), 1688 } 1689 } 1690 1691 func (w *window) draw() { 1692 w.flushScroll() 1693 size, cfg := w.getConfig() 1694 if cfg == (unit.Metric{}) { 1695 return 1696 } 1697 if size != w.config.Size { 1698 w.config.Size = size 1699 w.w.Event(ConfigEvent{Config: w.config}) 1700 } 1701 anim := w.animating || w.fling.anim.Active() 1702 sync := w.redraw 1703 w.redraw = false 1704 // Draw animation only when not waiting for frame callback. 1705 redrawAnim := anim && w.lastFrameCallback == nil 1706 if !redrawAnim && !sync { 1707 return 1708 } 1709 if anim { 1710 w.lastFrameCallback = C.wl_surface_frame(w.surf) 1711 // Use the surface as listener data for gio_onFrameDone. 1712 C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf)) 1713 } 1714 w.w.Event(frameEvent{ 1715 FrameEvent: system.FrameEvent{ 1716 Now: time.Now(), 1717 Size: w.config.Size, 1718 Metric: cfg, 1719 }, 1720 Sync: sync, 1721 }) 1722 } 1723 1724 func (w *window) setStage(s system.Stage) { 1725 if s == w.stage { 1726 return 1727 } 1728 w.stage = s 1729 w.w.Event(system.StageEvent{Stage: s}) 1730 } 1731 1732 func (w *window) display() *C.struct_wl_display { 1733 return w.disp.disp 1734 } 1735 1736 func (w *window) surface() (*C.struct_wl_surface, int, int) { 1737 sz, _ := w.getConfig() 1738 return w.surf, sz.X, sz.Y 1739 } 1740 1741 func (w *window) ShowTextInput(show bool) {} 1742 1743 func (w *window) SetInputHint(_ key.InputHint) {} 1744 1745 func (w *window) EditorStateChanged(old, new editorState) {} 1746 1747 func (w *window) NewContext() (context, error) { 1748 var firstErr error 1749 if f := newWaylandEGLContext; f != nil { 1750 c, err := f(w) 1751 if err == nil { 1752 return c, nil 1753 } 1754 firstErr = err 1755 } 1756 if f := newWaylandVulkanContext; f != nil { 1757 c, err := f(w) 1758 if err == nil { 1759 return c, nil 1760 } 1761 firstErr = err 1762 } 1763 if firstErr != nil { 1764 return nil, firstErr 1765 } 1766 return nil, errors.New("wayland: no available GPU backends") 1767 } 1768 1769 // detectUIScale reports the system UI scale, or 1.0 if it fails. 1770 func detectUIScale() float32 { 1771 // TODO: What about other window environments? 1772 out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output() 1773 if err != nil { 1774 return 1.0 1775 } 1776 scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32) 1777 if err != nil { 1778 return 1.0 1779 } 1780 return float32(scale) 1781 } 1782 1783 func newWLDisplay() (*wlDisplay, error) { 1784 d := &wlDisplay{ 1785 outputMap: make(map[C.uint32_t]*C.struct_wl_output), 1786 outputConfig: make(map[*C.struct_wl_output]*wlOutput), 1787 } 1788 pipe := make([]int, 2) 1789 if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil { 1790 return nil, fmt.Errorf("wayland: failed to create pipe: %v", err) 1791 } 1792 d.notify.read = pipe[0] 1793 d.notify.write = pipe[1] 1794 xkb, err := xkb.New() 1795 if err != nil { 1796 d.destroy() 1797 return nil, fmt.Errorf("wayland: %v", err) 1798 } 1799 d.xkb = xkb 1800 d.disp, err = C.wl_display_connect(nil) 1801 if d.disp == nil { 1802 d.destroy() 1803 return nil, fmt.Errorf("wayland: wl_display_connect failed: %v", err) 1804 } 1805 callbackMap.Store(unsafe.Pointer(d.disp), d) 1806 d.reg = C.wl_display_get_registry(d.disp) 1807 if d.reg == nil { 1808 d.destroy() 1809 return nil, errors.New("wayland: wl_display_get_registry failed") 1810 } 1811 C.wl_registry_add_listener(d.reg, &C.gio_registry_listener, unsafe.Pointer(d.disp)) 1812 // Wait for the server to register all its globals to the 1813 // registry listener (gio_onRegistryGlobal). 1814 C.wl_display_roundtrip(d.disp) 1815 // Configuration listeners are added to outputs by gio_onRegistryGlobal. 1816 // We need another roundtrip to get the initial output configurations 1817 // through the gio_onOutput* callbacks. 1818 C.wl_display_roundtrip(d.disp) 1819 return d, nil 1820 } 1821 1822 func (d *wlDisplay) destroy() { 1823 if d.notify.write != 0 { 1824 syscall.Close(d.notify.write) 1825 d.notify.write = 0 1826 } 1827 if d.notify.read != 0 { 1828 syscall.Close(d.notify.read) 1829 d.notify.read = 0 1830 } 1831 d.repeat.Stop(0) 1832 if d.xkb != nil { 1833 d.xkb.Destroy() 1834 d.xkb = nil 1835 } 1836 if d.seat != nil { 1837 d.seat.destroy() 1838 d.seat = nil 1839 } 1840 if d.imm != nil { 1841 C.zwp_text_input_manager_v3_destroy(d.imm) 1842 } 1843 if d.decor != nil { 1844 C.zxdg_decoration_manager_v1_destroy(d.decor) 1845 } 1846 if d.shm != nil { 1847 C.wl_shm_destroy(d.shm) 1848 } 1849 if d.compositor != nil { 1850 C.wl_compositor_destroy(d.compositor) 1851 } 1852 if d.wm != nil { 1853 C.xdg_wm_base_destroy(d.wm) 1854 } 1855 for _, output := range d.outputMap { 1856 C.wl_output_destroy(output) 1857 } 1858 if d.reg != nil { 1859 C.wl_registry_destroy(d.reg) 1860 } 1861 if d.disp != nil { 1862 C.wl_display_disconnect(d.disp) 1863 callbackDelete(unsafe.Pointer(d.disp)) 1864 } 1865 } 1866 1867 // fromFixed converts a Wayland wl_fixed_t 23.8 number to float32. 1868 func fromFixed(v C.wl_fixed_t) float32 { 1869 // Convert to float64 to avoid overflow. 1870 // From wayland-util.h. 1871 b := ((1023 + 44) << 52) + (1 << 51) + uint64(v) 1872 f := math.Float64frombits(b) - (3 << 43) 1873 return float32(f) 1874 }