diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd
index 2677e348..6ca8e022 100644
--- a/docs/labwc-actions.5.scd
+++ b/docs/labwc-actions.5.scd
@@ -77,6 +77,18 @@ Actions are used in menus and keyboard/mouse bindings.
**
Move to position (x, y).
+ *x* and *y* take the following types of value:
+ - A positive number like *10*. This will position the window at the
+ specified distance (in pixels) from the top/left edge of the output
+ (monitor).
+ - A negative number like *-5* which will similarly position the window
+ but relative to the bottom/right edge.
+ - The string *center* which will center the window relative to the
+ output in that dimension
+ - A percentage like *33%* which is interpreted as a relative value of
+ the output dimensions and will position the window by that amount
+ from the top/left edge.
+
**
Resize window.
diff --git a/src/action.c b/src/action.c
index 265ff6e7..e94a26ab 100644
--- a/src/action.c
+++ b/src/action.c
@@ -447,6 +447,11 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
}
break;
case ACTION_TYPE_MOVETO:
+ if (!strcmp(argument, "x") || !strcmp(argument, "y")) {
+ action_arg_add_str(action, argument, content);
+ goto cleanup;
+ }
+ break;
case ACTION_TYPE_MOVE_RELATIVE:
if (!strcmp(argument, "x") || !strcmp(argument, "y")) {
action_arg_add_int(action, argument, atoi(content));
@@ -1054,6 +1059,54 @@ warp_cursor(struct view *view, const char *to, const char *x, const char *y)
cursor_update_focus();
}
+struct point {
+ int x;
+ int y;
+};
+
+static struct point
+get_moveto_point(struct view *view, const char *pos_x, const char *pos_y)
+{
+ struct point point = { 0 };
+ struct border margin = ssd_thickness(view);
+ struct wlr_box max_extents = ssd_max_extents(view);
+ struct wlr_box usable = output_usable_area_in_layout_coords(view->output);
+ point.x += usable.x;
+ point.y += usable.y;
+
+ /* Parse horizontal position */
+ if (!strcasecmp(pos_x, "center")) {
+ point.x += (usable.width - max_extents.width) / 2;
+ point.x += margin.left;
+ } else if (strchr(pos_x, '%')) {
+ point.x += (usable.width * atoi(pos_x)) / 100;
+ point.x += margin.left;
+ } else if (pos_x[0] == '-') {
+ point.x += usable.width - view->current.width + strtol(pos_x, NULL, 10);
+ point.x -= margin.right;
+ } else {
+ point.x += atoi(pos_x);
+ point.x += margin.left;
+ }
+
+ /* Parse veritcal position */
+ if (!strcasecmp(pos_y, "center")) {
+ point.y += (usable.height - max_extents.height) / 2;
+ point.y += margin.top;
+ } else if (strchr(pos_y, '%')) {
+ point.y += (usable.height * atoi(pos_y)) / 100;
+ point.y += margin.top;
+ } else if (pos_y[0] == '-') {
+ point.y += usable.height - view->current.height + strtol(pos_y, NULL, 10);
+ point.y -= margin.bottom;
+ } else {
+ point.y += atoi(pos_y);
+ point.y += margin.top;
+ }
+
+ return point;
+}
+
static void
run_action(struct view *view, struct action *action,
struct cursor_context *ctx)
@@ -1299,10 +1352,10 @@ run_action(struct view *view, struct action *action,
break;
case ACTION_TYPE_MOVETO:
if (view) {
- int x = action_get_int(action, "x", 0);
- int y = action_get_int(action, "y", 0);
- struct border margin = ssd_thickness(view);
- view_move(view, x + margin.left, y + margin.top);
+ const char *x = action_get_str(action, "x", "0");
+ const char *y = action_get_str(action, "y", "0");
+ struct point point = get_moveto_point(view, x, y);
+ view_move(view, point.x, point.y);
}
break;
case ACTION_TYPE_RESIZETO: