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