Consume releases for compositor shortcuts

d8086fdebfc429d42c04f54b07c66a34ed4ee446
Alexis Sellier committed ago 1 parent c8cc6a7b
swm.c +29 -3
255 255
    bool                       is_virtual;
256 256
    int                        nsyms;
257 257
    const xkb_keysym_t        *keysyms; /* Valid only when nsyms is nonzero. */
258 258
    uint32_t                   mods;    /* Valid only when nsyms is nonzero. */
259 259
    struct wl_event_source    *key_repeat_source;
260 +
    uint32_t                   handled_keys[(KEY_CNT + 31) / 32];
260 261
    struct wl_listener         modifiers;
261 262
    struct wl_listener         key;
262 263
    struct wl_listener         destroy;
263 264
} keyboard_group_t;
264 265
3753 3754
    /* Handle window-manager shortcuts before applications receive the key. */
3754 3755
    const key_t *k;
3755 3756
    int          inhibited = shortcuts_inhibited();
3756 3757
3757 3758
    for (k = keys; k < END(keys); k++) {
3759 +
3758 3760
        if (CLEANMASK(mods) == CLEANMASK(k->mod) &&
3759 3761
            xkb_keysym_to_lower(sym) == xkb_keysym_to_lower(k->keysym) && k->func) {
3760 3762
            int repeatable = k->func == focus_stack || k->func == swap_client ||
3761 3763
                             k->func == stack_config || k->func == cycle_workspace;
3762 3764
3772 3774
        }
3773 3775
    }
3774 3776
    return 0;
3775 3777
}
3776 3778
3779 +
/* Track compositor-consumed presses so their unmatched releases are not sent
3780 +
 * to the focused application. */
3781 +
static bool key_event_should_forward(
3782 +
    keyboard_group_t *group, uint32_t keycode, uint32_t state, bool handled
3783 +
) {
3784 +
    uint32_t *word;
3785 +
    uint32_t  mask;
3786 +
3787 +
    if (keycode >= KEY_CNT)
3788 +
        return !handled;
3789 +
    word = &group->handled_keys[keycode / 32];
3790 +
    mask = 1u << (keycode % 32);
3791 +
3792 +
    if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
3793 +
        if (handled)
3794 +
            *word |= mask;
3795 +
        else
3796 +
            *word &= ~mask;
3797 +
        return !handled;
3798 +
    }
3799 +
    if (state == WL_KEYBOARD_KEY_STATE_RELEASED && (*word & mask)) {
3800 +
        *word &= ~mask;
3801 +
        return false;
3802 +
    }
3803 +
    return true;
3804 +
}
3777 3805
/* Handle shortcuts and forward unhandled key events to the focused application. */
3778 3806
void key_press(struct wl_listener *listener, void *data) {
3779 3807
    int i;
3780 3808
3781 3809
    /* This event is raised when a key is pressed or released. */
3798 3826
        for (i = 0; i < nsyms; i++)
3799 3827
            handled |= key_binding(mods, syms[i], 0);
3800 3828
    }
3801 3829
    if ((handled & 2) && group->wlr_group->keyboard.repeat_info.delay > 0) {
3802 3830
        group->mods    = mods;
3803 -
        group->keysyms = syms;
3804 -
        group->nsyms   = nsyms;
3805 3831
        wl_event_source_timer_update(
3806 3832
            group->key_repeat_source, group->wlr_group->keyboard.repeat_info.delay
3807 3833
        );
3808 3834
    } else {
3809 3835
        group->nsyms = 0;
3810 3836
        wl_event_source_timer_update(group->key_repeat_source, 0);
3811 3837
    }
3812 -
    if (handled)
3838 +
    if (!key_event_should_forward(group, event->keycode, event->state, handled))
3813 3839
        return;
3814 3840
3815 3841
    wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard);
3816 3842
    /* Forward unhandled keys to the focused application. */
3817 3843
    wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, event->state);
test/unit.c +10 -0
942 942
    create_pointer_constraint(nullptr, &constraint);
943 943
944 944
    for (i = 0; i < LENGTH(pointer_constraint_used); i++)
945 945
946 946
        if (pointer_constraint_used[i]) {
947 +
947 948
            wrapper = &pointer_constraint_items[i];
948 949
            break;
949 950
        }
950 951
    assert(wrapper && wrapper->constraint == &constraint);
951 952
    destroy_pointer_constraint(&wrapper->destroy, nullptr);
964 965
    assert(client.bw == 0);
965 966
    maximize_notify(&client.maximize, nullptr);
966 967
    create_decoration(nullptr, &decoration);
967 968
    urgent(nullptr, &(struct wlr_xdg_activation_v1_request_activate_event){});
968 969
}
970 +
/* Verify a consumed shortcut press also consumes its release. */
971 +
static void test_shortcut_release(void) {
972 +
    keyboard_group_t group = {};
973 +
974 +
    assert(!key_event_should_forward(&group, KEY_P, WL_KEYBOARD_KEY_STATE_PRESSED, true));
975 +
    assert(!key_event_should_forward(&group, KEY_P, WL_KEYBOARD_KEY_STATE_RELEASED, false));
976 +
    assert(key_event_should_forward(&group, KEY_A, WL_KEYBOARD_KEY_STATE_RELEASED, false));
977 +
}
969 978
970 979
/* Verify modifier handling, repeatable bindings, and key-repeat dispatch. */
971 980
static void test_keybindings_and_repeat(void) {
972 981
    struct wlr_seat           fake_seat      = {};
973 982
    struct wlr_keyboard_group keyboard_group = {};
1027 1036
        { "small-helpers", test_small_helpers },
1028 1037
        { "pointer-grab-button", test_pointer_grab_button },
1029 1038
        { "client-accessors", test_client_accessors },
1030 1039
        { "autostart", test_autostart_execution },
1031 1040
        { "pointer-gestures", test_pointer_gestures },
1041 +
        { "shortcut-release", test_shortcut_release },
1032 1042
        { "input-method", test_input_method_lifecycle },
1033 1043
        { "input-early-paths", test_input_early_paths },
1034 1044
        { "command-line", test_command_line_errors },
1035 1045
        { "pointer-and-decoration", test_pointer_and_decoration_lifecycle },
1036 1046
        { "keybindings-and-repeat", test_keybindings_and_repeat },