Advertise `layer-shell` v4
b52eb82ec14ac49667425ddc421972eb2f112b4e
1 parent
cf81044e
swm.c
+15 -4
| 1805 | 1805 | arrange_layer(m, &m->layers[i], &usable_area, 0); |
|
| 1806 | 1806 | ||
| 1807 | 1807 | /* Give keyboard focus to the highest layer that requests it. */ |
|
| 1808 | 1808 | for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { |
|
| 1809 | 1809 | wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { |
|
| 1810 | - | if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) |
|
| 1810 | + | if (locked || |
|
| 1811 | + | l->layer_surface->current.keyboard_interactive != |
|
| 1812 | + | ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE || |
|
| 1813 | + | !l->mapped) |
|
| 1811 | 1814 | continue; |
|
| 1812 | 1815 | /* Deactivate the focused client. */ |
|
| 1813 | 1816 | focus_client(nullptr, 0); |
|
| 1814 | 1817 | exclusive_focus = l; |
|
| 1815 | 1818 | client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); |
| 1888 | 1891 | void button_press(struct wl_listener *listener, void *data) { |
|
| 1889 | 1892 | struct wlr_pointer_button_event *event = data; |
|
| 1890 | 1893 | struct wlr_keyboard *keyboard; |
|
| 1891 | 1894 | uint32_t mods; |
|
| 1892 | 1895 | client_t *c; |
|
| 1896 | + | layer_surface_t *l; |
|
| 1893 | 1897 | const button_t *b; |
|
| 1894 | 1898 | ||
| 1895 | 1899 | wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); |
|
| 1896 | 1900 | ||
| 1897 | 1901 | switch (event->state) { |
| 1901 | 1905 | ||
| 1902 | 1906 | if (locked) |
|
| 1903 | 1907 | break; |
|
| 1904 | 1908 | ||
| 1905 | 1909 | /* Focus a window when a button is pressed over it. */ |
|
| 1906 | - | point_to_node(cursor->x, cursor->y, nullptr, &c, nullptr, nullptr, nullptr); |
|
| 1910 | + | point_to_node(cursor->x, cursor->y, nullptr, &c, &l, nullptr, nullptr); |
|
| 1907 | 1911 | ||
| 1908 | 1912 | if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) |
|
| 1909 | 1913 | focus_client(c, 1); |
|
| 1914 | + | else if ( |
|
| 1915 | + | l && l->layer_surface->current.keyboard_interactive == |
|
| 1916 | + | ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND |
|
| 1917 | + | ) { |
|
| 1918 | + | focus_client(nullptr, 0); |
|
| 1919 | + | client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); |
|
| 1920 | + | } |
|
| 1910 | 1921 | ||
| 1911 | 1922 | keyboard = wlr_seat_get_keyboard(seat); |
|
| 1912 | 1923 | mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; |
|
| 1913 | 1924 | ||
| 1914 | 1925 | for (b = buttons; b < END(buttons); b++) { |
| 2954 | 2965 | /* Deactivate the old window when focus changes. */ |
|
| 2955 | 2966 | if (old && (!c || client_surface(c) != old)) { |
|
| 2956 | 2967 | /* If an overlay is focused, don't focus or activate the client, |
|
| 2957 | 2968 | * but only update its position in fstack to render its border with |
|
| 2958 | 2969 | * focuscolor and focus it after the overlay is closed. */ |
|
| 2959 | - | if (old_client_type == LAYER_SHELL && |
|
| 2970 | + | if (old_client_type == LAYER_SHELL && old_l == exclusive_focus && |
|
| 2960 | 2971 | wlr_scene_node_coords(&old_l->scene->node, &unused_lx, &unused_ly) && |
|
| 2961 | 2972 | old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { |
|
| 2962 | 2973 | return; |
|
| 2963 | 2974 | } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { |
|
| 2964 | 2975 | return; |
| 5636 | 5647 | wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); |
|
| 5637 | 5648 | ||
| 5638 | 5649 | xdg_dialog_mgr = wlr_xdg_wm_dialog_v1_create(dpy, 1); |
|
| 5639 | 5650 | wl_signal_add(&xdg_dialog_mgr->events.new_dialog, &new_xdg_dialog); |
|
| 5640 | 5651 | ||
| 5641 | - | layer_shell = wlr_layer_shell_v1_create(dpy, 3); |
|
| 5652 | + | layer_shell = wlr_layer_shell_v1_create(dpy, 4); |
|
| 5642 | 5653 | wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); |
|
| 5643 | 5654 | ||
| 5644 | 5655 | idle_notifier = wlr_idle_notifier_v1_create(dpy); |
|
| 5645 | 5656 | ||
| 5646 | 5657 | idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); |
test/clients.py
+73 -3
| 37 | 37 | WlShm, |
|
| 38 | 38 | ) |
|
| 39 | 39 | from protocols.wlr_foreign_toplevel_management_unstable_v1 import ( |
|
| 40 | 40 | ZwlrForeignToplevelManagerV1, |
|
| 41 | 41 | ) |
|
| 42 | - | from protocols.wlr_layer_shell_unstable_v1 import ZwlrLayerShellV1 |
|
| 42 | + | from protocols.wlr_layer_shell_unstable_v1 import ( |
|
| 43 | + | ZwlrLayerShellV1, |
|
| 44 | + | ZwlrLayerSurfaceV1, |
|
| 45 | + | ) |
|
| 43 | 46 | from protocols.wlr_output_power_management_unstable_v1 import ( |
|
| 44 | 47 | ZwlrOutputPowerManagerV1, |
|
| 45 | 48 | ) |
|
| 46 | 49 | from protocols.wlr_virtual_pointer_unstable_v1 import ZwlrVirtualPointerManagerV1 |
|
| 47 | 50 | from protocols.xdg_shell import XdgPositioner, XdgToplevel, XdgWmBase |
| 58 | 61 | self.display = Display() |
|
| 59 | 62 | self.display.connect() |
|
| 60 | 63 | self.registry = self.display.get_registry() |
|
| 61 | 64 | self.interfaces = {interface.name: interface for interface in interfaces} |
|
| 62 | 65 | self.globals: dict[str, object] = {} |
|
| 66 | + | self._versions: dict[str, int] = {} |
|
| 63 | 67 | self.registry.dispatcher["global"] = self._global |
|
| 64 | 68 | self.display.roundtrip() |
|
| 65 | 69 | ||
| 66 | 70 | def _global(self, registry, name: int, interface: str, version: int) -> None: |
|
| 67 | 71 | wanted = self.interfaces.get(interface) |
|
| 68 | 72 | if wanted is not None and interface not in self.globals: |
|
| 73 | + | self._versions[interface] = version |
|
| 69 | 74 | self.globals[interface] = registry.bind(name, wanted, min(version, wanted.version)) |
|
| 70 | 75 | ||
| 71 | 76 | def get(self, interface): |
|
| 72 | 77 | """Return a required bound global.""" |
|
| 73 | 78 | ||
| 74 | 79 | try: |
|
| 75 | 80 | return self.globals[interface.name] |
|
| 76 | 81 | except KeyError as error: |
|
| 77 | 82 | raise Failure(f"compositor does not advertise {interface.name}") from error |
|
| 78 | 83 | ||
| 84 | + | def require_version(self, interface, version: int) -> None: |
|
| 85 | + | """Require a minimum advertised version for one global.""" |
|
| 86 | + | ||
| 87 | + | advertised = self._versions.get(interface.name, 0) |
|
| 88 | + | if advertised < version: |
|
| 89 | + | raise Failure( |
|
| 90 | + | f"compositor advertises {interface.name} version {advertised}, " |
|
| 91 | + | f"but version {version} is required" |
|
| 92 | + | ) |
|
| 93 | + | ||
| 79 | 94 | def roundtrips(self, count: int = 1) -> None: |
|
| 80 | 95 | """Complete several request/event exchanges.""" |
|
| 81 | 96 | ||
| 82 | 97 | for _ in range(count): |
|
| 83 | 98 | if self.display.roundtrip() < 0: |
| 512 | 527 | ||
| 513 | 528 | def layer(arguments: list[str]) -> None: |
|
| 514 | 529 | """Create, configure, map, and destroy layer-shell surfaces.""" |
|
| 515 | 530 | ||
| 516 | 531 | count = int(arguments[0]) if arguments else 2 |
|
| 517 | - | connection = Connection(WlCompositor, WlShm, ZwlrLayerShellV1) |
|
| 532 | + | connection = Connection( |
|
| 533 | + | WlCompositor, |
|
| 534 | + | WlSeat, |
|
| 535 | + | WlShm, |
|
| 536 | + | ZwpVirtualKeyboardManagerV1, |
|
| 537 | + | ZwlrLayerShellV1, |
|
| 538 | + | ZwlrVirtualPointerManagerV1, |
|
| 539 | + | ) |
|
| 518 | 540 | compositor = connection.get(WlCompositor) |
|
| 541 | + | seat = connection.get(WlSeat) |
|
| 519 | 542 | shm = connection.get(WlShm) |
|
| 520 | 543 | shell = connection.get(ZwlrLayerShellV1) |
|
| 544 | + | connection.require_version(ZwlrLayerShellV1, 4) |
|
| 545 | + | virtual_keyboard = connection.get(ZwpVirtualKeyboardManagerV1).create_virtual_keyboard( |
|
| 546 | + | seat |
|
| 547 | + | ) |
|
| 548 | + | keymap, _ = keymap_text() |
|
| 549 | + | keymap_stream = tempfile.TemporaryFile(dir=os.environ.get("SWM_TEST_DIR")) |
|
| 550 | + | keymap_stream.write(keymap + b"\0") |
|
| 551 | + | keymap_stream.flush() |
|
| 552 | + | virtual_keyboard.keymap( |
|
| 553 | + | WlKeyboard.keymap_format.xkb_v1, |
|
| 554 | + | keymap_stream.fileno(), |
|
| 555 | + | len(keymap) + 1, |
|
| 556 | + | ) |
|
| 557 | + | connection.roundtrips() |
|
| 558 | + | keyboard = seat.get_keyboard() |
|
| 559 | + | keyboard_enters = [] |
|
| 560 | + | keyboard.dispatcher["enter"] = lambda proxy, serial, surface, keys: ( |
|
| 561 | + | keyboard_enters.append(surface) |
|
| 562 | + | ) |
|
| 563 | + | keyboard.dispatcher["leave"] = lambda proxy, serial, surface: None |
|
| 521 | 564 | layers = [] |
|
| 522 | 565 | for index in range(count): |
|
| 523 | 566 | surface = compositor.create_surface() |
|
| 524 | 567 | role = shell.get_layer_surface( |
|
| 525 | 568 | surface, |
|
| 526 | 569 | None, |
|
| 527 | 570 | ZwlrLayerShellV1.layer.top, |
|
| 528 | 571 | f"swm-layer-{index}", |
|
| 529 | 572 | ) |
|
| 573 | + | role.set_keyboard_interactivity( |
|
| 574 | + | ZwlrLayerSurfaceV1.keyboard_interactivity.on_demand |
|
| 575 | + | ) |
|
| 576 | + | role.set_anchor( |
|
| 577 | + | ZwlrLayerSurfaceV1.anchor.top |
|
| 578 | + | | ( |
|
| 579 | + | ZwlrLayerSurfaceV1.anchor.left |
|
| 580 | + | if index == 0 |
|
| 581 | + | else ZwlrLayerSurfaceV1.anchor.right |
|
| 582 | + | ) |
|
| 583 | + | ) |
|
| 530 | 584 | configured = [] |
|
| 531 | 585 | ||
| 532 | 586 | def configure( |
|
| 533 | 587 | proxy, |
|
| 534 | 588 | serial, |
| 547 | 601 | configured.append(True) |
|
| 548 | 602 | ||
| 549 | 603 | role.dispatcher["configure"] = configure |
|
| 550 | 604 | role.dispatcher["closed"] = lambda proxy: None |
|
| 551 | 605 | role.set_size(80 + index, 40 + index) |
|
| 552 | - | role.set_anchor(1 << (index % 4)) |
|
| 553 | 606 | surface.commit() |
|
| 554 | 607 | layers.append((surface, role, configured)) |
|
| 555 | 608 | connection.roundtrips(3) |
|
| 556 | 609 | if not all(configured for _, _, configured in layers): |
|
| 557 | 610 | raise Failure("layer surface was not configured") |
|
| 611 | + | if keyboard_enters: |
|
| 612 | + | raise Failure("on-demand layer surface received keyboard focus when mapped") |
|
| 613 | + | ||
| 614 | + | pointer = connection.get(ZwlrVirtualPointerManagerV1).create_virtual_pointer(seat) |
|
| 615 | + | pointer.motion_absolute(0, 20, 20, 1000, 1000) |
|
| 616 | + | pointer.frame() |
|
| 617 | + | pointer.button(1, 0x110, 1) |
|
| 618 | + | pointer.frame() |
|
| 619 | + | pointer.button(2, 0x110, 0) |
|
| 620 | + | pointer.frame() |
|
| 621 | + | connection.roundtrips(2) |
|
| 622 | + | if not keyboard_enters: |
|
| 623 | + | raise Failure("on-demand layer surface did not receive keyboard focus when pressed") |
|
| 624 | + | pointer.destroy() |
|
| 625 | + | keyboard.release() |
|
| 626 | + | virtual_keyboard.destroy() |
|
| 627 | + | keymap_stream.close() |
|
| 558 | 628 | for surface, role, _ in reversed(layers): |
|
| 559 | 629 | role.destroy() |
|
| 560 | 630 | surface.destroy() |
|
| 561 | 631 | connection.roundtrips() |
|
| 562 | 632 | connection.close() |
test/unit.c
+3 -3
| 527 | 527 | struct wlr_box box; |
|
| 528 | 528 | char storage[MAX_COMMAND_SIZE], *expanded[MAX_COMMAND_ARGS]; |
|
| 529 | 529 | const char *const command[] = { "before-$SWM_TEST_EXPAND", "$SWM_TEST_MISSING", nullptr }; |
|
| 530 | 530 | ||
| 531 | 531 | init_xdg_client(&client, &xdg, &toplevel, "xdg-app", "XDG title"); |
|
| 532 | - | xdg.geometry = (struct wlr_box){ 3, 4, 50, 60 }; |
|
| 533 | - | client.geom = (struct wlr_box){ 1, 2, 70, 80 }; |
|
| 534 | - | client.bw = 2; |
|
| 532 | + | xdg.geometry = (struct wlr_box){ 3, 4, 50, 60 }; |
|
| 533 | + | client.geom = (struct wlr_box){ 1, 2, 70, 80 }; |
|
| 534 | + | client.bw = 2; |
|
| 535 | 535 | client.is_fullscreen = true; |
|
| 536 | 536 | assert(!client_allows_move_resize(&client, CURSOR_MOVE)); |
|
| 537 | 537 | assert(client_allows_move_resize(&client, CURSOR_RESIZE)); |
|
| 538 | 538 | client.is_fullscreen = false; |
|
| 539 | 539 | assert(client_allows_move_resize(&client, CURSOR_MOVE)); |