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);