gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/os_wayland.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 // +build linux,!android 4 5 package app 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "image" 12 "math" 13 "os/exec" 14 "strconv" 15 "sync" 16 "time" 17 "unsafe" 18 19 "gioui.org/ui/f32" 20 "gioui.org/ui/internal/fling" 21 "gioui.org/ui/key" 22 "gioui.org/ui/pointer" 23 syscall "golang.org/x/sys/unix" 24 ) 25 26 // Use wayland-scanner to generate glue code for the xdg-shell and xdg-decoration extensions. 27 //go:generate wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.h 28 //go:generate wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.c 29 30 //go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.h 31 //go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.c 32 33 //go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.h 34 //go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.c 35 36 //go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_xdg_shell.c 37 //go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_xdg_decoration.c 38 //go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_text_input.c 39 40 /* 41 #cgo LDFLAGS: -lwayland-client -lwayland-cursor 42 43 #include <stdlib.h> 44 #include <wayland-client.h> 45 #include <wayland-cursor.h> 46 #include "wayland_text_input.h" 47 #include "wayland_xdg_shell.h" 48 #include "wayland_xdg_decoration.h" 49 #include "os_wayland.h" 50 */ 51 import "C" 52 53 type wlConn struct { 54 disp *C.struct_wl_display 55 compositor *C.struct_wl_compositor 56 wm *C.struct_xdg_wm_base 57 imm *C.struct_zwp_text_input_manager_v3 58 im *C.struct_zwp_text_input_v3 59 shm *C.struct_wl_shm 60 cursor struct { 61 theme *C.struct_wl_cursor_theme 62 cursor *C.struct_wl_cursor 63 surf *C.struct_wl_surface 64 } 65 decor *C.struct_zxdg_decoration_manager_v1 66 seat *C.struct_wl_seat 67 seatName C.uint32_t 68 pointer *C.struct_wl_pointer 69 touch *C.struct_wl_touch 70 keyboard *C.struct_wl_keyboard 71 xkb *xkb 72 73 repeat repeatState 74 } 75 76 type repeatState struct { 77 rate int 78 delay time.Duration 79 80 key C.uint32_t 81 win *Window 82 stopC chan struct{} 83 84 start time.Duration 85 last time.Duration 86 mu sync.Mutex 87 now time.Duration 88 } 89 90 type window struct { 91 w *Window 92 disp *C.struct_wl_display 93 surf *C.struct_wl_surface 94 wmSurf *C.struct_xdg_surface 95 topLvl *C.struct_xdg_toplevel 96 decor *C.struct_zxdg_toplevel_decoration_v1 97 // Notification pipe fds. 98 notify struct { 99 read, write int 100 } 101 ppdp, ppsp float32 102 scroll struct { 103 time time.Duration 104 steps image.Point 105 dist f32.Point 106 } 107 lastPos f32.Point 108 lastTouch f32.Point 109 110 fling struct { 111 yExtrapolation fling.Extrapolation 112 xExtrapolation fling.Extrapolation 113 anim fling.Animation 114 start bool 115 dir f32.Point 116 } 117 118 stage Stage 119 dead bool 120 pendingErr error 121 lastFrameCallback *C.struct_wl_callback 122 123 mu sync.Mutex 124 animating bool 125 needAck bool 126 // The last configure serial waiting to be ack'ed. 127 serial C.uint32_t 128 width int 129 height int 130 newScale bool 131 scale int 132 } 133 134 type wlOutput struct { 135 width int 136 height int 137 physWidth int 138 physHeight int 139 transform C.int32_t 140 scale int 141 windows []*window 142 } 143 144 var connMu sync.Mutex 145 var conn *wlConn 146 var mainDone = make(chan struct{}) 147 148 var ( 149 winMap = make(map[interface{}]*window) 150 outputMap = make(map[C.uint32_t]*C.struct_wl_output) 151 outputConfig = make(map[*C.struct_wl_output]*wlOutput) 152 ) 153 154 func main() { 155 <-mainDone 156 } 157 158 func createWindow(window *Window, opts *windowOptions) error { 159 connMu.Lock() 160 defer connMu.Unlock() 161 if len(winMap) > 0 { 162 return errors.New("multiple windows are not supported") 163 } 164 if err := waylandConnect(); err != nil { 165 return err 166 } 167 w, err := createNativeWindow(opts) 168 if err != nil { 169 conn.destroy() 170 return err 171 } 172 w.w = window 173 go func() { 174 w.w.setDriver(w) 175 w.loop() 176 w.destroy() 177 conn.destroy() 178 close(mainDone) 179 }() 180 return nil 181 } 182 183 func createNativeWindow(opts *windowOptions) (*window, error) { 184 pipe := make([]int, 2) 185 if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil { 186 return nil, fmt.Errorf("createNativeWindow: failed to create pipe: %v", err) 187 } 188 189 fontScale := detectFontScale() 190 var ppmm float32 191 var scale int 192 for _, conf := range outputConfig { 193 if d, err := conf.ppmm(); err == nil && d > ppmm { 194 ppmm = d 195 } 196 if s := conf.scale; s > scale { 197 scale = s 198 } 199 } 200 ppdp := ppmm * mmPrDp 201 ppsp := ppdp * fontScale 202 ppdp *= monitorScale 203 if ppdp < minDensity { 204 ppdp = minDensity 205 } 206 if ppsp < minDensity { 207 ppsp = minDensity 208 } 209 210 w := &window{ 211 disp: conn.disp, 212 scale: scale, 213 newScale: scale != 1, 214 ppdp: ppdp, 215 ppsp: ppsp, 216 } 217 w.notify.read = pipe[0] 218 w.notify.write = pipe[1] 219 w.surf = C.wl_compositor_create_surface(conn.compositor) 220 if w.surf == nil { 221 w.destroy() 222 return nil, errors.New("wayland: wl_compositor_create_surface failed") 223 } 224 w.wmSurf = C.xdg_wm_base_get_xdg_surface(conn.wm, w.surf) 225 if w.wmSurf == nil { 226 w.destroy() 227 return nil, errors.New("wayland: xdg_wm_base_get_xdg_surface failed") 228 } 229 w.topLvl = C.xdg_surface_get_toplevel(w.wmSurf) 230 if w.topLvl == nil { 231 w.destroy() 232 return nil, errors.New("wayland: xdg_surface_get_toplevel failed") 233 } 234 C.gio_xdg_wm_base_add_listener(conn.wm) 235 C.gio_wl_surface_add_listener(w.surf) 236 C.gio_xdg_surface_add_listener(w.wmSurf) 237 C.gio_xdg_toplevel_add_listener(w.topLvl) 238 title := C.CString(opts.Title) 239 C.xdg_toplevel_set_title(w.topLvl, title) 240 C.free(unsafe.Pointer(title)) 241 242 _, _, cfg := w.config() 243 w.width = cfg.Px(opts.Width) 244 w.height = cfg.Px(opts.Height) 245 if conn.decor != nil { 246 // Request server side decorations. 247 w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(conn.decor, w.topLvl) 248 C.zxdg_toplevel_decoration_v1_set_mode(w.decor, C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) 249 } 250 w.updateOpaqueRegion() 251 C.wl_surface_commit(w.surf) 252 winMap[w.topLvl] = w 253 winMap[w.surf] = w 254 winMap[w.wmSurf] = w 255 return w, nil 256 } 257 258 //export gio_onSeatCapabilities 259 func gio_onSeatCapabilities(data unsafe.Pointer, seat *C.struct_wl_seat, caps C.uint32_t) { 260 if seat != conn.seat { 261 panic("unexpected seat") 262 } 263 if conn.im == nil && conn.imm != nil { 264 conn.im = C.zwp_text_input_manager_v3_get_text_input(conn.imm, conn.seat) 265 C.gio_zwp_text_input_v3_add_listener(conn.im) 266 } 267 switch { 268 case conn.pointer == nil && caps&C.WL_SEAT_CAPABILITY_POINTER != 0: 269 conn.pointer = C.wl_seat_get_pointer(seat) 270 C.gio_wl_pointer_add_listener(conn.pointer) 271 case conn.pointer != nil && caps&C.WL_SEAT_CAPABILITY_POINTER == 0: 272 C.wl_pointer_release(conn.pointer) 273 conn.pointer = nil 274 } 275 switch { 276 case conn.touch == nil && caps&C.WL_SEAT_CAPABILITY_TOUCH != 0: 277 conn.touch = C.wl_seat_get_touch(seat) 278 C.gio_wl_touch_add_listener(conn.touch) 279 case conn.touch != nil && caps&C.WL_SEAT_CAPABILITY_TOUCH == 0: 280 C.wl_touch_release(conn.touch) 281 conn.touch = nil 282 } 283 switch { 284 case conn.keyboard == nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD != 0: 285 conn.keyboard = C.wl_seat_get_keyboard(seat) 286 C.gio_wl_keyboard_add_listener(conn.keyboard) 287 case conn.keyboard != nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD == 0: 288 C.wl_keyboard_release(conn.keyboard) 289 conn.keyboard = nil 290 } 291 } 292 293 //export gio_onSeatName 294 func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) { 295 } 296 297 //export gio_onXdgSurfaceConfigure 298 func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) { 299 w := winMap[wmSurf] 300 w.mu.Lock() 301 w.serial = serial 302 w.needAck = true 303 w.mu.Unlock() 304 w.setStage(StageRunning) 305 w.draw(true) 306 } 307 308 //export gio_onToplevelClose 309 func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) { 310 w := winMap[topLvl] 311 w.dead = true 312 } 313 314 //export gio_onToplevelConfigure 315 func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel, width, height C.int32_t, states *C.struct_wl_array) { 316 w := winMap[topLvl] 317 if width != 0 && height != 0 { 318 w.mu.Lock() 319 defer w.mu.Unlock() 320 w.width = int(width) 321 w.height = int(height) 322 w.updateOpaqueRegion() 323 } 324 } 325 326 //export gio_onOutputMode 327 func gio_onOutputMode(data unsafe.Pointer, output *C.struct_wl_output, flags C.uint32_t, width, height, refresh C.int32_t) { 328 if flags&C.WL_OUTPUT_MODE_CURRENT == 0 { 329 return 330 } 331 c := outputConfig[output] 332 c.width = int(width) 333 c.height = int(height) 334 } 335 336 //export gio_onOutputGeometry 337 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) { 338 c := outputConfig[output] 339 c.transform = transform 340 c.physWidth = int(physWidth) 341 c.physHeight = int(physHeight) 342 } 343 344 //export gio_onOutputScale 345 func gio_onOutputScale(data unsafe.Pointer, output *C.struct_wl_output, scale C.int32_t) { 346 c := outputConfig[output] 347 c.scale = int(scale) 348 } 349 350 //export gio_onOutputDone 351 func gio_onOutputDone(data unsafe.Pointer, output *C.struct_wl_output) { 352 conf := outputConfig[output] 353 for _, w := range conf.windows { 354 w.draw(true) 355 } 356 } 357 358 //export gio_onSurfaceEnter 359 func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) { 360 w := winMap[surf] 361 conf := outputConfig[output] 362 var found bool 363 for _, w2 := range conf.windows { 364 if w2 == w { 365 found = true 366 break 367 } 368 } 369 if !found { 370 conf.windows = append(conf.windows, w) 371 } 372 w.updateOutputs() 373 } 374 375 //export gio_onSurfaceLeave 376 func gio_onSurfaceLeave(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) { 377 w := winMap[surf] 378 conf := outputConfig[output] 379 for i, w2 := range conf.windows { 380 if w2 == w { 381 conf.windows = append(conf.windows[:i], conf.windows[i+1:]...) 382 break 383 } 384 } 385 w.updateOutputs() 386 } 387 388 //export gio_onRegistryGlobal 389 func gio_onRegistryGlobal(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t, cintf *C.char, version C.uint32_t) { 390 switch C.GoString(cintf) { 391 case "wl_compositor": 392 conn.compositor = (*C.struct_wl_compositor)(C.wl_registry_bind(reg, name, &C.wl_compositor_interface, 3)) 393 case "wl_output": 394 output := (*C.struct_wl_output)(C.wl_registry_bind(reg, name, &C.wl_output_interface, 2)) 395 C.gio_wl_output_add_listener(output) 396 outputMap[name] = output 397 outputConfig[output] = new(wlOutput) 398 case "wl_seat": 399 if conn.seat == nil { 400 conn.seatName = name 401 conn.seat = (*C.struct_wl_seat)(C.wl_registry_bind(reg, name, &C.wl_seat_interface, 5)) 402 C.gio_wl_seat_add_listener(conn.seat) 403 } 404 case "wl_shm": 405 conn.shm = (*C.struct_wl_shm)(C.wl_registry_bind(reg, name, &C.wl_shm_interface, 1)) 406 case "xdg_wm_base": 407 conn.wm = (*C.struct_xdg_wm_base)(C.wl_registry_bind(reg, name, &C.xdg_wm_base_interface, 1)) 408 case "zxdg_decoration_manager_v1": 409 conn.decor = (*C.struct_zxdg_decoration_manager_v1)(C.wl_registry_bind(reg, name, &C.zxdg_decoration_manager_v1_interface, 1)) 410 // TODO: Implement and test text-input support. 411 /*case "zwp_text_input_manager_v3": 412 conn.imm = (*C.struct_zwp_text_input_manager_v3)(C.wl_registry_bind(reg, name, &C.zwp_text_input_manager_v3_interface, 1))*/ 413 } 414 } 415 416 //export gio_onRegistryGlobalRemove 417 func gio_onRegistryGlobalRemove(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t) { 418 if conn.seat != nil && name == conn.seatName { 419 if conn.im != nil { 420 C.zwp_text_input_v3_destroy(conn.im) 421 conn.im = nil 422 } 423 if conn.pointer != nil { 424 delete(winMap, conn.pointer) 425 } 426 if conn.touch != nil { 427 delete(winMap, conn.touch) 428 } 429 if conn.keyboard != nil { 430 delete(winMap, conn.keyboard) 431 } 432 C.wl_seat_release(conn.seat) 433 conn.seat = nil 434 } 435 if output, exists := outputMap[name]; exists { 436 C.wl_output_destroy(output) 437 delete(outputMap, name) 438 delete(outputConfig, output) 439 } 440 } 441 442 //export gio_onTouchDown 443 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) { 444 w := winMap[surf] 445 winMap[touch] = w 446 w.lastTouch = f32.Point{ 447 X: fromFixed(x) * float32(w.scale), 448 Y: fromFixed(y) * float32(w.scale), 449 } 450 w.w.event(pointer.Event{ 451 Type: pointer.Press, 452 Source: pointer.Touch, 453 Position: w.lastTouch, 454 PointerID: pointer.ID(id), 455 Time: time.Duration(t) * time.Millisecond, 456 }) 457 } 458 459 //export gio_onTouchUp 460 func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, id C.int32_t) { 461 w := winMap[touch] 462 w.w.event(pointer.Event{ 463 Type: pointer.Release, 464 Source: pointer.Touch, 465 Position: w.lastTouch, 466 PointerID: pointer.ID(id), 467 Time: time.Duration(t) * time.Millisecond, 468 }) 469 } 470 471 //export gio_onTouchMotion 472 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) { 473 w := winMap[touch] 474 w.lastTouch = f32.Point{ 475 X: fromFixed(x) * float32(w.scale), 476 Y: fromFixed(y) * float32(w.scale), 477 } 478 w.w.event(pointer.Event{ 479 Type: pointer.Move, 480 Position: w.lastTouch, 481 Source: pointer.Touch, 482 PointerID: pointer.ID(id), 483 Time: time.Duration(t) * time.Millisecond, 484 }) 485 } 486 487 //export gio_onTouchFrame 488 func gio_onTouchFrame(data unsafe.Pointer, touch *C.struct_wl_touch) { 489 } 490 491 //export gio_onTouchCancel 492 func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) { 493 w := winMap[touch] 494 w.w.event(pointer.Event{ 495 Type: pointer.Cancel, 496 Source: pointer.Touch, 497 }) 498 } 499 500 //export gio_onPointerEnter 501 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) { 502 // Get images[0]. 503 img := *conn.cursor.cursor.images 504 buf := C.wl_cursor_image_get_buffer(img) 505 if buf == nil { 506 return 507 } 508 C.wl_pointer_set_cursor(pointer, serial, conn.cursor.surf, C.int32_t(img.hotspot_x), C.int32_t(img.hotspot_y)) 509 C.wl_surface_attach(conn.cursor.surf, buf, 0, 0) 510 C.wl_surface_damage(conn.cursor.surf, 0, 0, C.int32_t(img.width), C.int32_t(img.height)) 511 C.wl_surface_commit(conn.cursor.surf) 512 w := winMap[surf] 513 winMap[pointer] = w 514 w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)} 515 } 516 517 //export gio_onPointerLeave 518 func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surface *C.struct_wl_surface) { 519 } 520 521 //export gio_onPointerMotion 522 func gio_onPointerMotion(data unsafe.Pointer, p *C.struct_wl_pointer, t C.uint32_t, x, y C.wl_fixed_t) { 523 w := winMap[p] 524 w.resetFling() 525 w.onPointerMotion(x, y, t) 526 } 527 528 //export gio_onPointerButton 529 func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t, button, state C.uint32_t) { 530 w := winMap[p] 531 // From linux-event-codes.h. 532 const BTN_LEFT = 0x110 533 if button != BTN_LEFT { 534 return 535 } 536 var typ pointer.Type 537 switch state { 538 case 0: 539 typ = pointer.Release 540 case 1: 541 typ = pointer.Press 542 } 543 w.flushScroll() 544 w.resetFling() 545 w.w.event(pointer.Event{ 546 Type: typ, 547 Source: pointer.Mouse, 548 Position: w.lastPos, 549 Time: time.Duration(t) * time.Millisecond, 550 }) 551 } 552 553 //export gio_onPointerAxis 554 func gio_onPointerAxis(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.uint32_t, value C.wl_fixed_t) { 555 w := winMap[ptr] 556 v := fromFixed(value) 557 w.resetFling() 558 if w.scroll.dist == (f32.Point{}) { 559 w.scroll.time = time.Duration(t) * time.Millisecond 560 } 561 switch axis { 562 case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL: 563 w.scroll.dist.X += v 564 case C.WL_POINTER_AXIS_VERTICAL_SCROLL: 565 w.scroll.dist.Y += v 566 } 567 } 568 569 //export gio_onPointerFrame 570 func gio_onPointerFrame(data unsafe.Pointer, pointer *C.struct_wl_pointer) { 571 w := winMap[pointer] 572 w.flushScroll() 573 w.flushFling() 574 } 575 576 func (w *window) flushFling() { 577 if !w.fling.start { 578 return 579 } 580 w.fling.start = false 581 estx, esty := w.fling.xExtrapolation.Estimate(), w.fling.yExtrapolation.Estimate() 582 w.fling.xExtrapolation = fling.Extrapolation{} 583 w.fling.yExtrapolation = fling.Extrapolation{} 584 vel := float32(math.Sqrt(float64(estx.Velocity*estx.Velocity + esty.Velocity*esty.Velocity))) 585 _, _, c := w.config() 586 c.now = time.Now() 587 if !w.fling.anim.Start(&c, vel) { 588 return 589 } 590 invDist := 1 / vel 591 w.fling.dir.X = estx.Velocity * invDist 592 w.fling.dir.Y = esty.Velocity * invDist 593 // Wake up the window loop. 594 w.wakeup() 595 } 596 597 //export gio_onPointerAxisSource 598 func gio_onPointerAxisSource(data unsafe.Pointer, pointer *C.struct_wl_pointer, source C.uint32_t) { 599 } 600 601 //export gio_onPointerAxisStop 602 func gio_onPointerAxisStop(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.uint32_t) { 603 w := winMap[ptr] 604 w.fling.start = true 605 } 606 607 //export gio_onPointerAxisDiscrete 608 func gio_onPointerAxisDiscrete(data unsafe.Pointer, pointer *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) { 609 w := winMap[pointer] 610 w.resetFling() 611 switch axis { 612 case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL: 613 w.scroll.steps.X += int(discrete) 614 case C.WL_POINTER_AXIS_VERTICAL_SCROLL: 615 w.scroll.steps.Y += int(discrete) 616 } 617 } 618 619 func (w *window) resetFling() { 620 w.fling.start = false 621 w.fling.anim = fling.Animation{} 622 } 623 624 //export gio_onKeyboardKeymap 625 func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) { 626 defer syscall.Close(int(fd)) 627 conn.repeat.Stop(0) 628 if conn.xkb != nil { 629 conn.xkb.Destroy() 630 } 631 if format != C.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 { 632 return 633 } 634 xkb, err := newXKB(format, fd, size) 635 if err != nil { 636 // TODO: Do better. 637 panic(err) 638 } 639 conn.xkb = xkb 640 } 641 642 //export gio_onKeyboardEnter 643 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) { 644 conn.repeat.Stop(0) 645 w := winMap[surf] 646 winMap[keyboard] = w 647 w.w.event(key.FocusEvent{Focus: true}) 648 } 649 650 //export gio_onKeyboardLeave 651 func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface) { 652 conn.repeat.Stop(0) 653 w := winMap[keyboard] 654 w.w.event(key.FocusEvent{Focus: false}) 655 } 656 657 //export gio_onKeyboardKey 658 func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, timestamp, keyCode, state C.uint32_t) { 659 t := time.Duration(timestamp) * time.Millisecond 660 w := winMap[keyboard] 661 w.resetFling() 662 conn.repeat.Stop(t) 663 if state != C.WL_KEYBOARD_KEY_STATE_PRESSED || conn.xkb == nil { 664 return 665 } 666 conn.xkb.dispatchKey(w.w, keyCode) 667 if conn.xkb.isRepeatKey(keyCode) { 668 conn.repeat.Start(w, keyCode, t) 669 } 670 } 671 672 func (r *repeatState) Start(w *window, keyCode C.uint32_t, t time.Duration) { 673 if r.rate <= 0 { 674 return 675 } 676 stopC := make(chan struct{}) 677 r.start = t 678 r.last = 0 679 r.now = 0 680 r.stopC = stopC 681 r.key = keyCode 682 r.win = w.w 683 rate, delay := r.rate, r.delay 684 go func() { 685 timer := time.NewTimer(delay) 686 for { 687 select { 688 case <-timer.C: 689 case <-stopC: 690 close(stopC) 691 return 692 } 693 r.Advance(delay) 694 w.wakeup() 695 delay = time.Second / time.Duration(rate) 696 timer.Reset(delay) 697 } 698 }() 699 } 700 701 func (r *repeatState) Stop(t time.Duration) { 702 if r.stopC == nil { 703 return 704 } 705 r.stopC <- struct{}{} 706 <-r.stopC 707 r.stopC = nil 708 t -= r.start 709 if r.now > t { 710 r.now = t 711 } 712 } 713 714 func (r *repeatState) Advance(dt time.Duration) { 715 r.mu.Lock() 716 defer r.mu.Unlock() 717 r.now += dt 718 } 719 720 func (r *repeatState) Repeat() { 721 if r.rate <= 0 { 722 return 723 } 724 r.mu.Lock() 725 now := r.now 726 r.mu.Unlock() 727 for { 728 var delay time.Duration 729 if r.last < r.delay { 730 delay = r.delay 731 } else { 732 delay = time.Second / time.Duration(r.rate) 733 } 734 if r.last+delay > now { 735 break 736 } 737 conn.xkb.dispatchKey(r.win, r.key) 738 r.last += delay 739 } 740 } 741 742 //export gio_onFrameDone 743 func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.uint32_t) { 744 C.wl_callback_destroy(callback) 745 surf := (*C.struct_wl_surface)(data) 746 w := winMap[surf] 747 if w.lastFrameCallback == callback { 748 w.lastFrameCallback = nil 749 w.draw(false) 750 } 751 } 752 753 func (w *window) loop() { 754 dispfd := C.wl_display_get_fd(conn.disp) 755 // Poll for events and notifications. 756 pollfds := []syscall.PollFd{ 757 {Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR}, 758 {Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR}, 759 } 760 dispEvents := &pollfds[0].Revents 761 // Plenty of room for a backlog of notifications. 762 var buf = make([]byte, 100) 763 loop: 764 for { 765 C.wl_display_dispatch_pending(conn.disp) 766 if ret := C.wl_display_flush(conn.disp); ret < 0 { 767 break 768 } 769 if w.dead { 770 w.w.event(DestroyEvent{Err: w.pendingErr}) 771 break 772 } 773 // Clear poll events. 774 *dispEvents = 0 775 if _, err := syscall.Ppoll(pollfds, nil, nil); err != nil && err != syscall.EINTR { 776 panic(fmt.Errorf("ppoll failed: %v", err)) 777 } 778 redraw := false 779 // Clear notifications. 780 for { 781 _, err := syscall.Read(w.notify.read, buf) 782 if err == syscall.EAGAIN { 783 break 784 } 785 if err != nil { 786 panic(fmt.Errorf("read from notify pipe failed: %v", err)) 787 } 788 redraw = true 789 } 790 // Handle events 791 switch { 792 case *dispEvents&syscall.POLLIN != 0: 793 if ret := C.wl_display_dispatch(conn.disp); ret < 0 { 794 break loop 795 } 796 case *dispEvents&(syscall.POLLERR|syscall.POLLHUP) != 0: 797 break loop 798 } 799 conn.repeat.Repeat() 800 if redraw { 801 w.draw(false) 802 } 803 } 804 } 805 806 func (w *window) setAnimating(anim bool) { 807 w.mu.Lock() 808 w.animating = anim 809 animating := w.isAnimating() 810 w.mu.Unlock() 811 if animating { 812 w.wakeup() 813 } 814 } 815 816 // Wakeup wakes up the event loop through the notification pipe. 817 func (w *window) wakeup() { 818 oneByte := make([]byte, 1) 819 if _, err := syscall.Write(w.notify.write, oneByte); err != nil && err != syscall.EAGAIN { 820 panic(fmt.Errorf("failed to write to pipe: %v", err)) 821 } 822 } 823 824 func (w *window) destroy() { 825 if w.notify.write != 0 { 826 syscall.Close(w.notify.write) 827 w.notify.write = 0 828 } 829 if w.notify.read != 0 { 830 syscall.Close(w.notify.read) 831 w.notify.read = 0 832 } 833 if w.topLvl != nil { 834 delete(winMap, w.topLvl) 835 C.xdg_toplevel_destroy(w.topLvl) 836 } 837 if w.surf != nil { 838 delete(winMap, w.surf) 839 C.wl_surface_destroy(w.surf) 840 } 841 if w.wmSurf != nil { 842 delete(winMap, w.wmSurf) 843 C.xdg_surface_destroy(w.wmSurf) 844 } 845 if w.decor != nil { 846 C.zxdg_toplevel_decoration_v1_destroy(w.decor) 847 } 848 } 849 850 //export gio_onKeyboardModifiers 851 func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, depressed, latched, locked, group C.uint32_t) { 852 conn.repeat.Stop(0) 853 if conn.xkb == nil { 854 return 855 } 856 conn.xkb.updateMask(depressed, latched, locked, group) 857 } 858 859 //export gio_onKeyboardRepeatInfo 860 func gio_onKeyboardRepeatInfo(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, rate, delay C.int32_t) { 861 conn.repeat.Stop(0) 862 conn.repeat.rate = int(rate) 863 conn.repeat.delay = time.Duration(delay) * time.Millisecond 864 } 865 866 //export gio_onTextInputEnter 867 func gio_onTextInputEnter(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) { 868 } 869 870 //export gio_onTextInputLeave 871 func gio_onTextInputLeave(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) { 872 } 873 874 //export gio_onTextInputPreeditString 875 func gio_onTextInputPreeditString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char, begin, end C.int32_t) { 876 } 877 878 //export gio_onTextInputCommitString 879 func gio_onTextInputCommitString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char) { 880 } 881 882 //export gio_onTextInputDeleteSurroundingText 883 func gio_onTextInputDeleteSurroundingText(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, before, after C.uint32_t) { 884 } 885 886 //export gio_onTextInputDone 887 func gio_onTextInputDone(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, serial C.uint32_t) { 888 } 889 890 // ppmm returns the approximate pixels per millimeter for the output. 891 func (c *wlOutput) ppmm() (float32, error) { 892 if c.physWidth == 0 || c.physHeight == 0 { 893 return 0, errors.New("no physical size data for output") 894 } 895 // Because of https://gitlab.gnome.org/GNOME/mutter/issues/369, output dimensions might be undetectably swapped. 896 // Instead, compute and return sqrt(px²/mm²). 897 density := float32(math.Sqrt(float64(c.width*c.height) / float64(c.physWidth*c.physHeight))) 898 return density, nil 899 } 900 901 func (w *window) flushScroll() { 902 var fling f32.Point 903 if w.fling.anim.Active() { 904 dist := float32(w.fling.anim.Tick(time.Now())) 905 fling = w.fling.dir.Mul(dist) 906 } 907 // The Wayland reported scroll distance for 908 // discrete scroll axis is only 10 pixels, where 909 // 100 seems more appropriate. 910 const discreteScale = 10 911 if w.scroll.steps.X != 0 { 912 w.scroll.dist.X *= discreteScale 913 } 914 if w.scroll.steps.Y != 0 { 915 w.scroll.dist.Y *= discreteScale 916 } 917 total := w.scroll.dist.Add(fling) 918 if total == (f32.Point{}) { 919 return 920 } 921 w.w.event(pointer.Event{ 922 Type: pointer.Move, 923 Source: pointer.Mouse, 924 Position: w.lastPos, 925 Scroll: total, 926 Time: w.scroll.time, 927 }) 928 if w.scroll.steps == (image.Point{}) { 929 w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X) 930 w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y) 931 } 932 w.scroll.dist = f32.Point{} 933 w.scroll.steps = image.Point{} 934 } 935 936 func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) { 937 w.flushScroll() 938 w.lastPos = f32.Point{ 939 X: fromFixed(x) * float32(w.scale), 940 Y: fromFixed(y) * float32(w.scale), 941 } 942 w.w.event(pointer.Event{ 943 Type: pointer.Move, 944 Position: w.lastPos, 945 Source: pointer.Mouse, 946 Time: time.Duration(t) * time.Millisecond, 947 }) 948 } 949 950 func (w *window) updateOpaqueRegion() { 951 reg := C.wl_compositor_create_region(conn.compositor) 952 C.wl_region_add(reg, 0, 0, C.int32_t(w.width), C.int32_t(w.height)) 953 C.wl_surface_set_opaque_region(w.surf, reg) 954 C.wl_region_destroy(reg) 955 } 956 957 func (w *window) updateOutputs() { 958 scale := 1 959 var found bool 960 for _, conf := range outputConfig { 961 for _, w2 := range conf.windows { 962 if w2 == w { 963 found = true 964 if conf.scale > scale { 965 scale = conf.scale 966 } 967 } 968 } 969 } 970 w.mu.Lock() 971 if found && scale != w.scale { 972 w.scale = scale 973 w.newScale = true 974 } 975 w.mu.Unlock() 976 if !found { 977 w.setStage(StagePaused) 978 } else { 979 w.setStage(StageRunning) 980 w.draw(true) 981 } 982 } 983 984 func (w *window) config() (int, int, Config) { 985 width, height := w.width*w.scale, w.height*w.scale 986 return width, height, Config{ 987 pxPerDp: w.ppdp * float32(w.scale), 988 pxPerSp: w.ppsp * float32(w.scale), 989 } 990 } 991 992 func (w *window) isAnimating() bool { 993 return w.animating || w.fling.anim.Active() 994 } 995 996 func (w *window) kill(err error) { 997 if w.pendingErr == nil { 998 w.pendingErr = err 999 } 1000 w.dead = true 1001 } 1002 1003 func (w *window) draw(sync bool) { 1004 w.flushScroll() 1005 w.mu.Lock() 1006 animating := w.isAnimating() 1007 dead := w.dead 1008 w.mu.Unlock() 1009 if dead || (!animating && !sync) { 1010 return 1011 } 1012 width, height, cfg := w.config() 1013 if cfg == (Config{}) { 1014 return 1015 } 1016 if animating && w.lastFrameCallback == nil { 1017 w.lastFrameCallback = C.wl_surface_frame(w.surf) 1018 // Use the surface as listener data for gio_onFrameDone. 1019 C.gio_wl_callback_add_listener(w.lastFrameCallback, unsafe.Pointer(w.surf)) 1020 } 1021 cfg.now = time.Now() 1022 w.w.event(UpdateEvent{ 1023 Size: image.Point{ 1024 X: width, 1025 Y: height, 1026 }, 1027 Config: cfg, 1028 sync: sync, 1029 }) 1030 } 1031 1032 func (w *window) setStage(s Stage) { 1033 if s == w.stage { 1034 return 1035 } 1036 w.stage = s 1037 w.w.event(StageEvent{s}) 1038 } 1039 1040 func (w *window) display() unsafe.Pointer { 1041 return unsafe.Pointer(w.disp) 1042 } 1043 1044 func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) { 1045 w.mu.Lock() 1046 defer w.mu.Unlock() 1047 if w.needAck { 1048 C.xdg_surface_ack_configure(w.wmSurf, w.serial) 1049 w.needAck = false 1050 } 1051 width, height, scale := w.width, w.height, w.scale 1052 if w.newScale { 1053 C.wl_surface_set_buffer_scale(w.surf, C.int32_t(scale)) 1054 w.newScale = false 1055 } 1056 return unsafe.Pointer(w.surf), width * scale, height * scale 1057 } 1058 1059 func (w *window) showTextInput(show bool) {} 1060 1061 // detectFontScale reports current font scale, or 1.0 1062 // if it fails. 1063 func detectFontScale() float32 { 1064 // TODO: What about other window environments? 1065 out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output() 1066 if err != nil { 1067 return 1.0 1068 } 1069 scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32) 1070 if err != nil { 1071 return 1.0 1072 } 1073 return float32(scale) 1074 } 1075 1076 func waylandConnect() error { 1077 c := new(wlConn) 1078 conn = c 1079 c.disp = C.wl_display_connect(nil) 1080 if c.disp == nil { 1081 c.destroy() 1082 return errors.New("wayland: wl_display_connect failed") 1083 } 1084 reg := C.wl_display_get_registry(c.disp) 1085 if reg == nil { 1086 c.destroy() 1087 return errors.New("wayland: wl_display_get_registry failed") 1088 } 1089 C.gio_wl_registry_add_listener(reg) 1090 // Wait for the server to register all its globals to the 1091 // registry listener (gio_onRegistryGlobal). 1092 C.wl_display_roundtrip(c.disp) 1093 // Configuration listeners are added to outputs by gio_onRegistryGlobal. 1094 // We need another roundtrip to get the initial output configurations 1095 // through the gio_onOutput* callbacks. 1096 C.wl_display_roundtrip(c.disp) 1097 if c.compositor == nil { 1098 c.destroy() 1099 return errors.New("wayland: no compositor available") 1100 } 1101 if c.wm == nil { 1102 c.destroy() 1103 return errors.New("wayland: no xdg_wm_base available") 1104 } 1105 if c.shm == nil { 1106 c.destroy() 1107 return errors.New("wayland: no wl_shm available") 1108 } 1109 if len(outputMap) == 0 { 1110 c.destroy() 1111 return errors.New("wayland: no outputs available") 1112 } 1113 c.cursor.theme = C.wl_cursor_theme_load(nil, 32, c.shm) 1114 if c.cursor.theme == nil { 1115 c.destroy() 1116 return errors.New("wayland: wl_cursor_theme_load failed") 1117 } 1118 cname := C.CString("left_ptr") 1119 defer C.free(unsafe.Pointer(cname)) 1120 c.cursor.cursor = C.wl_cursor_theme_get_cursor(c.cursor.theme, cname) 1121 if c.cursor.cursor == nil { 1122 c.destroy() 1123 return errors.New("wayland: wl_cursor_theme_get_cursor failed") 1124 } 1125 c.cursor.surf = C.wl_compositor_create_surface(conn.compositor) 1126 if c.cursor.surf == nil { 1127 c.destroy() 1128 return errors.New("wayland: wl_compositor_create_surface failed") 1129 } 1130 return nil 1131 } 1132 1133 func (c *wlConn) destroy() { 1134 c.repeat.Stop(0) 1135 if c.xkb != nil { 1136 c.xkb.Destroy() 1137 c.xkb = nil 1138 } 1139 if c.cursor.surf != nil { 1140 C.wl_surface_destroy(c.cursor.surf) 1141 } 1142 if c.cursor.theme != nil { 1143 C.wl_cursor_theme_destroy(c.cursor.theme) 1144 } 1145 if c.keyboard != nil { 1146 C.wl_keyboard_release(c.keyboard) 1147 } 1148 if c.pointer != nil { 1149 C.wl_pointer_release(c.pointer) 1150 } 1151 if c.touch != nil { 1152 C.wl_touch_release(c.touch) 1153 } 1154 if c.im != nil { 1155 C.zwp_text_input_v3_destroy(c.im) 1156 } 1157 if c.imm != nil { 1158 C.zwp_text_input_manager_v3_destroy(c.imm) 1159 } 1160 if c.seat != nil { 1161 C.wl_seat_release(c.seat) 1162 } 1163 if c.decor != nil { 1164 C.zxdg_decoration_manager_v1_destroy(c.decor) 1165 } 1166 if c.shm != nil { 1167 C.wl_shm_destroy(c.shm) 1168 } 1169 if c.compositor != nil { 1170 C.wl_compositor_destroy(c.compositor) 1171 } 1172 if c.wm != nil { 1173 C.xdg_wm_base_destroy(c.wm) 1174 } 1175 for _, output := range outputMap { 1176 C.wl_output_destroy(output) 1177 } 1178 if c.disp != nil { 1179 C.wl_display_disconnect(c.disp) 1180 } 1181 } 1182 1183 // fromFixed converts a Wayland wl_fixed_t 23.8 number to float32. 1184 func fromFixed(v C.wl_fixed_t) float32 { 1185 // Convert to float64 to avoid overflow. 1186 // From wayland-util.h. 1187 b := ((1023 + 44) << 52) + (1 << 51) + uint64(v) 1188 f := math.Float64frombits(b) - (3 << 43) 1189 return float32(f) 1190 }