Advertise `layer-shell` v4

b52eb82ec14ac49667425ddc421972eb2f112b4e
Alexis Sellier committed ago 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));