2021-09-24 21:45:48 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2022-06-21 18:48:05 +02:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2022-06-10 19:42:34 +02:00
|
|
|
#include <assert.h>
|
2022-06-21 18:48:05 +02:00
|
|
|
#include <signal.h>
|
2022-06-10 19:42:34 +02:00
|
|
|
#include <string.h>
|
2020-09-25 19:42:40 +01:00
|
|
|
#include <strings.h>
|
2022-06-21 18:48:05 +02:00
|
|
|
#include <unistd.h>
|
2021-07-23 21:15:55 +01:00
|
|
|
#include <wlr/util/log.h>
|
2022-11-22 11:59:54 -05:00
|
|
|
#include "action.h"
|
2023-10-20 18:34:14 -04:00
|
|
|
#include "common/macros.h"
|
2022-10-05 08:43:56 +02:00
|
|
|
#include "common/list.h"
|
2022-09-16 18:41:02 -04:00
|
|
|
#include "common/mem.h"
|
2023-03-26 22:34:44 +01:00
|
|
|
#include "common/parse-bool.h"
|
2020-09-28 20:41:41 +01:00
|
|
|
#include "common/spawn.h"
|
2023-05-11 23:36:51 +02:00
|
|
|
#include "common/string-helpers.h"
|
2022-02-12 19:43:32 +00:00
|
|
|
#include "debug.h"
|
2020-09-25 20:05:20 +01:00
|
|
|
#include "labwc.h"
|
2024-05-15 23:07:23 +01:00
|
|
|
#include "magnifier.h"
|
2020-10-31 15:27:22 +00:00
|
|
|
#include "menu/menu.h"
|
2024-04-10 17:39:31 -05:00
|
|
|
#include "osd.h"
|
2024-03-07 00:22:51 +01:00
|
|
|
#include "output-virtual.h"
|
2022-07-06 08:06:48 +02:00
|
|
|
#include "regions.h"
|
2022-01-26 02:54:03 +01:00
|
|
|
#include "ssd.h"
|
2022-11-21 10:10:39 -05:00
|
|
|
#include "view.h"
|
2022-06-15 01:38:22 +02:00
|
|
|
#include "workspaces.h"
|
2024-06-13 11:04:30 +01:00
|
|
|
#include "input/keyboard.h"
|
2022-01-05 09:11:24 +01:00
|
|
|
|
2022-11-22 11:59:54 -05:00
|
|
|
enum action_arg_type {
|
|
|
|
|
LAB_ACTION_ARG_STR = 0,
|
2023-03-26 10:57:53 +01:00
|
|
|
LAB_ACTION_ARG_BOOL,
|
2023-06-25 20:20:52 +01:00
|
|
|
LAB_ACTION_ARG_INT,
|
2023-08-28 19:14:04 +03:00
|
|
|
LAB_ACTION_ARG_QUERY_LIST,
|
|
|
|
|
LAB_ACTION_ARG_ACTION_LIST,
|
2022-11-22 11:59:54 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct action_arg {
|
|
|
|
|
struct wl_list link; /* struct action.args */
|
|
|
|
|
|
|
|
|
|
char *key; /* May be NULL if there is just one arg */
|
|
|
|
|
enum action_arg_type type;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct action_arg_str {
|
|
|
|
|
struct action_arg base;
|
|
|
|
|
char *value;
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-26 10:57:53 +01:00
|
|
|
struct action_arg_bool {
|
|
|
|
|
struct action_arg base;
|
|
|
|
|
bool value;
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-25 20:20:52 +01:00
|
|
|
struct action_arg_int {
|
|
|
|
|
struct action_arg base;
|
|
|
|
|
int value;
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-28 19:14:04 +03:00
|
|
|
struct action_arg_list {
|
|
|
|
|
struct action_arg base;
|
|
|
|
|
struct wl_list value;
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-05 09:11:24 +01:00
|
|
|
enum action_type {
|
2022-09-06 13:22:43 -04:00
|
|
|
ACTION_TYPE_INVALID = 0,
|
|
|
|
|
ACTION_TYPE_NONE,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_CLOSE,
|
2022-03-15 15:47:46 +00:00
|
|
|
ACTION_TYPE_KILL,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_DEBUG,
|
|
|
|
|
ACTION_TYPE_EXECUTE,
|
|
|
|
|
ACTION_TYPE_EXIT,
|
|
|
|
|
ACTION_TYPE_MOVE_TO_EDGE,
|
2024-09-21 15:53:05 +02:00
|
|
|
ACTION_TYPE_TOGGLE_SNAP_TO_EDGE,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_SNAP_TO_EDGE,
|
2023-08-05 23:58:40 +02:00
|
|
|
ACTION_TYPE_GROW_TO_EDGE,
|
|
|
|
|
ACTION_TYPE_SHRINK_TO_EDGE,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_NEXT_WINDOW,
|
|
|
|
|
ACTION_TYPE_PREVIOUS_WINDOW,
|
|
|
|
|
ACTION_TYPE_RECONFIGURE,
|
|
|
|
|
ACTION_TYPE_SHOW_MENU,
|
|
|
|
|
ACTION_TYPE_TOGGLE_MAXIMIZE,
|
2023-05-04 21:29:14 +01:00
|
|
|
ACTION_TYPE_MAXIMIZE,
|
2024-05-19 18:10:44 +01:00
|
|
|
ACTION_TYPE_UNMAXIMIZE,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_TOGGLE_FULLSCREEN,
|
2024-04-20 06:29:51 +02:00
|
|
|
ACTION_TYPE_SET_DECORATIONS,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_TOGGLE_DECORATIONS,
|
2022-04-09 01:16:09 +02:00
|
|
|
ACTION_TYPE_TOGGLE_ALWAYS_ON_TOP,
|
2023-05-11 22:26:41 +01:00
|
|
|
ACTION_TYPE_TOGGLE_ALWAYS_ON_BOTTOM,
|
2023-11-25 17:54:36 -06:00
|
|
|
ACTION_TYPE_TOGGLE_OMNIPRESENT,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_FOCUS,
|
2023-11-07 18:53:27 +00:00
|
|
|
ACTION_TYPE_UNFOCUS,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_ICONIFY,
|
|
|
|
|
ACTION_TYPE_MOVE,
|
|
|
|
|
ACTION_TYPE_RAISE,
|
2023-03-20 03:31:49 +01:00
|
|
|
ACTION_TYPE_LOWER,
|
2022-01-05 09:11:24 +01:00
|
|
|
ACTION_TYPE_RESIZE,
|
2023-06-27 21:20:04 +03:00
|
|
|
ACTION_TYPE_RESIZE_RELATIVE,
|
2023-06-06 20:33:53 +01:00
|
|
|
ACTION_TYPE_MOVETO,
|
2023-12-01 17:03:58 +00:00
|
|
|
ACTION_TYPE_RESIZETO,
|
2023-10-14 14:57:44 +02:00
|
|
|
ACTION_TYPE_MOVETO_CURSOR,
|
2023-06-26 19:04:33 +03:00
|
|
|
ACTION_TYPE_MOVE_RELATIVE,
|
2022-06-15 01:38:22 +02:00
|
|
|
ACTION_TYPE_SEND_TO_DESKTOP,
|
2023-05-15 18:50:13 +02:00
|
|
|
ACTION_TYPE_GO_TO_DESKTOP,
|
2024-09-21 15:53:05 +02:00
|
|
|
ACTION_TYPE_TOGGLE_SNAP_TO_REGION,
|
2023-03-03 18:16:46 +01:00
|
|
|
ACTION_TYPE_SNAP_TO_REGION,
|
2024-09-21 15:54:30 +02:00
|
|
|
ACTION_TYPE_UNSNAP,
|
2023-03-03 18:16:46 +01:00
|
|
|
ACTION_TYPE_TOGGLE_KEYBINDS,
|
2023-03-05 17:16:23 +01:00
|
|
|
ACTION_TYPE_FOCUS_OUTPUT,
|
2024-01-21 23:45:47 +01:00
|
|
|
ACTION_TYPE_MOVE_TO_OUTPUT,
|
2024-01-22 22:56:18 +01:00
|
|
|
ACTION_TYPE_FIT_TO_OUTPUT,
|
2023-08-28 19:14:04 +03:00
|
|
|
ACTION_TYPE_IF,
|
|
|
|
|
ACTION_TYPE_FOR_EACH,
|
2023-12-09 12:01:11 +03:00
|
|
|
ACTION_TYPE_VIRTUAL_OUTPUT_ADD,
|
|
|
|
|
ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE,
|
2023-12-28 16:47:21 -05:00
|
|
|
ACTION_TYPE_AUTO_PLACE,
|
2024-01-08 22:58:58 +02:00
|
|
|
ACTION_TYPE_TOGGLE_TEARING,
|
2023-08-08 03:39:35 +02:00
|
|
|
ACTION_TYPE_SHADE,
|
|
|
|
|
ACTION_TYPE_UNSHADE,
|
|
|
|
|
ACTION_TYPE_TOGGLE_SHADE,
|
2024-08-18 16:09:39 +02:00
|
|
|
ACTION_TYPE_ENABLE_TABLET_MOUSE_EMULATION,
|
|
|
|
|
ACTION_TYPE_DISABLE_TABLET_MOUSE_EMULATION,
|
2024-06-16 08:47:48 +02:00
|
|
|
ACTION_TYPE_TOGGLE_TABLET_MOUSE_EMULATION,
|
2024-05-15 23:07:23 +01:00
|
|
|
ACTION_TYPE_TOGGLE_MAGNIFY,
|
|
|
|
|
ACTION_TYPE_ZOOM_IN,
|
2024-12-22 14:48:47 +02:00
|
|
|
ACTION_TYPE_ZOOM_OUT,
|
|
|
|
|
ACTION_TYPE_WARP_CURSOR,
|
2022-01-05 09:11:24 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const char *action_names[] = {
|
2022-09-06 13:22:43 -04:00
|
|
|
"INVALID",
|
|
|
|
|
"None",
|
2022-01-05 09:11:24 +01:00
|
|
|
"Close",
|
2022-03-15 15:47:46 +00:00
|
|
|
"Kill",
|
2022-01-05 09:11:24 +01:00
|
|
|
"Debug",
|
|
|
|
|
"Execute",
|
|
|
|
|
"Exit",
|
|
|
|
|
"MoveToEdge",
|
2024-09-21 15:53:05 +02:00
|
|
|
"ToggleSnapToEdge",
|
2022-01-05 09:11:24 +01:00
|
|
|
"SnapToEdge",
|
2023-08-05 23:58:40 +02:00
|
|
|
"GrowToEdge",
|
|
|
|
|
"ShrinkToEdge",
|
2022-01-05 09:11:24 +01:00
|
|
|
"NextWindow",
|
|
|
|
|
"PreviousWindow",
|
|
|
|
|
"Reconfigure",
|
|
|
|
|
"ShowMenu",
|
|
|
|
|
"ToggleMaximize",
|
2023-05-04 21:29:14 +01:00
|
|
|
"Maximize",
|
2024-05-19 18:10:44 +01:00
|
|
|
"UnMaximize",
|
2022-01-05 09:11:24 +01:00
|
|
|
"ToggleFullscreen",
|
2024-04-20 06:29:51 +02:00
|
|
|
"SetDecorations",
|
2022-01-05 09:11:24 +01:00
|
|
|
"ToggleDecorations",
|
2022-04-09 01:16:09 +02:00
|
|
|
"ToggleAlwaysOnTop",
|
2023-05-11 22:26:41 +01:00
|
|
|
"ToggleAlwaysOnBottom",
|
2023-11-25 17:54:36 -06:00
|
|
|
"ToggleOmnipresent",
|
2022-01-05 09:11:24 +01:00
|
|
|
"Focus",
|
2023-11-07 18:53:27 +00:00
|
|
|
"Unfocus",
|
2022-01-05 09:11:24 +01:00
|
|
|
"Iconify",
|
|
|
|
|
"Move",
|
|
|
|
|
"Raise",
|
2023-03-20 03:31:49 +01:00
|
|
|
"Lower",
|
2022-01-05 09:11:24 +01:00
|
|
|
"Resize",
|
2023-06-27 21:20:04 +03:00
|
|
|
"ResizeRelative",
|
2023-06-06 20:33:53 +01:00
|
|
|
"MoveTo",
|
2023-12-01 17:03:58 +00:00
|
|
|
"ResizeTo",
|
2023-10-14 14:57:44 +02:00
|
|
|
"MoveToCursor",
|
2023-06-26 19:04:33 +03:00
|
|
|
"MoveRelative",
|
2022-06-15 01:38:22 +02:00
|
|
|
"SendToDesktop",
|
2023-05-15 18:50:13 +02:00
|
|
|
"GoToDesktop",
|
2024-09-21 15:53:05 +02:00
|
|
|
"ToggleSnapToRegion",
|
2022-07-06 08:06:48 +02:00
|
|
|
"SnapToRegion",
|
2024-09-21 15:54:30 +02:00
|
|
|
"UnSnap",
|
2023-03-03 18:16:46 +01:00
|
|
|
"ToggleKeybinds",
|
2023-03-05 17:16:23 +01:00
|
|
|
"FocusOutput",
|
2024-01-21 23:45:47 +01:00
|
|
|
"MoveToOutput",
|
2024-01-22 22:56:18 +01:00
|
|
|
"FitToOutput",
|
2023-08-28 19:14:04 +03:00
|
|
|
"If",
|
|
|
|
|
"ForEach",
|
2023-12-09 12:01:11 +03:00
|
|
|
"VirtualOutputAdd",
|
|
|
|
|
"VirtualOutputRemove",
|
2023-12-28 16:47:21 -05:00
|
|
|
"AutoPlace",
|
2024-01-08 22:58:58 +02:00
|
|
|
"ToggleTearing",
|
2023-08-08 03:39:35 +02:00
|
|
|
"Shade",
|
|
|
|
|
"Unshade",
|
|
|
|
|
"ToggleShade",
|
2024-08-18 16:09:39 +02:00
|
|
|
"EnableTabletMouseEmulation",
|
|
|
|
|
"DisableTabletMouseEmulation",
|
2024-06-16 08:47:48 +02:00
|
|
|
"ToggleTabletMouseEmulation",
|
2024-05-15 23:07:23 +01:00
|
|
|
"ToggleMagnify",
|
|
|
|
|
"ZoomIn",
|
|
|
|
|
"ZoomOut",
|
2024-12-22 14:48:47 +02:00
|
|
|
"WarpCursor",
|
2022-01-05 09:11:24 +01:00
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-26 22:01:17 +01:00
|
|
|
void
|
2023-05-12 14:33:12 +02:00
|
|
|
action_arg_add_str(struct action *action, const char *key, const char *value)
|
2023-03-26 22:01:17 +01:00
|
|
|
{
|
2023-05-12 14:33:12 +02:00
|
|
|
assert(action);
|
|
|
|
|
assert(key);
|
2023-03-26 22:01:17 +01:00
|
|
|
assert(value && "Tried to add NULL action string argument");
|
|
|
|
|
struct action_arg_str *arg = znew(*arg);
|
|
|
|
|
arg->base.type = LAB_ACTION_ARG_STR;
|
2023-05-12 14:33:12 +02:00
|
|
|
arg->base.key = xstrdup(key);
|
2023-03-26 22:01:17 +01:00
|
|
|
arg->value = xstrdup(value);
|
|
|
|
|
wl_list_append(&action->args, &arg->base.link);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2023-05-12 14:33:12 +02:00
|
|
|
action_arg_add_bool(struct action *action, const char *key, bool value)
|
2023-03-26 22:01:17 +01:00
|
|
|
{
|
2023-05-12 14:33:12 +02:00
|
|
|
assert(action);
|
|
|
|
|
assert(key);
|
2023-03-26 22:01:17 +01:00
|
|
|
struct action_arg_bool *arg = znew(*arg);
|
|
|
|
|
arg->base.type = LAB_ACTION_ARG_BOOL;
|
2023-05-12 14:33:12 +02:00
|
|
|
arg->base.key = xstrdup(key);
|
2023-03-26 22:01:17 +01:00
|
|
|
arg->value = value;
|
|
|
|
|
wl_list_append(&action->args, &arg->base.link);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-25 20:20:52 +01:00
|
|
|
static void
|
2023-05-12 14:33:12 +02:00
|
|
|
action_arg_add_int(struct action *action, const char *key, int value)
|
2023-06-25 20:20:52 +01:00
|
|
|
{
|
2023-05-12 14:33:12 +02:00
|
|
|
assert(action);
|
|
|
|
|
assert(key);
|
2023-06-25 20:20:52 +01:00
|
|
|
struct action_arg_int *arg = znew(*arg);
|
|
|
|
|
arg->base.type = LAB_ACTION_ARG_INT;
|
2023-05-12 14:33:12 +02:00
|
|
|
arg->base.key = xstrdup(key);
|
2023-06-25 20:20:52 +01:00
|
|
|
arg->value = value;
|
|
|
|
|
wl_list_append(&action->args, &arg->base.link);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 19:14:04 +03:00
|
|
|
static void
|
|
|
|
|
action_arg_add_list(struct action *action, const char *key, enum action_arg_type type)
|
|
|
|
|
{
|
|
|
|
|
assert(action);
|
|
|
|
|
assert(key);
|
|
|
|
|
struct action_arg_list *arg = znew(*arg);
|
|
|
|
|
arg->base.type = type;
|
|
|
|
|
arg->base.key = xstrdup(key);
|
|
|
|
|
wl_list_init(&arg->value);
|
|
|
|
|
wl_list_append(&action->args, &arg->base.link);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
action_arg_add_querylist(struct action *action, const char *key)
|
|
|
|
|
{
|
|
|
|
|
action_arg_add_list(action, key, LAB_ACTION_ARG_QUERY_LIST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
action_arg_add_actionlist(struct action *action, const char *key)
|
|
|
|
|
{
|
|
|
|
|
action_arg_add_list(action, key, LAB_ACTION_ARG_ACTION_LIST);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:21:49 +03:00
|
|
|
static void *
|
|
|
|
|
action_get_arg(struct action *action, const char *key, enum action_arg_type type)
|
|
|
|
|
{
|
|
|
|
|
assert(action);
|
|
|
|
|
assert(key);
|
|
|
|
|
struct action_arg *arg;
|
|
|
|
|
wl_list_for_each(arg, &action->args, link) {
|
|
|
|
|
if (!strcasecmp(key, arg->key) && arg->type == type) {
|
|
|
|
|
return arg;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
|
action_get_str(struct action *action, const char *key, const char *default_value)
|
|
|
|
|
{
|
|
|
|
|
struct action_arg_str *arg = action_get_arg(action, key, LAB_ACTION_ARG_STR);
|
|
|
|
|
return arg ? arg->value : default_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
action_get_bool(struct action *action, const char *key, bool default_value)
|
|
|
|
|
{
|
|
|
|
|
struct action_arg_bool *arg = action_get_arg(action, key, LAB_ACTION_ARG_BOOL);
|
|
|
|
|
return arg ? arg->value : default_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
action_get_int(struct action *action, const char *key, int default_value)
|
|
|
|
|
{
|
|
|
|
|
struct action_arg_int *arg = action_get_arg(action, key, LAB_ACTION_ARG_INT);
|
|
|
|
|
return arg ? arg->value : default_value;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 19:14:04 +03:00
|
|
|
struct wl_list *
|
|
|
|
|
action_get_querylist(struct action *action, const char *key)
|
|
|
|
|
{
|
|
|
|
|
struct action_arg_list *arg = action_get_arg(action, key, LAB_ACTION_ARG_QUERY_LIST);
|
|
|
|
|
return arg ? &arg->value : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wl_list *
|
|
|
|
|
action_get_actionlist(struct action *action, const char *key)
|
|
|
|
|
{
|
|
|
|
|
struct action_arg_list *arg = action_get_arg(action, key, LAB_ACTION_ARG_ACTION_LIST);
|
|
|
|
|
return arg ? &arg->value : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 15:28:25 +01:00
|
|
|
void
|
2023-08-06 10:26:04 +02:00
|
|
|
action_arg_from_xml_node(struct action *action, const char *nodename, const char *content)
|
2022-12-10 15:28:25 +01:00
|
|
|
{
|
|
|
|
|
assert(action);
|
2023-05-11 23:36:51 +02:00
|
|
|
|
|
|
|
|
char *argument = xstrdup(nodename);
|
|
|
|
|
string_truncate_at_pattern(argument, ".action");
|
|
|
|
|
|
|
|
|
|
switch (action->type) {
|
|
|
|
|
case ACTION_TYPE_EXECUTE:
|
2023-07-02 21:20:04 +01:00
|
|
|
/*
|
|
|
|
|
* <action name="Execute"> with an <execute> child is
|
|
|
|
|
* deprecated, but we support it anyway for backward
|
|
|
|
|
* compatibility with old openbox-menu generators
|
|
|
|
|
*/
|
|
|
|
|
if (!strcmp(argument, "command") || !strcmp(argument, "execute")) {
|
2023-05-11 23:36:51 +02:00
|
|
|
action_arg_add_str(action, "command", content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_MOVE_TO_EDGE:
|
2023-08-05 23:58:40 +02:00
|
|
|
if (!strcasecmp(argument, "snapWindows")) {
|
|
|
|
|
action_arg_add_bool(action, argument, parse_bool(content, true));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
/* Falls through */
|
2024-09-21 15:53:05 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_SNAP_TO_EDGE:
|
2023-05-11 23:36:51 +02:00
|
|
|
case ACTION_TYPE_SNAP_TO_EDGE:
|
2023-08-05 23:58:40 +02:00
|
|
|
case ACTION_TYPE_GROW_TO_EDGE:
|
|
|
|
|
case ACTION_TYPE_SHRINK_TO_EDGE:
|
2023-05-11 23:36:51 +02:00
|
|
|
if (!strcmp(argument, "direction")) {
|
2023-08-02 04:30:50 +02:00
|
|
|
enum view_edge edge = view_edge_parse(content);
|
2024-09-21 15:53:05 +02:00
|
|
|
bool allow_center = action->type == ACTION_TYPE_TOGGLE_SNAP_TO_EDGE
|
|
|
|
|
|| action->type == ACTION_TYPE_SNAP_TO_EDGE;
|
|
|
|
|
if ((edge == VIEW_EDGE_CENTER && !allow_center)
|
2023-08-02 04:30:50 +02:00
|
|
|
|| edge == VIEW_EDGE_INVALID) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
|
|
|
|
|
action_names[action->type], argument, content);
|
|
|
|
|
} else {
|
|
|
|
|
action_arg_add_int(action, argument, edge);
|
|
|
|
|
}
|
2023-05-11 23:36:51 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_SHOW_MENU:
|
|
|
|
|
if (!strcmp(argument, "menu")) {
|
|
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
2024-03-10 14:28:41 -04:00
|
|
|
if (!strcasecmp(argument, "atCursor")) {
|
|
|
|
|
action_arg_add_bool(action, argument, parse_bool(content, true));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
2024-09-05 07:52:23 -05:00
|
|
|
if (!strcasecmp(argument, "x.position")) {
|
|
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
if (!strcasecmp(argument, "y.position")) {
|
|
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
2023-05-11 23:36:51 +02:00
|
|
|
break;
|
view: implement separate horizontal/vertical maximize
This is a useful (if lesser-known) feature of at least a few popular X11
window managers, for example Openbox and XFWM4. Typically right-click on
the maximize button toggles horizontal maximize, while middle-click
toggles vertical maximize.
Support in labwc uses the same configuration syntax as Openbox, where the
Maximize/ToggleMaximize actions have an optional "direction" argument:
horizontal, vertical, or both (default). The default mouse bindings match
the XFWM4 defaults (not sure what Openbox has by default).
Most of the external protocols still assume "maximized" is a Boolean,
which is no longer true internally. For the sake of the outside world,
a view is only "maximized" if maximized in both directions.
Internally, I've taken the following approach:
- SSD code decorates the view as "maximized" (i.e. hiding borders) only
if maximized in both directions.
- Layout code (interactive move/resize, tiling, etc.) generally treats
the view as "maximized" (with the restrictions that entails) if
maximized in either direction. For example, moving a vertically-
maximized view first restores the natural geometry (this differs from
Openbox, which instead allows the view to move only horizontally.)
v2: use enum view_axis for view->maximized
v3:
- update docs
- allow resizing if partly maximized
- add TODOs & corrections noted by Consolatis
2023-10-26 00:38:29 -04:00
|
|
|
case ACTION_TYPE_TOGGLE_MAXIMIZE:
|
|
|
|
|
case ACTION_TYPE_MAXIMIZE:
|
2024-05-19 18:10:44 +01:00
|
|
|
case ACTION_TYPE_UNMAXIMIZE:
|
view: implement separate horizontal/vertical maximize
This is a useful (if lesser-known) feature of at least a few popular X11
window managers, for example Openbox and XFWM4. Typically right-click on
the maximize button toggles horizontal maximize, while middle-click
toggles vertical maximize.
Support in labwc uses the same configuration syntax as Openbox, where the
Maximize/ToggleMaximize actions have an optional "direction" argument:
horizontal, vertical, or both (default). The default mouse bindings match
the XFWM4 defaults (not sure what Openbox has by default).
Most of the external protocols still assume "maximized" is a Boolean,
which is no longer true internally. For the sake of the outside world,
a view is only "maximized" if maximized in both directions.
Internally, I've taken the following approach:
- SSD code decorates the view as "maximized" (i.e. hiding borders) only
if maximized in both directions.
- Layout code (interactive move/resize, tiling, etc.) generally treats
the view as "maximized" (with the restrictions that entails) if
maximized in either direction. For example, moving a vertically-
maximized view first restores the natural geometry (this differs from
Openbox, which instead allows the view to move only horizontally.)
v2: use enum view_axis for view->maximized
v3:
- update docs
- allow resizing if partly maximized
- add TODOs & corrections noted by Consolatis
2023-10-26 00:38:29 -04:00
|
|
|
if (!strcmp(argument, "direction")) {
|
|
|
|
|
enum view_axis axis = view_axis_parse(content);
|
2024-10-18 02:07:52 +03:00
|
|
|
if (axis == VIEW_AXIS_NONE || axis == VIEW_AXIS_INVALID) {
|
view: implement separate horizontal/vertical maximize
This is a useful (if lesser-known) feature of at least a few popular X11
window managers, for example Openbox and XFWM4. Typically right-click on
the maximize button toggles horizontal maximize, while middle-click
toggles vertical maximize.
Support in labwc uses the same configuration syntax as Openbox, where the
Maximize/ToggleMaximize actions have an optional "direction" argument:
horizontal, vertical, or both (default). The default mouse bindings match
the XFWM4 defaults (not sure what Openbox has by default).
Most of the external protocols still assume "maximized" is a Boolean,
which is no longer true internally. For the sake of the outside world,
a view is only "maximized" if maximized in both directions.
Internally, I've taken the following approach:
- SSD code decorates the view as "maximized" (i.e. hiding borders) only
if maximized in both directions.
- Layout code (interactive move/resize, tiling, etc.) generally treats
the view as "maximized" (with the restrictions that entails) if
maximized in either direction. For example, moving a vertically-
maximized view first restores the natural geometry (this differs from
Openbox, which instead allows the view to move only horizontally.)
v2: use enum view_axis for view->maximized
v3:
- update docs
- allow resizing if partly maximized
- add TODOs & corrections noted by Consolatis
2023-10-26 00:38:29 -04:00
|
|
|
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
|
|
|
|
|
action_names[action->type], argument, content);
|
|
|
|
|
} else {
|
|
|
|
|
action_arg_add_int(action, argument, axis);
|
|
|
|
|
}
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-04-20 06:29:51 +02:00
|
|
|
case ACTION_TYPE_SET_DECORATIONS:
|
|
|
|
|
if (!strcmp(argument, "decorations")) {
|
|
|
|
|
enum ssd_mode mode = ssd_mode_parse(content);
|
2024-10-18 02:07:52 +03:00
|
|
|
if (mode != LAB_SSD_MODE_INVALID) {
|
|
|
|
|
action_arg_add_int(action, argument, mode);
|
|
|
|
|
} else {
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
|
|
|
|
|
action_names[action->type], argument, content);
|
|
|
|
|
}
|
2024-04-20 06:29:51 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
if (!strcasecmp(argument, "forceSSD")) {
|
|
|
|
|
action_arg_add_bool(action, argument, parse_bool(content, false));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-06-27 21:20:04 +03:00
|
|
|
case ACTION_TYPE_RESIZE_RELATIVE:
|
|
|
|
|
if (!strcmp(argument, "left") || !strcmp(argument, "right") ||
|
|
|
|
|
!strcmp(argument, "top") || !strcmp(argument, "bottom")) {
|
|
|
|
|
action_arg_add_int(action, argument, atoi(content));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-06-06 20:33:53 +01:00
|
|
|
case ACTION_TYPE_MOVETO:
|
2023-06-26 19:04:33 +03:00
|
|
|
case ACTION_TYPE_MOVE_RELATIVE:
|
2023-06-06 20:33:53 +01:00
|
|
|
if (!strcmp(argument, "x") || !strcmp(argument, "y")) {
|
|
|
|
|
action_arg_add_int(action, argument, atoi(content));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-12-01 17:03:58 +00:00
|
|
|
case ACTION_TYPE_RESIZETO:
|
|
|
|
|
if (!strcmp(argument, "width") || !strcmp(argument, "height")) {
|
|
|
|
|
action_arg_add_int(action, argument, atoi(content));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-05-11 23:36:51 +02:00
|
|
|
case ACTION_TYPE_SEND_TO_DESKTOP:
|
|
|
|
|
if (!strcmp(argument, "follow")) {
|
|
|
|
|
action_arg_add_bool(action, argument, parse_bool(content, true));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
/* Falls through to GoToDesktop */
|
|
|
|
|
case ACTION_TYPE_GO_TO_DESKTOP:
|
|
|
|
|
if (!strcmp(argument, "to")) {
|
|
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
2023-05-14 20:44:59 +03:00
|
|
|
if (!strcmp(argument, "wrap")) {
|
|
|
|
|
action_arg_add_bool(action, argument, parse_bool(content, true));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
2023-05-11 23:36:51 +02:00
|
|
|
break;
|
2024-09-21 15:53:05 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_SNAP_TO_REGION:
|
2023-05-11 23:36:51 +02:00
|
|
|
case ACTION_TYPE_SNAP_TO_REGION:
|
|
|
|
|
if (!strcmp(argument, "region")) {
|
|
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
2023-05-15 18:50:13 +02:00
|
|
|
}
|
2023-05-11 23:36:51 +02:00
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_FOCUS_OUTPUT:
|
2024-01-21 23:45:47 +01:00
|
|
|
case ACTION_TYPE_MOVE_TO_OUTPUT:
|
2024-03-06 04:30:17 +01:00
|
|
|
if (!strcmp(argument, "output")) {
|
2024-01-21 23:45:47 +01:00
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
if (!strcmp(argument, "direction")) {
|
|
|
|
|
enum view_edge edge = view_edge_parse(content);
|
|
|
|
|
if (edge == VIEW_EDGE_CENTER) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
|
|
|
|
|
action_names[action->type], argument, content);
|
|
|
|
|
} else {
|
|
|
|
|
action_arg_add_int(action, argument, edge);
|
|
|
|
|
}
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
2024-03-02 15:42:05 +00:00
|
|
|
if (!strcmp(argument, "wrap")) {
|
|
|
|
|
action_arg_add_bool(action, argument, parse_bool(content, false));
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
2024-01-21 23:45:47 +01:00
|
|
|
break;
|
2023-12-09 12:01:11 +03:00
|
|
|
case ACTION_TYPE_VIRTUAL_OUTPUT_ADD:
|
|
|
|
|
case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE:
|
|
|
|
|
if (!strcmp(argument, "output_name")) {
|
|
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-05-07 09:46:05 -04:00
|
|
|
case ACTION_TYPE_AUTO_PLACE:
|
|
|
|
|
if (!strcmp(argument, "policy")) {
|
|
|
|
|
enum view_placement_policy policy =
|
|
|
|
|
view_placement_parse(content);
|
|
|
|
|
if (policy == LAB_PLACE_INVALID) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
|
|
|
|
|
action_names[action->type], argument, content);
|
|
|
|
|
} else {
|
|
|
|
|
action_arg_add_int(action, argument, policy);
|
|
|
|
|
}
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-12-22 14:48:47 +02:00
|
|
|
case ACTION_TYPE_WARP_CURSOR:
|
|
|
|
|
if (!strcmp(argument, "to") || !strcmp(argument, "x") || !strcmp(argument, "y")) {
|
|
|
|
|
action_arg_add_str(action, argument, content);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-12-10 15:28:25 +01:00
|
|
|
}
|
2023-05-11 23:36:51 +02:00
|
|
|
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'",
|
|
|
|
|
action_names[action->type], argument);
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
free(argument);
|
2022-12-10 15:28:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-05 09:11:24 +01:00
|
|
|
static enum action_type
|
|
|
|
|
action_type_from_str(const char *action_name)
|
|
|
|
|
{
|
2022-04-04 20:53:36 +01:00
|
|
|
for (size_t i = 1; action_names[i]; i++) {
|
2022-01-05 09:11:24 +01:00
|
|
|
if (!strcasecmp(action_name, action_names[i])) {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid action: %s", action_name);
|
2022-09-06 13:22:43 -04:00
|
|
|
return ACTION_TYPE_INVALID;
|
2022-01-05 09:11:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct action *
|
|
|
|
|
action_create(const char *action_name)
|
|
|
|
|
{
|
|
|
|
|
if (!action_name) {
|
|
|
|
|
wlr_log(WLR_ERROR, "action name not specified");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2023-01-29 04:06:46 +01:00
|
|
|
|
|
|
|
|
enum action_type action_type = action_type_from_str(action_name);
|
|
|
|
|
if (action_type == ACTION_TYPE_NONE) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-18 15:22:26 -04:00
|
|
|
struct action *action = znew(*action);
|
2023-01-29 04:06:46 +01:00
|
|
|
action->type = action_type;
|
2022-06-10 19:42:34 +02:00
|
|
|
wl_list_init(&action->args);
|
2022-01-05 09:11:24 +01:00
|
|
|
return action;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 18:16:46 +01:00
|
|
|
bool
|
|
|
|
|
actions_contain_toggle_keybinds(struct wl_list *action_list)
|
|
|
|
|
{
|
|
|
|
|
struct action *action;
|
|
|
|
|
wl_list_for_each(action, action_list, link) {
|
|
|
|
|
if (action->type == ACTION_TYPE_TOGGLE_KEYBINDS) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 19:14:04 +03:00
|
|
|
static bool
|
|
|
|
|
action_list_is_valid(struct wl_list *actions)
|
|
|
|
|
{
|
|
|
|
|
assert(actions);
|
|
|
|
|
|
|
|
|
|
struct action *action;
|
|
|
|
|
wl_list_for_each(action, actions, link) {
|
|
|
|
|
if (!action_is_valid(action)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-13 15:57:37 +02:00
|
|
|
/* Checks for *required* arguments */
|
|
|
|
|
bool
|
|
|
|
|
action_is_valid(struct action *action)
|
|
|
|
|
{
|
|
|
|
|
const char *arg_name = NULL;
|
2023-08-06 09:47:37 +02:00
|
|
|
enum action_arg_type arg_type = LAB_ACTION_ARG_STR;
|
|
|
|
|
|
2023-05-13 15:57:37 +02:00
|
|
|
switch (action->type) {
|
|
|
|
|
case ACTION_TYPE_EXECUTE:
|
|
|
|
|
arg_name = "command";
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_MOVE_TO_EDGE:
|
2024-09-21 15:53:05 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_SNAP_TO_EDGE:
|
2023-05-13 15:57:37 +02:00
|
|
|
case ACTION_TYPE_SNAP_TO_EDGE:
|
2023-08-05 23:58:40 +02:00
|
|
|
case ACTION_TYPE_GROW_TO_EDGE:
|
|
|
|
|
case ACTION_TYPE_SHRINK_TO_EDGE:
|
2023-05-13 15:57:37 +02:00
|
|
|
arg_name = "direction";
|
2023-08-06 09:47:37 +02:00
|
|
|
arg_type = LAB_ACTION_ARG_INT;
|
2023-05-13 15:57:37 +02:00
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_SHOW_MENU:
|
|
|
|
|
arg_name = "menu";
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_GO_TO_DESKTOP:
|
|
|
|
|
case ACTION_TYPE_SEND_TO_DESKTOP:
|
|
|
|
|
arg_name = "to";
|
|
|
|
|
break;
|
2024-09-21 15:53:05 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_SNAP_TO_REGION:
|
2023-05-13 15:57:37 +02:00
|
|
|
case ACTION_TYPE_SNAP_TO_REGION:
|
|
|
|
|
arg_name = "region";
|
|
|
|
|
break;
|
2023-08-28 19:14:04 +03:00
|
|
|
case ACTION_TYPE_IF:
|
|
|
|
|
case ACTION_TYPE_FOR_EACH:
|
|
|
|
|
; /* works around "a label can only be part of a statement" */
|
2024-04-13 21:24:04 +02:00
|
|
|
static const char * const branches[] = { "then", "else", "none" };
|
2023-08-28 19:14:04 +03:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(branches); i++) {
|
|
|
|
|
struct wl_list *children = action_get_actionlist(action, branches[i]);
|
|
|
|
|
if (children && !action_list_is_valid(children)) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid action in %s '%s' branch",
|
|
|
|
|
action_names[action->type], branches[i]);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2023-05-13 15:57:37 +02:00
|
|
|
default:
|
|
|
|
|
/* No arguments required */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:21:49 +03:00
|
|
|
if (action_get_arg(action, arg_name, arg_type)) {
|
2023-05-13 15:57:37 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wlr_log(WLR_ERROR, "Missing required argument for %s: %s",
|
|
|
|
|
action_names[action->type], arg_name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-08 01:32:35 +09:00
|
|
|
bool
|
|
|
|
|
action_is_show_menu(struct action *action)
|
|
|
|
|
{
|
|
|
|
|
return action->type == ACTION_TYPE_SHOW_MENU;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-13 16:27:46 +02:00
|
|
|
void
|
|
|
|
|
action_free(struct action *action)
|
2022-02-24 02:33:17 +01:00
|
|
|
{
|
2023-05-13 16:27:46 +02:00
|
|
|
/* Free args */
|
2022-06-10 19:42:34 +02:00
|
|
|
struct action_arg *arg, *arg_tmp;
|
2023-05-13 16:27:46 +02:00
|
|
|
wl_list_for_each_safe(arg, arg_tmp, &action->args, link) {
|
|
|
|
|
wl_list_remove(&arg->link);
|
|
|
|
|
zfree(arg->key);
|
|
|
|
|
if (arg->type == LAB_ACTION_ARG_STR) {
|
|
|
|
|
struct action_arg_str *str_arg = (struct action_arg_str *)arg;
|
|
|
|
|
zfree(str_arg->value);
|
2023-08-28 19:14:04 +03:00
|
|
|
} else if (arg->type == LAB_ACTION_ARG_ACTION_LIST) {
|
|
|
|
|
struct action_arg_list *list_arg = (struct action_arg_list *)arg;
|
|
|
|
|
action_list_free(&list_arg->value);
|
|
|
|
|
} else if (arg->type == LAB_ACTION_ARG_QUERY_LIST) {
|
|
|
|
|
struct action_arg_list *list_arg = (struct action_arg_list *)arg;
|
|
|
|
|
struct view_query *elm, *next;
|
|
|
|
|
wl_list_for_each_safe(elm, next, &list_arg->value, link) {
|
|
|
|
|
view_query_free(elm);
|
|
|
|
|
}
|
2023-05-13 16:27:46 +02:00
|
|
|
}
|
|
|
|
|
zfree(arg);
|
|
|
|
|
}
|
|
|
|
|
zfree(action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
action_list_free(struct wl_list *action_list)
|
|
|
|
|
{
|
2022-01-23 13:46:46 +01:00
|
|
|
struct action *action, *action_tmp;
|
|
|
|
|
wl_list_for_each_safe(action, action_tmp, action_list, link) {
|
|
|
|
|
wl_list_remove(&action->link);
|
2023-05-13 16:27:46 +02:00
|
|
|
action_free(action);
|
2022-01-23 13:46:46 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-31 15:27:22 +00:00
|
|
|
static void
|
2024-09-21 01:11:27 +09:00
|
|
|
show_menu(struct server *server, struct view *view, struct cursor_context *ctx,
|
2024-09-05 07:52:23 -05:00
|
|
|
const char *menu_name, bool at_cursor,
|
|
|
|
|
const char *pos_x, const char *pos_y)
|
2020-10-31 15:27:22 +00:00
|
|
|
{
|
2023-03-07 22:59:56 +01:00
|
|
|
if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH
|
|
|
|
|
&& server->input_mode != LAB_INPUT_STATE_MENU) {
|
|
|
|
|
/* Prevent opening a menu while resizing / moving a view */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-09 20:59:04 +01:00
|
|
|
struct menu *menu = menu_get_by_id(server, menu_name);
|
2022-02-19 02:05:38 +01:00
|
|
|
if (!menu) {
|
2022-01-26 00:07:10 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-03-10 14:28:41 -04:00
|
|
|
|
2024-09-18 12:08:10 -05:00
|
|
|
/*
|
2024-10-20 17:58:09 +09:00
|
|
|
* We always refresh client-list-combined-menu and client-send-to-menu
|
|
|
|
|
* so that they are up-to-date whether they are directly opened as a
|
|
|
|
|
* top-level menu or opened as a submenu which we don't know at this
|
|
|
|
|
* point. It is also needed to calculate the proper width for placement
|
|
|
|
|
* as it fluctuates depending on application/workspace titles.
|
2024-09-18 12:08:10 -05:00
|
|
|
*/
|
|
|
|
|
update_client_list_combined_menu(menu->server);
|
2024-10-20 17:58:09 +09:00
|
|
|
update_client_send_to_menu(menu->server);
|
2024-09-17 06:52:10 -05:00
|
|
|
|
2024-03-10 14:28:41 -04:00
|
|
|
int x = server->seat.cursor->x;
|
|
|
|
|
int y = server->seat.cursor->y;
|
|
|
|
|
|
|
|
|
|
/* The client menu needs an active client */
|
2024-09-15 22:29:05 +02:00
|
|
|
bool is_client_menu = !strcasecmp(menu_name, "client-menu");
|
2024-10-20 17:58:09 +09:00
|
|
|
if (is_client_menu && !view) {
|
|
|
|
|
return;
|
2020-10-31 15:27:22 +00:00
|
|
|
}
|
2024-03-10 14:28:41 -04:00
|
|
|
/* Place menu in the view corner if desired (and menu is not root-menu) */
|
|
|
|
|
if (!at_cursor && view) {
|
2024-12-19 01:06:48 +09:00
|
|
|
struct wlr_box extent = ssd_max_extents(view);
|
|
|
|
|
x = extent.x;
|
2023-02-08 23:19:14 -05:00
|
|
|
y = view->current.y;
|
2024-09-21 01:11:27 +09:00
|
|
|
/* Push the client menu underneath the button */
|
|
|
|
|
if (is_client_menu && ssd_part_contains(
|
|
|
|
|
LAB_SSD_BUTTON, ctx->type)) {
|
|
|
|
|
assert(ctx->node);
|
2024-12-19 01:06:48 +09:00
|
|
|
int lx, ly;
|
|
|
|
|
wlr_scene_node_coords(ctx->node, &lx, &ly);
|
|
|
|
|
/* MAX() prevents negative x when the window is maximized */
|
|
|
|
|
x = MAX(x, lx - server->theme->menu_border_width);
|
2024-09-21 01:11:27 +09:00
|
|
|
}
|
2020-10-31 15:27:22 +00:00
|
|
|
}
|
2024-03-10 14:28:41 -04:00
|
|
|
|
2024-09-05 07:52:23 -05:00
|
|
|
/*
|
|
|
|
|
* determine placement by looking at x and y
|
|
|
|
|
* x/y can be number, "center" or a %percent of screen dimensions
|
|
|
|
|
*/
|
|
|
|
|
if (pos_x && pos_y) {
|
|
|
|
|
struct output *output = output_nearest_to(server,
|
|
|
|
|
server->seat.cursor->x, server->seat.cursor->y);
|
2024-12-08 17:59:56 +09:00
|
|
|
struct wlr_box usable = output_usable_area_in_layout_coords(output);
|
2024-09-05 07:52:23 -05:00
|
|
|
|
|
|
|
|
if (!strcasecmp(pos_x, "center")) {
|
2024-12-08 17:59:56 +09:00
|
|
|
x = (usable.width - menu->size.width) / 2;
|
2024-09-05 07:52:23 -05:00
|
|
|
} else if (strchr(pos_x, '%')) {
|
2024-12-08 17:59:56 +09:00
|
|
|
x = (usable.width * atoi(pos_x)) / 100;
|
2024-09-05 07:52:23 -05:00
|
|
|
} else {
|
|
|
|
|
if (pos_x[0] == '-') {
|
|
|
|
|
int neg_x = strtol(pos_x, NULL, 10);
|
2024-12-08 17:59:56 +09:00
|
|
|
x = usable.width + neg_x;
|
2024-09-05 07:52:23 -05:00
|
|
|
} else {
|
|
|
|
|
x = atoi(pos_x);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!strcasecmp(pos_y, "center")) {
|
2024-12-08 17:59:56 +09:00
|
|
|
y = (usable.height / 2) - (menu->size.height / 2);
|
2024-09-05 07:52:23 -05:00
|
|
|
} else if (strchr(pos_y, '%')) {
|
2024-12-08 17:59:56 +09:00
|
|
|
y = (usable.height * atoi(pos_y)) / 100;
|
2024-09-05 07:52:23 -05:00
|
|
|
} else {
|
|
|
|
|
if (pos_y[0] == '-') {
|
|
|
|
|
int neg_y = strtol(pos_y, NULL, 10);
|
2024-12-08 17:59:56 +09:00
|
|
|
y = usable.height + neg_y;
|
2024-09-05 07:52:23 -05:00
|
|
|
} else {
|
|
|
|
|
y = atoi(pos_y);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* keep menu from being off screen */
|
|
|
|
|
x = MAX(x, 0);
|
2024-12-08 17:59:56 +09:00
|
|
|
x = MIN(x, usable.width - 1);
|
2024-09-05 07:52:23 -05:00
|
|
|
y = MAX(y, 0);
|
2024-12-08 17:59:56 +09:00
|
|
|
y = MIN(y, usable.height - 1);
|
2024-09-05 07:52:23 -05:00
|
|
|
/* adjust for which monitor to appear on */
|
2024-12-08 17:59:56 +09:00
|
|
|
x += usable.x;
|
|
|
|
|
y += usable.y;
|
2024-09-05 07:52:23 -05:00
|
|
|
}
|
|
|
|
|
|
2022-06-09 17:10:36 +02:00
|
|
|
/* Replaced by next show_menu() or cleaned on view_destroy() */
|
|
|
|
|
menu->triggered_by_view = view;
|
2023-10-31 21:08:04 +00:00
|
|
|
menu_open_root(menu, x, y);
|
2020-10-31 15:27:22 +00:00
|
|
|
}
|
2020-06-18 20:18:01 +01:00
|
|
|
|
2021-12-03 16:37:53 +00:00
|
|
|
static struct view *
|
2022-09-14 23:09:36 -04:00
|
|
|
view_for_action(struct view *activator, struct server *server,
|
2024-09-21 01:11:27 +09:00
|
|
|
struct action *action, struct cursor_context *ctx)
|
2021-12-03 16:37:53 +00:00
|
|
|
{
|
2022-09-14 23:09:36 -04:00
|
|
|
/* View is explicitly specified for mousebinds */
|
|
|
|
|
if (activator) {
|
|
|
|
|
return activator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Select view based on action type for keybinds */
|
|
|
|
|
switch (action->type) {
|
|
|
|
|
case ACTION_TYPE_FOCUS:
|
|
|
|
|
case ACTION_TYPE_MOVE:
|
2022-09-15 08:31:37 -04:00
|
|
|
case ACTION_TYPE_RESIZE: {
|
2024-09-21 01:11:27 +09:00
|
|
|
*ctx = get_cursor_context(server);
|
|
|
|
|
return ctx->view;
|
2022-09-15 08:31:37 -04:00
|
|
|
}
|
2022-09-14 23:09:36 -04:00
|
|
|
default:
|
2023-12-19 17:45:11 +00:00
|
|
|
return server->active_view;
|
2022-09-14 23:09:36 -04:00
|
|
|
}
|
2021-12-03 16:37:53 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-13 21:24:04 +02:00
|
|
|
static bool
|
2023-08-28 19:14:04 +03:00
|
|
|
run_if_action(struct view *view, struct server *server, struct action *action)
|
|
|
|
|
{
|
|
|
|
|
struct view_query *query;
|
|
|
|
|
struct wl_list *queries, *actions;
|
|
|
|
|
const char *branch = "then";
|
|
|
|
|
|
|
|
|
|
queries = action_get_querylist(action, "query");
|
|
|
|
|
if (queries) {
|
|
|
|
|
branch = "else";
|
|
|
|
|
/* All queries are OR'ed */
|
|
|
|
|
wl_list_for_each(query, queries, link) {
|
|
|
|
|
if (view_matches_query(view, query)) {
|
|
|
|
|
branch = "then";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actions = action_get_actionlist(action, branch);
|
|
|
|
|
if (actions) {
|
2024-09-21 01:11:27 +09:00
|
|
|
actions_run(view, server, actions, NULL);
|
2023-08-28 19:14:04 +03:00
|
|
|
}
|
2024-04-13 21:24:04 +02:00
|
|
|
return !strcmp(branch, "then");
|
2023-08-28 19:14:04 +03:00
|
|
|
}
|
|
|
|
|
|
2024-08-21 07:38:44 +03:00
|
|
|
static struct output *
|
|
|
|
|
get_target_output(struct output *output, struct server *server,
|
|
|
|
|
struct action *action)
|
|
|
|
|
{
|
|
|
|
|
const char *output_name = action_get_str(action, "output", NULL);
|
|
|
|
|
struct output *target = NULL;
|
|
|
|
|
|
|
|
|
|
if (output_name) {
|
|
|
|
|
target = output_from_name(server, output_name);
|
|
|
|
|
} else {
|
|
|
|
|
enum view_edge edge =
|
|
|
|
|
action_get_int(action, "direction", VIEW_EDGE_INVALID);
|
|
|
|
|
bool wrap = action_get_bool(action, "wrap", false);
|
|
|
|
|
target = output_get_adjacent(output, edge, wrap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!target) {
|
|
|
|
|
wlr_log(WLR_DEBUG, "Invalid output");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return target;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-22 14:48:47 +02:00
|
|
|
static void
|
|
|
|
|
warp_cursor(struct view *view, struct output *output, const char *to, const char *x, const char *y)
|
|
|
|
|
{
|
|
|
|
|
struct wlr_box target_area = {0};
|
|
|
|
|
int goto_x;
|
|
|
|
|
int goto_y;
|
|
|
|
|
|
|
|
|
|
if (!strcasecmp(to, "output") && output) {
|
|
|
|
|
target_area = output_usable_area_in_layout_coords(output);
|
|
|
|
|
} else if (!strcasecmp(to, "window") && view) {
|
|
|
|
|
target_area = view->current;
|
|
|
|
|
} else {
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid argument for action WarpCursor: 'to' (%s)", to);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!strcasecmp(x, "center")) {
|
|
|
|
|
goto_x = target_area.x + target_area.width / 2;
|
|
|
|
|
} else {
|
|
|
|
|
int offset_x = atoi(x);
|
|
|
|
|
goto_x = offset_x >= 0 ?
|
|
|
|
|
target_area.x + offset_x :
|
|
|
|
|
target_area.x + target_area.width + offset_x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!strcasecmp(y, "center")) {
|
|
|
|
|
goto_y = target_area.y + target_area.height / 2;
|
|
|
|
|
} else {
|
|
|
|
|
int offset_y = atoi(y);
|
|
|
|
|
goto_y = offset_y >= 0 ?
|
|
|
|
|
target_area.y + offset_y :
|
|
|
|
|
target_area.y + target_area.height + offset_y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wlr_cursor_warp(output->server->seat.cursor, NULL, goto_x, goto_y);
|
|
|
|
|
cursor_update_focus(output->server);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
void
|
2022-02-24 02:16:19 +01:00
|
|
|
actions_run(struct view *activator, struct server *server,
|
2024-09-21 01:11:27 +09:00
|
|
|
struct wl_list *actions, struct cursor_context *cursor_ctx)
|
2020-06-18 20:18:01 +01:00
|
|
|
{
|
2022-01-05 09:11:24 +01:00
|
|
|
if (!actions) {
|
|
|
|
|
wlr_log(WLR_ERROR, "empty actions");
|
2020-06-18 20:18:01 +01:00
|
|
|
return;
|
2022-01-05 09:11:24 +01:00
|
|
|
}
|
|
|
|
|
|
2024-06-14 10:23:04 +01:00
|
|
|
/* This cancels any pending on-release keybinds */
|
2024-06-14 10:24:49 +01:00
|
|
|
keyboard_reset_current_keybind();
|
2024-06-13 10:57:34 +01:00
|
|
|
|
2022-01-05 09:11:24 +01:00
|
|
|
struct view *view;
|
|
|
|
|
struct action *action;
|
2024-08-21 07:38:44 +03:00
|
|
|
struct output *output;
|
|
|
|
|
struct output *target;
|
2024-09-21 01:11:27 +09:00
|
|
|
struct cursor_context ctx = {0};
|
|
|
|
|
if (cursor_ctx) {
|
|
|
|
|
ctx = *cursor_ctx;
|
|
|
|
|
}
|
2024-08-21 07:38:44 +03:00
|
|
|
|
2022-01-05 09:11:24 +01:00
|
|
|
wl_list_for_each(action, actions, link) {
|
2023-08-28 18:29:20 +03:00
|
|
|
wlr_log(WLR_DEBUG, "Handling action %u: %s", action->type,
|
|
|
|
|
action_names[action->type]);
|
2023-01-06 18:44:13 +01:00
|
|
|
|
2022-04-04 20:53:36 +01:00
|
|
|
/*
|
|
|
|
|
* Refetch view because it may have been changed due to the
|
|
|
|
|
* previous action
|
|
|
|
|
*/
|
2024-09-21 01:11:27 +09:00
|
|
|
view = view_for_action(activator, server, action, &ctx);
|
2022-01-05 09:11:24 +01:00
|
|
|
|
2022-01-05 21:23:01 +00:00
|
|
|
switch (action->type) {
|
|
|
|
|
case ACTION_TYPE_CLOSE:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
|
|
|
|
view_close(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-03-15 15:47:46 +00:00
|
|
|
case ACTION_TYPE_KILL:
|
2024-04-22 17:43:46 +02:00
|
|
|
if (view) {
|
2022-03-15 15:47:46 +00:00
|
|
|
/* Send SIGTERM to the process associated with the surface */
|
2024-04-22 17:43:46 +02:00
|
|
|
assert(view->impl->get_pid);
|
|
|
|
|
pid_t pid = view->impl->get_pid(view);
|
|
|
|
|
if (pid == getpid()) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Preventing sending SIGTERM to labwc");
|
|
|
|
|
} else if (pid > 0) {
|
2022-03-15 15:47:46 +00:00
|
|
|
kill(pid, SIGTERM);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_DEBUG:
|
2022-02-12 19:43:32 +00:00
|
|
|
debug_dump_scene(server);
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_EXECUTE:
|
2023-05-15 18:50:13 +02:00
|
|
|
{
|
2024-04-14 14:20:57 -04:00
|
|
|
struct buf cmd = BUF_INIT;
|
2023-08-28 18:29:20 +03:00
|
|
|
buf_add(&cmd, action_get_str(action, "command", NULL));
|
2023-09-21 23:09:05 +01:00
|
|
|
buf_expand_tilde(&cmd);
|
2024-04-16 23:36:32 -04:00
|
|
|
spawn_async_no_shell(cmd.data);
|
2024-04-14 14:20:57 -04:00
|
|
|
buf_reset(&cmd);
|
2022-01-05 09:11:24 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_EXIT:
|
2022-01-05 09:11:24 +01:00
|
|
|
wl_display_terminate(server->wl_display);
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_MOVE_TO_EDGE:
|
2022-11-22 02:09:42 -05:00
|
|
|
if (view) {
|
2023-08-02 04:30:50 +02:00
|
|
|
/* Config parsing makes sure that direction is a valid direction */
|
2023-08-28 18:21:49 +03:00
|
|
|
enum view_edge edge = action_get_int(action, "direction", 0);
|
2023-08-05 23:58:40 +02:00
|
|
|
bool snap_to_windows = action_get_bool(action, "snapWindows", true);
|
|
|
|
|
view_move_to_edge(view, edge, snap_to_windows);
|
2022-06-03 02:19:31 +02:00
|
|
|
}
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2024-09-21 15:53:05 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_SNAP_TO_EDGE:
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_SNAP_TO_EDGE:
|
2022-11-22 02:09:42 -05:00
|
|
|
if (view) {
|
2023-08-02 04:30:50 +02:00
|
|
|
/* Config parsing makes sure that direction is a valid direction */
|
2023-08-28 18:21:49 +03:00
|
|
|
enum view_edge edge = action_get_int(action, "direction", 0);
|
2024-09-21 15:53:05 +02:00
|
|
|
if (action->type == ACTION_TYPE_TOGGLE_SNAP_TO_EDGE
|
|
|
|
|
&& view->maximized == VIEW_AXIS_NONE
|
|
|
|
|
&& !view->fullscreen
|
|
|
|
|
&& view_is_tiled(view)
|
|
|
|
|
&& view->tiled == edge) {
|
|
|
|
|
view_set_untiled(view);
|
|
|
|
|
view_apply_natural_geometry(view);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-11-07 14:43:53 +09:00
|
|
|
view_snap_to_edge(view, edge,
|
|
|
|
|
/*across_outputs*/ true,
|
|
|
|
|
/*store_natural_geometry*/ true);
|
2022-06-03 02:19:31 +02:00
|
|
|
}
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2023-08-05 23:58:40 +02:00
|
|
|
case ACTION_TYPE_GROW_TO_EDGE:
|
|
|
|
|
if (view) {
|
|
|
|
|
/* Config parsing makes sure that direction is a valid direction */
|
|
|
|
|
enum view_edge edge = action_get_int(action, "direction", 0);
|
|
|
|
|
view_grow_to_edge(view, edge);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_SHRINK_TO_EDGE:
|
|
|
|
|
if (view) {
|
|
|
|
|
/* Config parsing makes sure that direction is a valid direction */
|
|
|
|
|
enum view_edge edge = action_get_int(action, "direction", 0);
|
|
|
|
|
view_shrink_to_edge(view, edge);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_NEXT_WINDOW:
|
2024-12-31 10:17:05 +09:00
|
|
|
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) {
|
|
|
|
|
osd_cycle(server, LAB_CYCLE_DIR_FORWARD);
|
|
|
|
|
} else {
|
|
|
|
|
osd_begin(server, LAB_CYCLE_DIR_FORWARD);
|
|
|
|
|
}
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_PREVIOUS_WINDOW:
|
2024-12-31 10:17:05 +09:00
|
|
|
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) {
|
|
|
|
|
osd_cycle(server, LAB_CYCLE_DIR_BACKWARD);
|
|
|
|
|
} else {
|
|
|
|
|
osd_begin(server, LAB_CYCLE_DIR_BACKWARD);
|
|
|
|
|
}
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_RECONFIGURE:
|
2022-06-14 22:45:45 +01:00
|
|
|
kill(getpid(), SIGHUP);
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_SHOW_MENU:
|
2024-09-21 01:11:27 +09:00
|
|
|
show_menu(server, view, &ctx,
|
2024-03-10 14:28:41 -04:00
|
|
|
action_get_str(action, "menu", NULL),
|
2024-09-05 07:52:23 -05:00
|
|
|
action_get_bool(action, "atCursor", true),
|
|
|
|
|
action_get_str(action, "x.position", NULL),
|
|
|
|
|
action_get_str(action, "y.position", NULL));
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_TOGGLE_MAXIMIZE:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
view: implement separate horizontal/vertical maximize
This is a useful (if lesser-known) feature of at least a few popular X11
window managers, for example Openbox and XFWM4. Typically right-click on
the maximize button toggles horizontal maximize, while middle-click
toggles vertical maximize.
Support in labwc uses the same configuration syntax as Openbox, where the
Maximize/ToggleMaximize actions have an optional "direction" argument:
horizontal, vertical, or both (default). The default mouse bindings match
the XFWM4 defaults (not sure what Openbox has by default).
Most of the external protocols still assume "maximized" is a Boolean,
which is no longer true internally. For the sake of the outside world,
a view is only "maximized" if maximized in both directions.
Internally, I've taken the following approach:
- SSD code decorates the view as "maximized" (i.e. hiding borders) only
if maximized in both directions.
- Layout code (interactive move/resize, tiling, etc.) generally treats
the view as "maximized" (with the restrictions that entails) if
maximized in either direction. For example, moving a vertically-
maximized view first restores the natural geometry (this differs from
Openbox, which instead allows the view to move only horizontally.)
v2: use enum view_axis for view->maximized
v3:
- update docs
- allow resizing if partly maximized
- add TODOs & corrections noted by Consolatis
2023-10-26 00:38:29 -04:00
|
|
|
enum view_axis axis = action_get_int(action,
|
|
|
|
|
"direction", VIEW_AXIS_BOTH);
|
|
|
|
|
view_toggle_maximize(view, axis);
|
2022-01-05 09:11:24 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2023-05-04 21:29:14 +01:00
|
|
|
case ACTION_TYPE_MAXIMIZE:
|
|
|
|
|
if (view) {
|
view: implement separate horizontal/vertical maximize
This is a useful (if lesser-known) feature of at least a few popular X11
window managers, for example Openbox and XFWM4. Typically right-click on
the maximize button toggles horizontal maximize, while middle-click
toggles vertical maximize.
Support in labwc uses the same configuration syntax as Openbox, where the
Maximize/ToggleMaximize actions have an optional "direction" argument:
horizontal, vertical, or both (default). The default mouse bindings match
the XFWM4 defaults (not sure what Openbox has by default).
Most of the external protocols still assume "maximized" is a Boolean,
which is no longer true internally. For the sake of the outside world,
a view is only "maximized" if maximized in both directions.
Internally, I've taken the following approach:
- SSD code decorates the view as "maximized" (i.e. hiding borders) only
if maximized in both directions.
- Layout code (interactive move/resize, tiling, etc.) generally treats
the view as "maximized" (with the restrictions that entails) if
maximized in either direction. For example, moving a vertically-
maximized view first restores the natural geometry (this differs from
Openbox, which instead allows the view to move only horizontally.)
v2: use enum view_axis for view->maximized
v3:
- update docs
- allow resizing if partly maximized
- add TODOs & corrections noted by Consolatis
2023-10-26 00:38:29 -04:00
|
|
|
enum view_axis axis = action_get_int(action,
|
|
|
|
|
"direction", VIEW_AXIS_BOTH);
|
|
|
|
|
view_maximize(view, axis,
|
|
|
|
|
/*store_natural_geometry*/ true);
|
2023-05-04 21:29:14 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2024-05-19 18:10:44 +01:00
|
|
|
case ACTION_TYPE_UNMAXIMIZE:
|
|
|
|
|
if (view) {
|
|
|
|
|
enum view_axis axis = action_get_int(action,
|
|
|
|
|
"direction", VIEW_AXIS_BOTH);
|
|
|
|
|
view_maximize(view, view->maximized & ~axis,
|
|
|
|
|
/*store_natural_geometry*/ true);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_TOGGLE_FULLSCREEN:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
|
|
|
|
view_toggle_fullscreen(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-04-20 06:29:51 +02:00
|
|
|
case ACTION_TYPE_SET_DECORATIONS:
|
|
|
|
|
if (view) {
|
|
|
|
|
enum ssd_mode mode = action_get_int(action,
|
|
|
|
|
"decorations", LAB_SSD_MODE_FULL);
|
|
|
|
|
bool force_ssd = action_get_bool(action,
|
|
|
|
|
"forceSSD", false);
|
|
|
|
|
view_set_decorations(view, mode, force_ssd);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_TOGGLE_DECORATIONS:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
|
|
|
|
view_toggle_decorations(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-04-09 01:16:09 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_ALWAYS_ON_TOP:
|
|
|
|
|
if (view) {
|
|
|
|
|
view_toggle_always_on_top(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-05-11 22:26:41 +01:00
|
|
|
case ACTION_TYPE_TOGGLE_ALWAYS_ON_BOTTOM:
|
|
|
|
|
if (view) {
|
|
|
|
|
view_toggle_always_on_bottom(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-11-25 17:54:36 -06:00
|
|
|
case ACTION_TYPE_TOGGLE_OMNIPRESENT:
|
|
|
|
|
if (view) {
|
|
|
|
|
view_toggle_visible_on_all_workspaces(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_FOCUS:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
2023-09-27 18:37:28 -04:00
|
|
|
desktop_focus_view(view, /*raise*/ false);
|
2022-01-05 09:11:24 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2023-11-07 18:53:27 +00:00
|
|
|
case ACTION_TYPE_UNFOCUS:
|
|
|
|
|
seat_focus_surface(&server->seat, NULL);
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_ICONIFY:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
|
|
|
|
view_minimize(view, true);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_MOVE:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
|
|
|
|
interactive_begin(view, LAB_INPUT_STATE_MOVE, 0);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_RAISE:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
2023-04-01 14:06:52 -04:00
|
|
|
view_move_to_front(view);
|
2022-01-05 09:11:24 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2023-03-20 03:31:49 +01:00
|
|
|
case ACTION_TYPE_LOWER:
|
|
|
|
|
if (view) {
|
2023-04-01 14:06:52 -04:00
|
|
|
view_move_to_back(view);
|
2023-03-20 03:31:49 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
case ACTION_TYPE_RESIZE:
|
2022-01-05 09:11:24 +01:00
|
|
|
if (view) {
|
2024-09-21 01:11:27 +09:00
|
|
|
uint32_t resize_edges = cursor_get_resize_edges(
|
|
|
|
|
server->seat.cursor, &ctx);
|
2022-04-04 20:53:36 +01:00
|
|
|
interactive_begin(view, LAB_INPUT_STATE_RESIZE,
|
|
|
|
|
resize_edges);
|
2022-01-05 09:11:24 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2023-06-27 21:20:04 +03:00
|
|
|
case ACTION_TYPE_RESIZE_RELATIVE:
|
|
|
|
|
if (view) {
|
2023-08-28 18:21:49 +03:00
|
|
|
int left = action_get_int(action, "left", 0);
|
|
|
|
|
int right = action_get_int(action, "right", 0);
|
|
|
|
|
int top = action_get_int(action, "top", 0);
|
|
|
|
|
int bottom = action_get_int(action, "bottom", 0);
|
2023-06-27 21:20:04 +03:00
|
|
|
view_resize_relative(view, left, right, top, bottom);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-06-06 20:33:53 +01:00
|
|
|
case ACTION_TYPE_MOVETO:
|
|
|
|
|
if (view) {
|
2023-08-28 18:21:49 +03:00
|
|
|
int x = action_get_int(action, "x", 0);
|
|
|
|
|
int y = action_get_int(action, "y", 0);
|
2025-02-04 21:47:24 +01:00
|
|
|
struct border margin = ssd_thickness(view);
|
|
|
|
|
view_move(view, x + margin.left, y + margin.top);
|
2023-06-06 20:33:53 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2023-12-01 17:03:58 +00:00
|
|
|
case ACTION_TYPE_RESIZETO:
|
|
|
|
|
if (view) {
|
|
|
|
|
int width = action_get_int(action, "width", 0);
|
|
|
|
|
int height = action_get_int(action, "height", 0);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* To support only setting one of width/height
|
|
|
|
|
* in <action name="ResizeTo" width="" height=""/>
|
|
|
|
|
* we fall back to current dimension when unset.
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_box box = {
|
|
|
|
|
.x = view->pending.x,
|
|
|
|
|
.y = view->pending.y,
|
|
|
|
|
.width = width ? : view->pending.width,
|
|
|
|
|
.height = height ? : view->pending.height,
|
|
|
|
|
};
|
2023-08-08 03:39:35 +02:00
|
|
|
view_set_shade(view, false);
|
2023-12-01 17:03:58 +00:00
|
|
|
view_move_resize(view, box);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-06-26 19:04:33 +03:00
|
|
|
case ACTION_TYPE_MOVE_RELATIVE:
|
2023-07-23 12:04:36 +03:00
|
|
|
if (view) {
|
2023-08-28 18:21:49 +03:00
|
|
|
int x = action_get_int(action, "x", 0);
|
|
|
|
|
int y = action_get_int(action, "y", 0);
|
2023-07-23 12:04:36 +03:00
|
|
|
view_move_relative(view, x, y);
|
2023-06-26 19:04:33 +03:00
|
|
|
}
|
|
|
|
|
break;
|
2023-10-14 14:57:44 +02:00
|
|
|
case ACTION_TYPE_MOVETO_CURSOR:
|
2024-05-19 16:50:53 +01:00
|
|
|
wlr_log(WLR_ERROR,
|
|
|
|
|
"Action MoveToCursor is deprecated. To ensure your config works in future labwc "
|
|
|
|
|
"releases, please use <action name=\"AutoPlace\" policy=\"cursor\">");
|
2023-10-14 14:57:44 +02:00
|
|
|
if (view) {
|
|
|
|
|
view_move_to_cursor(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-05-15 18:50:13 +02:00
|
|
|
case ACTION_TYPE_SEND_TO_DESKTOP:
|
|
|
|
|
if (!view) {
|
2022-06-10 19:42:34 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2023-05-15 18:50:13 +02:00
|
|
|
/* Falls through to GoToDesktop */
|
|
|
|
|
case ACTION_TYPE_GO_TO_DESKTOP:
|
|
|
|
|
{
|
|
|
|
|
bool follow = true;
|
2023-08-28 18:21:49 +03:00
|
|
|
bool wrap = action_get_bool(action, "wrap", true);
|
|
|
|
|
const char *to = action_get_str(action, "to", NULL);
|
2023-05-15 18:50:13 +02:00
|
|
|
/*
|
|
|
|
|
* `to` is always != NULL here because otherwise we would have
|
|
|
|
|
* removed the action during the initial parsing step as it is
|
|
|
|
|
* a required argument for both SendToDesktop and GoToDesktop.
|
|
|
|
|
*/
|
|
|
|
|
struct workspace *target = workspaces_find(
|
2024-07-25 21:56:05 +02:00
|
|
|
server->workspaces.current, to, wrap);
|
2023-05-15 18:50:13 +02:00
|
|
|
if (!target) {
|
2023-04-25 21:28:47 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2023-05-15 18:50:13 +02:00
|
|
|
if (action->type == ACTION_TYPE_SEND_TO_DESKTOP) {
|
2022-11-21 13:03:49 -05:00
|
|
|
view_move_to_workspace(view, target);
|
2023-08-28 18:21:49 +03:00
|
|
|
follow = action_get_bool(action, "follow", true);
|
2024-05-18 19:01:02 +02:00
|
|
|
|
|
|
|
|
/* Ensure that the focus is not on another desktop */
|
|
|
|
|
if (!follow && server->active_view == view) {
|
|
|
|
|
desktop_focus_topmost_view(server);
|
|
|
|
|
}
|
2023-05-15 18:50:13 +02:00
|
|
|
}
|
|
|
|
|
if (follow) {
|
2023-09-27 18:37:28 -04:00
|
|
|
workspaces_switch_to(target,
|
|
|
|
|
/*update_focus*/ true);
|
2022-06-15 01:38:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-01-21 23:45:47 +01:00
|
|
|
case ACTION_TYPE_MOVE_TO_OUTPUT:
|
|
|
|
|
if (!view) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-08-21 07:38:44 +03:00
|
|
|
target = get_target_output(view->output, server, action);
|
|
|
|
|
if (target) {
|
|
|
|
|
view_move_to_output(view, target);
|
2024-01-21 23:45:47 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2024-01-22 22:56:18 +01:00
|
|
|
case ACTION_TYPE_FIT_TO_OUTPUT:
|
|
|
|
|
if (!view) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
view_constrain_size_to_that_of_usable_area(view);
|
|
|
|
|
break;
|
2024-09-21 15:53:05 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_SNAP_TO_REGION:
|
2022-07-06 08:06:48 +02:00
|
|
|
case ACTION_TYPE_SNAP_TO_REGION:
|
|
|
|
|
if (!view) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-08-21 07:38:44 +03:00
|
|
|
output = view->output;
|
2022-07-06 08:06:48 +02:00
|
|
|
if (!output) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-08-28 18:29:20 +03:00
|
|
|
const char *region_name = action_get_str(action, "region", NULL);
|
2022-07-06 08:06:48 +02:00
|
|
|
struct region *region = regions_from_name(region_name, output);
|
|
|
|
|
if (region) {
|
2024-09-21 15:53:05 +02:00
|
|
|
if (action->type == ACTION_TYPE_TOGGLE_SNAP_TO_REGION
|
|
|
|
|
&& view->maximized == VIEW_AXIS_NONE
|
|
|
|
|
&& !view->fullscreen
|
|
|
|
|
&& view_is_tiled(view)
|
|
|
|
|
&& view->tiled_region == region) {
|
|
|
|
|
view_set_untiled(view);
|
|
|
|
|
view_apply_natural_geometry(view);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-07-06 08:06:48 +02:00
|
|
|
view_snap_to_region(view, region,
|
|
|
|
|
/*store_natural_geometry*/ true);
|
|
|
|
|
} else {
|
|
|
|
|
wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-09-21 15:54:30 +02:00
|
|
|
case ACTION_TYPE_UNSNAP:
|
2024-09-24 06:39:21 +02:00
|
|
|
if (view && !view->fullscreen && !view_is_floating(view)) {
|
|
|
|
|
view_maximize(view, VIEW_AXIS_NONE,
|
|
|
|
|
/* store_natural_geometry */ false);
|
2024-09-21 15:54:30 +02:00
|
|
|
view_set_untiled(view);
|
|
|
|
|
view_apply_natural_geometry(view);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-03-03 18:16:46 +01:00
|
|
|
case ACTION_TYPE_TOGGLE_KEYBINDS:
|
2023-03-05 10:35:56 +01:00
|
|
|
if (view) {
|
|
|
|
|
view_toggle_keybinds(view);
|
|
|
|
|
}
|
2023-03-03 18:16:46 +01:00
|
|
|
break;
|
2023-03-05 17:16:23 +01:00
|
|
|
case ACTION_TYPE_FOCUS_OUTPUT:
|
2024-08-21 07:38:44 +03:00
|
|
|
output = output_nearest_to_cursor(server);
|
|
|
|
|
target = get_target_output(output, server, action);
|
|
|
|
|
if (target) {
|
|
|
|
|
desktop_focus_output(target);
|
2023-03-05 17:16:23 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2023-08-28 19:14:04 +03:00
|
|
|
case ACTION_TYPE_IF:
|
|
|
|
|
if (view) {
|
|
|
|
|
run_if_action(view, server, action);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_FOR_EACH:
|
|
|
|
|
{
|
|
|
|
|
struct wl_array views;
|
|
|
|
|
struct view **item;
|
2024-04-13 21:24:04 +02:00
|
|
|
bool matches = false;
|
2023-08-28 19:14:04 +03:00
|
|
|
wl_array_init(&views);
|
|
|
|
|
view_array_append(server, &views, LAB_VIEW_CRITERIA_NONE);
|
|
|
|
|
wl_array_for_each(item, &views) {
|
2024-04-13 21:24:04 +02:00
|
|
|
matches |= run_if_action(*item, server, action);
|
2023-08-28 19:14:04 +03:00
|
|
|
}
|
|
|
|
|
wl_array_release(&views);
|
2024-04-13 21:24:04 +02:00
|
|
|
if (!matches) {
|
|
|
|
|
struct wl_list *actions;
|
|
|
|
|
actions = action_get_actionlist(action, "none");
|
|
|
|
|
if (actions) {
|
2024-09-21 01:11:27 +09:00
|
|
|
actions_run(view, server, actions, NULL);
|
2024-04-13 21:24:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-28 19:14:04 +03:00
|
|
|
}
|
|
|
|
|
break;
|
2023-12-09 12:01:11 +03:00
|
|
|
case ACTION_TYPE_VIRTUAL_OUTPUT_ADD:
|
|
|
|
|
{
|
|
|
|
|
const char *output_name = action_get_str(action, "output_name",
|
|
|
|
|
NULL);
|
2024-03-07 00:22:51 +01:00
|
|
|
output_virtual_add(server, output_name,
|
|
|
|
|
/*store_wlr_output*/ NULL);
|
2023-12-09 12:01:11 +03:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE:
|
|
|
|
|
{
|
|
|
|
|
const char *output_name = action_get_str(action, "output_name",
|
|
|
|
|
NULL);
|
2024-03-07 00:22:51 +01:00
|
|
|
output_virtual_remove(server, output_name);
|
2023-12-09 12:01:11 +03:00
|
|
|
}
|
|
|
|
|
break;
|
2023-12-28 16:47:21 -05:00
|
|
|
case ACTION_TYPE_AUTO_PLACE:
|
|
|
|
|
if (view) {
|
2024-05-07 09:46:05 -04:00
|
|
|
enum view_placement_policy policy =
|
|
|
|
|
action_get_int(action, "policy", LAB_PLACE_AUTOMATIC);
|
|
|
|
|
view_place_by_policy(view,
|
|
|
|
|
/* allow_cursor */ true, policy);
|
2023-12-28 16:47:21 -05:00
|
|
|
}
|
|
|
|
|
break;
|
2024-01-08 22:58:58 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_TEARING:
|
|
|
|
|
if (view) {
|
2024-08-06 22:23:10 +02:00
|
|
|
switch (view->force_tearing) {
|
|
|
|
|
case LAB_STATE_UNSPECIFIED:
|
|
|
|
|
view->force_tearing =
|
|
|
|
|
output_get_tearing_allowance(view->output)
|
|
|
|
|
? LAB_STATE_DISABLED : LAB_STATE_ENABLED;
|
|
|
|
|
break;
|
|
|
|
|
case LAB_STATE_DISABLED:
|
|
|
|
|
view->force_tearing = LAB_STATE_ENABLED;
|
|
|
|
|
break;
|
|
|
|
|
case LAB_STATE_ENABLED:
|
|
|
|
|
view->force_tearing = LAB_STATE_DISABLED;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
wlr_log(WLR_ERROR, "force tearing %sabled",
|
|
|
|
|
view->force_tearing == LAB_STATE_ENABLED
|
|
|
|
|
? "en" : "dis");
|
2024-01-08 22:58:58 +02:00
|
|
|
}
|
|
|
|
|
break;
|
2023-08-08 03:39:35 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_SHADE:
|
|
|
|
|
if (view) {
|
|
|
|
|
view_set_shade(view, !view->shaded);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_SHADE:
|
|
|
|
|
if (view) {
|
|
|
|
|
view_set_shade(view, true);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_UNSHADE:
|
|
|
|
|
if (view) {
|
|
|
|
|
view_set_shade(view, false);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-08-18 16:09:39 +02:00
|
|
|
case ACTION_TYPE_ENABLE_TABLET_MOUSE_EMULATION:
|
|
|
|
|
rc.tablet.force_mouse_emulation = true;
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_DISABLE_TABLET_MOUSE_EMULATION:
|
|
|
|
|
rc.tablet.force_mouse_emulation = false;
|
|
|
|
|
break;
|
2024-06-16 08:47:48 +02:00
|
|
|
case ACTION_TYPE_TOGGLE_TABLET_MOUSE_EMULATION:
|
|
|
|
|
rc.tablet.force_mouse_emulation = !rc.tablet.force_mouse_emulation;
|
|
|
|
|
break;
|
2024-05-15 23:07:23 +01:00
|
|
|
case ACTION_TYPE_TOGGLE_MAGNIFY:
|
|
|
|
|
magnify_toggle(server);
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_ZOOM_IN:
|
|
|
|
|
magnify_set_scale(server, MAGNIFY_INCREASE);
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_TYPE_ZOOM_OUT:
|
|
|
|
|
magnify_set_scale(server, MAGNIFY_DECREASE);
|
|
|
|
|
break;
|
2024-12-22 14:48:47 +02:00
|
|
|
case ACTION_TYPE_WARP_CURSOR:
|
|
|
|
|
{
|
|
|
|
|
const char *to = action_get_str(action, "to", "output");
|
|
|
|
|
const char *x = action_get_str(action, "x", "center");
|
|
|
|
|
const char *y = action_get_str(action, "y", "center");
|
|
|
|
|
struct output *output = output_nearest_to_cursor(server);
|
|
|
|
|
|
|
|
|
|
warp_cursor(view, output, to, x, y);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-09-06 13:22:43 -04:00
|
|
|
case ACTION_TYPE_INVALID:
|
2022-06-10 19:42:34 +02:00
|
|
|
wlr_log(WLR_ERROR, "Not executing unknown action");
|
2022-01-05 09:11:24 +01:00
|
|
|
break;
|
2022-01-05 21:23:01 +00:00
|
|
|
default:
|
2022-01-05 09:11:24 +01:00
|
|
|
/*
|
|
|
|
|
* If we get here it must be a BUG caused most likely by
|
|
|
|
|
* action_names and action_type being out of sync or by
|
|
|
|
|
* adding a new action without installing a handler here.
|
|
|
|
|
*/
|
2022-04-04 20:53:36 +01:00
|
|
|
wlr_log(WLR_ERROR,
|
2022-06-10 19:42:34 +02:00
|
|
|
"Not executing invalid action (%u)"
|
|
|
|
|
" This is a BUG. Please report.", action->type);
|
2021-12-01 02:38:53 +00:00
|
|
|
}
|
2020-06-18 20:18:01 +01:00
|
|
|
}
|
|
|
|
|
}
|