diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 9236567e..86186e7f 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -105,8 +105,17 @@ Actions are used in menus and keyboard/mouse bindings. ** Re-load configuration and theme files. -** - Show menu. Valid menu names are "root-menu" and "client-menu". +** + Show a menu. + + *menu* The name of the menu to show. The menus "root-menu" and + "client-menu" are guaranteed to exist, but others may be defined + explicitly. See labwc-menu(5) for more information. + + *atCursor* [yes|no] When opening a menu, open the menu at the location + of the mouse cursor. When set to no, the menu will appear at the + upper-left corner of the window associated with the action. Default is + yes. ** Toggle decorations of focused window. diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h new file mode 100644 index 00000000..553fb32e --- /dev/null +++ b/include/config/default-bindings.h @@ -0,0 +1,447 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_DEFAULT_BINDINGS_H +#define LABWC_DEFAULT_BINDINGS_H + +static struct key_combos { + const char *binding, *action; + struct { + const char *name, *value; + } attributes[2]; +} key_combos[] = { { + .binding = "A-Tab", + .action = "NextWindow", + }, { + .binding = "W-Return", + .action = "Execute", + .attributes[0] = { + .name = "command", + .value = "alacritty", + }, + }, { + .binding = "A-F3", + .action = "Execute", + .attributes[0] = { + .name = "command", + .value = "bemenu-run", + }, + }, { + .binding = "A-F4", + .action = "Close", + }, { + .binding = "W-a", + .action = "ToggleMaximize", + }, { + .binding = "A-Left", + .action = "MoveToEdge", + .attributes[0] = { + .name = "direction", + .value = "left", + }, + }, { + .binding = "A-Right", + .action = "MoveToEdge", + .attributes[0] = { + .name = "direction", + .value = "right", + }, + }, { + .binding = "A-Up", + .action = "MoveToEdge", + .attributes[0] = { + .name = "direction", + .value = "up", + }, + }, { + .binding = "A-Down", + .action = "MoveToEdge", + .attributes[0] = { + .name = "direction", + .value = "down", + }, + }, { + .binding = "W-Left", + .action = "SnapToEdge", + .attributes[0] = { + .name = "direction", + .value = "left", + }, + }, { + .binding = "W-Right", + .action = "SnapToEdge", + .attributes[0] = { + .name = "direction", + .value = "right", + }, + }, { + .binding = "W-Up", + .action = "SnapToEdge", + .attributes[0] = { + .name = "direction", + .value = "up", + }, + }, { + .binding = "W-Down", + .action = "SnapToEdge", + .attributes[0] = { + .name = "direction", + .value = "down", + }, + }, { + .binding = "A-Space", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "client-menu" + }, + .attributes[1] = { + .name = "atCursor", + .value = "no", + }, + }, { + .binding = "XF86_AudioLowerVolume", + .action = "Execute", + .attributes[0] = { + .name = "command", + .value = "amixer sset Master 5%-", + }, + }, { + .binding = "XF86_AudioRaiseVolume", + .action = "Execute", + .attributes[0] = { + .name = "command", + .value = "amixer sset Master 5%+", + }, + }, { + .binding = "XF86_AudioMute", + .action = "Execute", + .attributes[0] = { + .name = "command", + .value = "amixer sset Master toggle", + }, + }, { + .binding = "XF86_MonBrightnessUp", + .action = "Execute", + .attributes[0] = { + .name = "command", + .value = "brightnessctl set +10%", + }, + }, { + .binding = "XF86_MonBrightnessDown", + .action = "Execute", + .attributes[0] = { + .name = "command", + .value = "brightnessctl set 10%-", + }, + }, { + .binding = NULL, + }, +}; + +/* + * `struct mouse_combo` variable description and examples: + * + * | Variable | Description | Examples + * |------------|----------------------------|---------------------------- + * | context | context name | Maximize, Root + * | button | mousebind button/direction | Left, Up + * | event | mousebind action | Click, Scroll + * | action | action name | ToggleMaximize, GoToDesktop + * |============|============================|============================ + * | Attributes | | + * |------------|----------------------------|---------------------------- + * | name | action attribute name | to + * | value | action attribute value | left + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +static struct mouse_combos { + const char *context, *button, *event, *action; + struct { + const char *name, *value; + } attributes[2]; +} mouse_combos[] = { { + .context = "Left", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "Top", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "Bottom", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "Right", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "TLCorner", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "TRCorner", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "BRCorner", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "BLCorner", + .button = "Left", + .event = "Drag", + .action = "Resize", + }, { + .context = "Frame", + .button = "A-Left", + .event = "Press", + .action = "Focus", + }, { + .context = "Frame", + .button = "A-Left", + .event = "Press", + .action = "Raise", + }, { + .context = "Frame", + .button = "A-Left", + .event = "Drag", + .action = "Move", + }, { + .context = "Frame", + .button = "A-Right", + .event = "Press", + .action = "Focus", + }, { + .context = "Frame", + .button = "A-Right", + .event = "Press", + .action = "Raise", + }, { + .context = "Frame", + .button = "A-Right", + .event = "Drag", + .action = "Resize", + }, { + .context = "Titlebar", + .button = "Left", + .event = "Press", + .action = "Focus", + }, { + .context = "Titlebar", + .button = "Left", + .event = "Press", + .action = "Raise", + }, { + .context = "Titlebar", + .button = "Up", + .event = "Scroll", + .action = "Unfocus", + }, { + .context = "Titlebar", + .button = "Up", + .event = "Scroll", + .action = "Shade", + }, { + .context = "Titlebar", + .button = "Down", + .event = "Scroll", + .action = "Unshade", + }, { + .context = "Titlebar", + .button = "Down", + .event = "Scroll", + .action = "Focus", + }, { + .context = "Title", + .button = "Left", + .event = "Drag", + .action = "Move", + }, { + .context = "Title", + .button = "Left", + .event = "DoubleClick", + .action = "ToggleMaximize", + }, { + .context = "TitleBar", + .button = "Right", + .event = "Click", + .action = "Focus", + }, { + .context = "TitleBar", + .button = "Right", + .event = "Click", + .action = "Raise", + }, { + .context = "Title", + .button = "Right", + .event = "Click", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "client-menu", + }, + .attributes[1] = { + .name = "atCursor", + .value = "yes", + }, + }, { + .context = "Close", + .button = "Left", + .event = "Click", + .action = "Close", + }, { + .context = "Iconify", + .button = "Left", + .event = "Click", + .action = "Iconify", + }, { + .context = "Maximize", + .button = "Left", + .event = "Click", + .action = "ToggleMaximize", + }, { + .context = "Maximize", + .button = "Right", + .event = "Click", + .action = "ToggleMaximize", + .attributes[0] = { + .name = "direction", + .value = "horizontal", + }, + }, { + .context = "Maximize", + .button = "Middle", + .event = "Click", + .action = "ToggleMaximize", + .attributes[0] = { + .name = "direction", + .value = "vertical", + }, + }, { + .context = "WindowMenu", + .button = "Left", + .event = "Click", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "client-menu", + }, + .attributes[1] = { + .name = "atCursor", + .value = "no", + }, + }, { + .context = "WindowMenu", + .button = "Right", + .event = "Click", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "client-menu", + }, + .attributes[1] = { + .name = "atCursor", + .value = "no", + }, + }, { + .context = "Root", + .button = "Left", + .event = "Press", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "root-menu", + }, + }, { + .context = "Root", + .button = "Right", + .event = "Press", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "root-menu", + }, + }, { + .context = "Root", + .button = "Middle", + .event = "Press", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "root-menu", + }, + }, { + .context = "Root", + .button = "Up", + .event = "Scroll", + .action = "GoToDesktop", + .attributes[0] = { + .name = "to", + .value = "left", + }, + }, { + .context = "Root", + .button = "Down", + .event = "Scroll", + .action = "GoToDesktop", + .attributes[0] = { + .name = "to", + .value = "right", + }, + }, { + .context = "Client", + .button = "Left", + .event = "Press", + .action = "Focus", + }, { + .context = "Client", + .button = "Left", + .event = "Press", + .action = "Raise", + }, { + .context = "Client", + .button = "Right", + .event = "Press", + .action = "Focus", + }, { + .context = "Client", + .button = "Right", + .event = "Press", + .action = "Raise", + }, { + .context = "Client", + .button = "Middle", + .event = "Press", + .action = "Focus", + }, { + .context = "Client", + .button = "Middle", + .event = "Press", + .action = "Raise", + }, { + .context = NULL, + }, +}; + +#endif /* LABWC_DEFAULT_BINDINGS_H */ diff --git a/src/action.c b/src/action.c index 6c026733..3723e42b 100644 --- a/src/action.c +++ b/src/action.c @@ -321,6 +321,10 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char action_arg_add_str(action, argument, content); goto cleanup; } + if (!strcasecmp(argument, "atCursor")) { + action_arg_add_bool(action, argument, parse_bool(content, true)); + goto cleanup; + } break; case ACTION_TYPE_TOGGLE_MAXIMIZE: case ACTION_TYPE_MAXIMIZE: @@ -571,7 +575,8 @@ action_list_free(struct wl_list *action_list) } static void -show_menu(struct server *server, struct view *view, const char *menu_name) +show_menu(struct server *server, struct view *view, + const char *menu_name, bool at_cursor) { if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH && server->input_mode != LAB_INPUT_STATE_MENU) { @@ -579,34 +584,25 @@ show_menu(struct server *server, struct view *view, const char *menu_name) return; } - bool force_menu_top_left = false; struct menu *menu = menu_get_by_id(server, menu_name); if (!menu) { return; } - if (!strcasecmp(menu_name, "client-menu")) { - if (!view) { - return; - } - enum ssd_part_type type = ssd_at(view->ssd, server->scene, - server->seat.cursor->x, server->seat.cursor->y); - if (type == LAB_SSD_BUTTON_WINDOW_MENU) { - force_menu_top_left = true; - } else if (ssd_part_contains(LAB_SSD_PART_TITLEBAR, type)) { - force_menu_top_left = false; - } else { - force_menu_top_left = true; - } + + int x = server->seat.cursor->x; + int y = server->seat.cursor->y; + + /* The client menu needs an active client */ + if (!view && strcasecmp(menu_name, "client-menu") == 0) { + return; } - int x, y; - if (force_menu_top_left) { + /* Place menu in the view corner if desired (and menu is not root-menu) */ + if (!at_cursor && view) { x = view->current.x; y = view->current.y; - } else { - x = server->seat.cursor->x; - y = server->seat.cursor->y; } + /* Replaced by next show_menu() or cleaned on view_destroy() */ menu->triggered_by_view = view; menu_open(menu, x, y); @@ -764,7 +760,9 @@ actions_run(struct view *activator, struct server *server, kill(getpid(), SIGHUP); break; case ACTION_TYPE_SHOW_MENU: - show_menu(server, view, action_get_str(action, "menu", NULL)); + show_menu(server, view, + action_get_str(action, "menu", NULL), + action_get_bool(action, "atCursor", true)); break; case ACTION_TYPE_TOGGLE_MAXIMIZE: if (view) { diff --git a/src/config/rcxml.c b/src/config/rcxml.c index e672aebd..fd5b41e8 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -21,6 +21,7 @@ #include "common/nodename.h" #include "common/parse-bool.h" #include "common/string-helpers.h" +#include "config/default-bindings.h" #include "config/keybind.h" #include "config/libinput.h" #include "config/mousebind.h" @@ -1165,137 +1166,41 @@ rcxml_init(void) rc.workspace_config.min_nr_workspaces = 1; } -static struct { - const char *binding, *action, *attribute, *value; -} key_combos[] = { - { "A-Tab", "NextWindow", NULL, NULL }, - { "W-Return", "Execute", "command", "alacritty" }, - { "A-F3", "Execute", "command", "bemenu-run" }, - { "A-F4", "Close", NULL, NULL }, - { "W-a", "ToggleMaximize", NULL, NULL }, - { "A-Left", "MoveToEdge", "direction", "left" }, - { "A-Right", "MoveToEdge", "direction", "right" }, - { "A-Up", "MoveToEdge", "direction", "up" }, - { "A-Down", "MoveToEdge", "direction", "down" }, - { "W-Left", "SnapToEdge", "direction", "left" }, - { "W-Right", "SnapToEdge", "direction", "right" }, - { "W-Up", "SnapToEdge", "direction", "up" }, - { "W-Down", "SnapToEdge", "direction", "down" }, - { "A-Space", "ShowMenu", "menu", "client-menu"}, - { "XF86_AudioLowerVolume", "Execute", "command", "amixer sset Master 5%-" }, - { "XF86_AudioRaiseVolume", "Execute", "command", "amixer sset Master 5%+" }, - { "XF86_AudioMute", "Execute", "command", "amixer sset Master toggle" }, - { "XF86_MonBrightnessUp", "Execute", "command", "brightnessctl set +10%" }, - { "XF86_MonBrightnessDown", "Execute", "command", "brightnessctl set 10%-" }, - { NULL, NULL, NULL, NULL }, -}; - static void load_default_key_bindings(void) { struct keybind *k; struct action *action; for (int i = 0; key_combos[i].binding; i++) { - k = keybind_create(key_combos[i].binding); + struct key_combos *current = &key_combos[i]; + k = keybind_create(current->binding); if (!k) { continue; } - action = action_create(key_combos[i].action); + action = action_create(current->action); wl_list_append(&k->actions, &action->link); - if (key_combos[i].attribute && key_combos[i].value) { + for (size_t j = 0; j < ARRAY_SIZE(current->attributes); j++) { + if (!current->attributes[j].name + || !current->attributes[j].value) { + break; + } action_arg_from_xml_node(action, - key_combos[i].attribute, key_combos[i].value); + current->attributes[j].name, + current->attributes[j].value); } } } -/* - * `struct mouse_combo` variable description and examples: - * - * | Variable | Description | Examples - * |-----------|----------------------------|--------------------- - * | context | context name | Maximize, Root - * | button | mousebind button/direction | Left, Up - * | event | mousebind action | Click, Scroll - * | action | action name | ToggleMaximize, GoToDesktop - * | attribute | action attribute | to - * | value | action attribute value | left - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ -static struct mouse_combos { - const char *context, *button, *event, *action, *attribute, *value; -} mouse_combos[] = { - { "Left", "Left", "Drag", "Resize", NULL, NULL}, - { "Top", "Left", "Drag", "Resize", NULL, NULL}, - { "Bottom", "Left", "Drag", "Resize", NULL, NULL}, - { "Right", "Left", "Drag", "Resize", NULL, NULL}, - { "TLCorner", "Left", "Drag", "Resize", NULL, NULL}, - { "TRCorner", "Left", "Drag", "Resize", NULL, NULL}, - { "BRCorner", "Left", "Drag", "Resize", NULL, NULL}, - { "BLCorner", "Left", "Drag", "Resize", NULL, NULL}, - { "Frame", "A-Left", "Press", "Focus", NULL, NULL}, - { "Frame", "A-Left", "Press", "Raise", NULL, NULL}, - { "Frame", "A-Left", "Drag", "Move", NULL, NULL}, - { "Frame", "A-Right", "Press", "Focus", NULL, NULL}, - { "Frame", "A-Right", "Press", "Raise", NULL, NULL}, - { "Frame", "A-Right", "Drag", "Resize", NULL, NULL}, - { "Titlebar", "Left", "Press", "Focus", NULL, NULL}, - { "Titlebar", "Left", "Press", "Raise", NULL, NULL}, - { "Titlebar", "Up", "Scroll", "Unfocus", NULL, NULL}, - { "Titlebar", "Up", "Scroll", "Shade", NULL, NULL}, - { "Titlebar", "Down", "Scroll", "Unshade", NULL, NULL}, - { "Titlebar", "Down", "Scroll", "Focus", NULL, NULL}, - { "Title", "Left", "Drag", "Move", NULL, NULL }, - { "Title", "Left", "DoubleClick", "ToggleMaximize", NULL, NULL }, - { "TitleBar", "Right", "Click", "Focus", NULL, NULL}, - { "TitleBar", "Right", "Click", "Raise", NULL, NULL}, - { "Title", "Right", "Click", "ShowMenu", "menu", "client-menu"}, - { "Close", "Left", "Click", "Close", NULL, NULL }, - { "Iconify", "Left", "Click", "Iconify", NULL, NULL}, - { "Maximize", "Left", "Click", "ToggleMaximize", NULL, NULL}, - { "Maximize", "Right", "Click", "ToggleMaximize", "direction", "horizontal"}, - { "Maximize", "Middle", "Click", "ToggleMaximize", "direction", "vertical"}, - { "WindowMenu", "Left", "Click", "ShowMenu", "menu", "client-menu"}, - { "WindowMenu", "Right", "Click", "ShowMenu", "menu", "client-menu"}, - { "Root", "Left", "Press", "ShowMenu", "menu", "root-menu"}, - { "Root", "Right", "Press", "ShowMenu", "menu", "root-menu"}, - { "Root", "Middle", "Press", "ShowMenu", "menu", "root-menu"}, - { "Root", "Up", "Scroll", "GoToDesktop", "to", "left"}, - { "Root", "Down", "Scroll", "GoToDesktop", "to", "right"}, - { "Client", "Left", "Press", "Focus", NULL, NULL}, - { "Client", "Left", "Press", "Raise", NULL, NULL}, - { "Client", "Right", "Press", "Focus", NULL, NULL}, - { "Client", "Right", "Press", "Raise", NULL, NULL}, - { "Client", "Middle", "Press", "Focus", NULL, NULL}, - { "Client", "Middle", "Press", "Raise", NULL, NULL}, - { NULL, NULL, NULL, NULL, NULL, NULL }, -}; - static void load_default_mouse_bindings(void) { uint32_t count = 0; struct mousebind *m; struct action *action; - struct mouse_combos *current; for (int i = 0; mouse_combos[i].context; i++) { - current = &mouse_combos[i]; + struct mouse_combos *current = &mouse_combos[i]; if (i == 0 || strcmp(current->context, mouse_combos[i - 1].context) || strcmp(current->button, mouse_combos[i - 1].button) @@ -1316,14 +1221,14 @@ load_default_mouse_bindings(void) action = action_create(current->action); wl_list_append(&m->actions, &action->link); - /* - * Only one attribute/value (of string type) is required for the - * built-in binds. If more are required in the future, a - * slightly more sophisticated approach will be needed. - */ - if (current->attribute && current->value) { + for (size_t j = 0; j < ARRAY_SIZE(current->attributes); j++) { + if (!current->attributes[j].name + || !current->attributes[j].value) { + break; + } action_arg_from_xml_node(action, - current->attribute, current->value); + current->attributes[j].name, + current->attributes[j].value); } } wlr_log(WLR_DEBUG, "Loaded %u merged mousebinds", count);