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