*<action name="ToggleFullscreen" />*
Toggle fullscreen state of focused window.
-*<action name="ToggleMaximize" />*
- Toggle maximize state of focused window.
+*<action name="ToggleMaximize" direction="value" />*
+ Toggle maximize state of focused window. Supported directions are
+ "both" (default), "horizontal", and "vertical".
-*<action name="Maximize" />*
- Maximize focused window.
+*<action name="Maximize" direction="value" />*
+ Maximize focused window. Supported directions are "both" (default),
+ "horizontal", and "vertical".
*<action name="ToggleAlwaysOnTop" />*
Toggle always-on-top of focused window.
<mousebind button="Right" action="Click">
<action name="Focus" />
<action name="Raise" />
- <action name="ShowMenu" menu="client-menu" />
</mousebind>
</context>
<mousebind button="Left" action="DoubleClick">
<action name="ToggleMaximize" />
</mousebind>
+ <mousebind button="Right" action="Click">
+ <action name="ShowMenu" menu="client-menu" />
+ </mousebind>
</context>
<context name="Maximize">
<mousebind button="Left" action="Click">
- <action name="Focus" />
- <action name="Raise" />
<action name="ToggleMaximize" />
</mousebind>
+ <mousebind button="Right" action="Click">
+ <action name="ToggleMaximize" direction="horizontal" />
+ </mousebind>
+ <mousebind button="Middle" action="Click">
+ <action name="ToggleMaximize" direction="vertical" />
+ </mousebind>
</context>
<context name="WindowMenu">
<mousebind button="Left" action="Click">
<action name="ShowMenu" menu="client-menu" />
</mousebind>
+ <mousebind button="Right" action="Click">
+ <action name="ShowMenu" menu="client-menu" />
+ </mousebind>
</context>
<context name="Iconify">
LAB_SSD_PREF_SERVER,
};
+/**
+ * Directions in which a view can be maximized. "None" is used
+ * internally to mean "not maximized" but is not valid in rc.xml.
+ * Therefore when parsing rc.xml, "None" means "Invalid".
+ */
+enum view_axis {
+ VIEW_AXIS_NONE = 0,
+ VIEW_AXIS_HORIZONTAL = (1 << 0),
+ VIEW_AXIS_VERTICAL = (1 << 1),
+ VIEW_AXIS_BOTH = (VIEW_AXIS_HORIZONTAL | VIEW_AXIS_VERTICAL),
+};
+
enum view_edge {
VIEW_EDGE_INVALID = 0,
bool ssd_titlebar_hidden;
enum ssd_preference ssd_preference;
bool minimized;
- bool maximized;
+ enum view_axis maximized;
bool fullscreen;
uint32_t tiled; /* private, enum view_edge in src/view.c */
bool inhibits_keybinds;
void view_center(struct view *view, const struct wlr_box *ref);
void view_restore_to(struct view *view, struct wlr_box geometry);
void view_set_untiled(struct view *view);
-void view_maximize(struct view *view, bool maximize,
+void view_maximize(struct view *view, enum view_axis axis,
bool store_natural_geometry);
void view_set_fullscreen(struct view *view, bool fullscreen);
-void view_toggle_maximize(struct view *view);
+void view_toggle_maximize(struct view *view, enum view_axis axis);
void view_toggle_decorations(struct view *view);
bool view_is_always_on_top(struct view *view);
void view_on_output_destroy(struct view *view);
void view_destroy(struct view *view);
+enum view_axis view_axis_parse(const char *direction);
enum view_edge view_edge_parse(const char *direction);
/* xdg.c */
goto cleanup;
}
break;
+ case ACTION_TYPE_TOGGLE_MAXIMIZE:
+ case ACTION_TYPE_MAXIMIZE:
+ if (!strcmp(argument, "direction")) {
+ enum view_axis axis = view_axis_parse(content);
+ if (axis == VIEW_AXIS_NONE) {
+ 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;
case ACTION_TYPE_RESIZE_RELATIVE:
if (!strcmp(argument, "left") || !strcmp(argument, "right") ||
!strcmp(argument, "top") || !strcmp(argument, "bottom")) {
break;
case ACTION_TYPE_TOGGLE_MAXIMIZE:
if (view) {
- view_toggle_maximize(view);
+ enum view_axis axis = action_get_int(action,
+ "direction", VIEW_AXIS_BOTH);
+ view_toggle_maximize(view, axis);
}
break;
case ACTION_TYPE_MAXIMIZE:
if (view) {
- view_maximize(view, true, /*store_natural_geometry*/ true);
+ enum view_axis axis = action_get_int(action,
+ "direction", VIEW_AXIS_BOTH);
+ view_maximize(view, axis,
+ /*store_natural_geometry*/ true);
}
break;
case ACTION_TYPE_TOGGLE_FULLSCREEN:
{ "Title", "Left", "DoubleClick", "ToggleMaximize", NULL, NULL },
{ "TitleBar", "Right", "Click", "Focus", NULL, NULL},
{ "TitleBar", "Right", "Click", "Raise", NULL, NULL},
- { "TitleBar", "Right", "Click", "ShowMenu", "menu", "client-menu"},
+ { "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"},
{
struct view *view = wl_container_of(listener, view, toplevel.maximize);
struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data;
- view_maximize(view, event->maximized, /*store_natural_geometry*/ true);
+ view_maximize(view, event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
+ /*store_natural_geometry*/ true);
}
static void
*/
return;
}
- if (view->maximized || view_is_tiled(view)) {
+ if (!view_is_floating(view)) {
/*
* Un-maximize and restore natural width/height.
* Don't reset tiled state yet since we may want
cursor_set(seat, LAB_CURSOR_GRAB);
break;
case LAB_INPUT_STATE_RESIZE:
- if (view->maximized || view->fullscreen) {
+ if (view->fullscreen || view->maximized == VIEW_AXIS_BOTH) {
/*
- * We don't allow resizing while maximized or
- * fullscreen.
+ * We don't allow resizing while fullscreen or
+ * maximized in both directions.
*/
return;
}
/*
- * Reset tiled state but keep the same geometry as the
- * starting point for the resize.
+ * If tiled or maximized in only one direction, reset
+ * tiled/maximized state but keep the same geometry as
+ * the starting point for the resize.
*/
view_set_untiled(view);
+ view_restore_to(view, view->pending);
cursor_set(seat, cursor_get_from_edge(edges));
break;
default:
/*store_natural_geometry*/ false);
} else if (cursor_y <= area->y + snap_range) {
if (rc.snap_top_maximize) {
- view_maximize(view, true,
+ view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ false);
} else {
view_snap_to_edge(view, VIEW_EDGE_UP,
struct view *v;
int p = max;
for_each_view(v, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
- if (v == view || v->output != output || v->minimized || v->maximized) {
+ if (v == view || v->output != output || v->minimized
+ || v->maximized == VIEW_AXIS_BOTH) {
continue;
}
struct theme *theme = view->server->theme;
- if (view->maximized) {
+ if (view->maximized == VIEW_AXIS_BOTH) {
struct border thickness = { 0 };
if (!view->ssd_titlebar_hidden) {
thickness.top += theme->title_height;
ssd_extents_update(ssd);
ssd->state.geometry = current;
}
- if (ssd->state.squared_corners != ssd->view->maximized) {
+ bool maximized = (ssd->view->maximized == VIEW_AXIS_BOTH);
+ if (ssd->state.squared_corners != maximized) {
ssd_border_update(ssd);
ssd_titlebar_update(ssd);
}
-(ssd->titlebar.height + theme->border_width), color);
} FOR_EACH_END
- if (view->maximized) {
+ if (view->maximized == VIEW_AXIS_BOTH) {
wlr_scene_node_set_enabled(&ssd->border.tree->node, false);
}
}
assert(ssd->border.tree);
struct view *view = ssd->view;
- if (view->maximized && ssd->border.tree->node.enabled) {
+ if (view->maximized == VIEW_AXIS_BOTH
+ && ssd->border.tree->node.enabled) {
/* Disable borders on maximize */
wlr_scene_node_set_enabled(&ssd->border.tree->node, false);
ssd->margin = ssd_thickness(ssd->view);
}
- if (view->maximized) {
+ if (view->maximized == VIEW_AXIS_BOTH) {
return;
} else if (!ssd->border.tree->node.enabled) {
/* And re-enabled them when unmaximized */
ssd->extents.tree = wlr_scene_tree_create(ssd->tree);
struct wlr_scene_tree *parent = ssd->extents.tree;
- if (view->maximized || view->fullscreen) {
+ if (view->fullscreen || view->maximized == VIEW_AXIS_BOTH) {
wlr_scene_node_set_enabled(&parent->node, false);
}
wl_list_init(&ssd->extents.parts);
ssd_extents_update(struct ssd *ssd)
{
struct view *view = ssd->view;
- if (view->maximized || view->fullscreen) {
+ if (view->fullscreen || view->maximized == VIEW_AXIS_BOTH) {
wlr_scene_node_set_enabled(&ssd->extents.tree->node, false);
return;
}
ssd_update_title(ssd);
- if (view->maximized) {
- set_squared_corners(ssd, view->maximized);
+ if (view->maximized == VIEW_AXIS_BOTH) {
+ set_squared_corners(ssd, true);
}
}
int width = view->current.width;
struct theme *theme = view->server->theme;
- if (view->maximized != ssd->state.squared_corners) {
- set_squared_corners(ssd, view->maximized);
+ bool maximized = (view->maximized == VIEW_AXIS_BOTH);
+ if (ssd->state.squared_corners != maximized) {
+ set_squared_corners(ssd, maximized);
}
if (width == ssd->state.geometry.width) {
view_resize_relative(struct view *view, int left, int right, int top, int bottom)
{
assert(view);
- if (view->fullscreen || view->maximized) {
+ if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
struct wlr_box newgeo = view->pending;
if (view->fullscreen) {
return;
}
- view_maximize(view, false, /*store_natural_geometry*/ false);
+ view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
if (view_is_tiled(view)) {
view_set_untiled(view);
view_restore_to(view, view->natural_geometry);
if (!output_is_usable(pending_output)) {
return;
}
- if (view->fullscreen) {
- view_set_fullscreen(view, false);
- }
- if (view->maximized) {
- view_maximize(view, false, /*store_natural_geometry*/ false);
- }
+ view_set_fullscreen(view, false);
+ view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
if (view_is_tiled(view)) {
view_set_untiled(view);
view_restore_to(view, view->natural_geometry);
view_apply_maximized_geometry(struct view *view)
{
assert(view);
- assert(view->maximized);
+ assert(view->maximized != VIEW_AXIS_NONE);
struct output *output = view->output;
assert(output_is_usable(output));
box.width /= output->wlr_output->scale;
}
+ /*
+ * If one axis (horizontal or vertical) is unmaximized, it
+ * should use the natural geometry. But if that geometry is not
+ * on-screen on the output where the view is maximized, then
+ * center the unmaximized axis.
+ */
+ struct wlr_box natural = view->natural_geometry;
+ if (view->maximized != VIEW_AXIS_BOTH) {
+ struct wlr_box intersect;
+ wlr_box_intersection(&intersect, &box, &natural);
+ if (wlr_box_empty(&intersect)) {
+ view_compute_centered_position(view, NULL,
+ natural.width, natural.height,
+ &natural.x, &natural.y);
+ }
+ }
+
if (view->ssd_enabled) {
struct border border = ssd_thickness(view);
box.x += border.left;
box.width -= border.right + border.left;
box.height -= border.top + border.bottom;
}
+
+ if (view->maximized == VIEW_AXIS_VERTICAL) {
+ box.x = natural.x;
+ box.width = natural.width;
+ } else if (view->maximized == VIEW_AXIS_HORIZONTAL) {
+ box.y = natural.y;
+ box.height = natural.height;
+ }
+
view_move_resize(view, box);
}
if (view->fullscreen) {
view_apply_fullscreen_geometry(view);
- } else if (view->maximized) {
+ } else if (view->maximized != VIEW_AXIS_NONE) {
view_apply_maximized_geometry(view);
} else if (view->tiled) {
view_apply_tiled_geometry(view);
/* For internal use only. Does not update geometry. */
static void
-set_maximized(struct view *view, bool maximized)
+set_maximized(struct view *view, enum view_axis maximized)
{
if (view->impl->maximize) {
- view->impl->maximize(view, maximized);
+ view->impl->maximize(view, (maximized == VIEW_AXIS_BOTH));
}
if (view->toplevel.handle) {
wlr_foreign_toplevel_handle_v1_set_maximized(
- view->toplevel.handle, maximized);
+ view->toplevel.handle, (maximized == VIEW_AXIS_BOTH));
}
view->maximized = maximized;
if (view->fullscreen) {
return;
}
- if (view->maximized) {
- set_maximized(view, false);
+ if (view->maximized != VIEW_AXIS_NONE) {
+ set_maximized(view, VIEW_AXIS_NONE);
}
view_move_resize(view, geometry);
}
view_is_floating(struct view *view)
{
assert(view);
- return !(view->fullscreen || view->maximized || view->tiled
- || view->tiled_region || view->tiled_region_evacuate);
+ return !(view->fullscreen || (view->maximized != VIEW_AXIS_NONE)
+ || view_is_tiled(view));
}
/* Reset tiled state of view without changing geometry */
}
void
-view_maximize(struct view *view, bool maximize, bool store_natural_geometry)
+view_maximize(struct view *view, enum view_axis axis,
+ bool store_natural_geometry)
{
assert(view);
- if (view->maximized == maximize) {
+ if (view->maximized == axis) {
return;
}
if (view->fullscreen) {
return;
}
- if (maximize) {
+ if (axis != VIEW_AXIS_NONE) {
/*
* Maximize via keybind or client request cancels
* interactive move/resize since we can't move/resize
* a maximized view.
*/
interactive_cancel(view);
- if (store_natural_geometry) {
+ if (store_natural_geometry && view_is_floating(view)) {
view_store_natural_geometry(view);
}
}
- set_maximized(view, maximize);
+ set_maximized(view, axis);
if (view_is_floating(view)) {
view_apply_natural_geometry(view);
} else {
}
void
-view_toggle_maximize(struct view *view)
+view_toggle_maximize(struct view *view, enum view_axis axis)
{
assert(view);
- view_maximize(view, !view->maximized,
- /*store_natural_geometry*/ true);
+ switch (axis) {
+ case VIEW_AXIS_HORIZONTAL:
+ case VIEW_AXIS_VERTICAL:
+ /* Toggle one axis (XOR) */
+ view_maximize(view, view->maximized ^ axis,
+ /*store_natural_geometry*/ true);
+ break;
+ case VIEW_AXIS_BOTH:
+ /*
+ * Maximize in both directions if unmaximized or partially
+ * maximized, otherwise unmaximize.
+ */
+ view_maximize(view, (view->maximized == VIEW_AXIS_BOTH) ?
+ VIEW_AXIS_NONE : VIEW_AXIS_BOTH,
+ /*store_natural_geometry*/ true);
+ break;
+ default:
+ break;
+ }
}
void
view_grow_to_edge(struct view *view, enum view_edge direction)
{
assert(view);
- if (view->fullscreen || view->maximized) {
+ /* TODO: allow grow to edge if maximized along the other axis */
+ if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
if (!output_is_usable(view->output)) {
view_shrink_to_edge(struct view *view, enum view_edge direction)
{
assert(view);
- if (view->fullscreen || view->maximized) {
+ /* TODO: allow shrink to edge if maximized along the other axis */
+ if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
if (!output_is_usable(view->output)) {
view_move_resize(view, geo);
}
+enum view_axis
+view_axis_parse(const char *direction)
+{
+ if (!direction) {
+ return VIEW_AXIS_NONE;
+ }
+ if (!strcasecmp(direction, "horizontal")) {
+ return VIEW_AXIS_HORIZONTAL;
+ } else if (!strcasecmp(direction, "vertical")) {
+ return VIEW_AXIS_VERTICAL;
+ } else if (!strcasecmp(direction, "both")) {
+ return VIEW_AXIS_BOTH;
+ } else {
+ return VIEW_AXIS_NONE;
+ }
+}
+
enum view_edge
view_edge_parse(const char *direction)
{
return;
}
- if (view->tiled == edge && !view->maximized) {
+ if (view->tiled == edge && view->maximized == VIEW_AXIS_NONE) {
/* We are already tiled for this edge and thus should switch outputs */
struct wlr_output *new_output = NULL;
struct wlr_output *current_output = output->wlr_output;
}
}
- if (view->maximized) {
+ if (view->maximized != VIEW_AXIS_NONE) {
/* Unmaximize + keep using existing natural_geometry */
- view_maximize(view, false, /*store_natural_geometry*/ false);
+ view_maximize(view, VIEW_AXIS_NONE,
+ /*store_natural_geometry*/ false);
} else if (store_natural_geometry) {
/* store current geometry as new natural_geometry */
view_store_natural_geometry(view);
return;
}
- if (view->maximized) {
+ if (view->maximized != VIEW_AXIS_NONE) {
/* Unmaximize + keep using existing natural_geometry */
- view_maximize(view, false, /*store_natural_geometry*/ false);
+ view_maximize(view, VIEW_AXIS_NONE,
+ /*store_natural_geometry*/ false);
} else if (store_natural_geometry) {
/* store current geometry as new natural_geometry */
view_store_natural_geometry(view);
if (!view->mapped && !view->output) {
view_set_output(view, output_nearest_to_cursor(view->server));
}
- view_maximize(view, xdg_toplevel_from_view(view)->requested.maximized,
+ bool maximized = xdg_toplevel_from_view(view)->requested.maximized;
+ view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
/*store_natural_geometry*/ true);
}
position_xdg_toplevel_view(view);
}
- if (!view->fullscreen && requested->fullscreen) {
- set_fullscreen_from_request(view, requested);
- } else if (!view->maximized && requested->maximized) {
- view_maximize(view, true,
- /*store_natural_geometry*/ true);
- }
+ set_fullscreen_from_request(view, requested);
+ view_maximize(view, requested->maximized ?
+ VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
+ /*store_natural_geometry*/ true);
/*
* Set initial "current" position directly before
view_set_decorations(view,
want_deco(xwayland_surface_from_view(view)));
}
- view_toggle_maximize(view);
+ view_toggle_maximize(view, VIEW_AXIS_BOTH);
}
static void
* 1. set fullscreen state
* 2. set decorations (depends on fullscreen state)
* 3. set maximized (geometry depends on decorations)
- *
- * TODO: support separate horizontal/vertical maximize
*/
- bool maximize = xwayland_surface->maximized_horz
- && xwayland_surface->maximized_vert;
view_set_fullscreen(view, xwayland_surface->fullscreen);
view_set_decorations(view, want_deco(xwayland_surface));
- view_maximize(view, maximize, /*store_natural_geometry*/ true);
+ enum view_axis axis = VIEW_AXIS_NONE;
+ if (xwayland_surface->maximized_horz) {
+ axis |= VIEW_AXIS_HORIZONTAL;
+ }
+ if (xwayland_surface->maximized_vert) {
+ axis |= VIEW_AXIS_VERTICAL;
+ }
+ view_maximize(view, axis, /*store_natural_geometry*/ true);
if (view->surface != xwayland_surface->surface) {
if (view->surface) {