uint32_t resize_edges, struct wlr_box *geom);
void edges_find_neighbors(struct border *nearest_edges, struct view *view,
- struct wlr_box target, struct output *output,
- edge_validator_t validator, bool use_pending, bool ignore_hidden);
+ struct wlr_box origin, struct wlr_box target,
+ struct output *output, edge_validator_t validator, bool ignore_hidden);
void edges_find_outputs(struct border *nearest_edges, struct view *view,
- struct wlr_box target, struct output *output,
- edge_validator_t validator, bool use_pending);
+ struct wlr_box origin, struct wlr_box target,
+ struct output *output, edge_validator_t validator);
void edges_adjust_move_coords(struct view *view, struct border edges,
int *x, int *y, bool use_pending);
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_SNAP_CONSTRAINTS_H
+#define LABWC_SNAP_CONSTRAINTS_H
+
+#include "common/border.h"
+#include "view.h"
+
+struct wlr_box;
+
+void snap_constraints_set(struct view *view,
+ enum view_edge direction, struct wlr_box geom);
+
+void snap_constraints_invalidate(struct view *view);
+
+void snap_constraints_update(struct view *view);
+
+struct wlr_box snap_constraints_effective(struct view *view,
+ enum view_edge direction);
+
+#endif /* LABWC_SNAP_CONSTRAINTS_H */
void snap_shrink_to_next_edge(struct view *view,
enum view_edge direction, struct wlr_box *geo);
+void snap_invalidate_edge_cache(struct view *view);
+void snap_update_cache_geometry(struct view *view);
+
#endif /* LABWC_SNAP_H */
void
edges_find_neighbors(struct border *nearest_edges, struct view *view,
- struct wlr_box target, struct output *output,
- edge_validator_t validator, bool use_pending, bool ignore_hidden)
+ struct wlr_box origin, struct wlr_box target,
+ struct output *output, edge_validator_t validator, bool ignore_hidden)
{
assert(view);
assert(validator);
struct border view_edges = { 0 };
struct border target_edges = { 0 };
- struct wlr_box *view_geom =
- use_pending ? &view->pending : &view->current;
-
- edges_for_target_geometry(&view_edges, view, *view_geom);
+ edges_for_target_geometry(&view_edges, view, origin);
edges_for_target_geometry(&target_edges, view, target);
struct view *v;
void
edges_find_outputs(struct border *nearest_edges, struct view *view,
- struct wlr_box target, struct output *output,
- edge_validator_t validator, bool use_pending)
+ struct wlr_box origin, struct wlr_box target,
+ struct output *output, edge_validator_t validator)
{
assert(view);
assert(validator);
struct border view_edges = { 0 };
struct border target_edges = { 0 };
- struct wlr_box *view_geom =
- use_pending ? &view->pending : &view->current;
-
- edges_for_target_geometry(&view_edges, view, *view_geom);
+ edges_for_target_geometry(&view_edges, view, origin);
edges_for_target_geometry(&target_edges, view, target);
struct output *o;
output_usable_area_in_layout_coords(o);
struct wlr_box ol;
- if (!wlr_box_intersection(&ol, view_geom, &usable) &&
+ if (!wlr_box_intersection(&ol, &origin, &usable) &&
!wlr_box_intersection(&ol, &target, &usable)) {
continue;
}
#include "labwc.h"
#include "regions.h"
#include "resize_indicator.h"
+#include "snap.h"
#include "view.h"
#include "window-rules.h"
'seat.c',
'server.c',
'session-lock.c',
+ 'snap-constraints.c',
'snap.c',
'tearing.c',
'theme.c',
if (rc.screen_edge_strength != 0) {
/* Find any relevant output edges encountered by this move */
- edges_find_outputs(&next_edges, view, target, NULL,
- check_edge_output, /* use_pending */ false);
+ edges_find_outputs(&next_edges, view,
+ view->current, target, NULL, check_edge_output);
}
if (rc.window_edge_strength != 0) {
/* Find any relevant window edges encountered by this move */
- edges_find_neighbors(&next_edges,
- view, target, NULL, check_edge_window,
- /* use_pending */ false, /* ignore_hidden */ true);
+ edges_find_neighbors(&next_edges, view, view->current, target,
+ NULL, check_edge_window, /* ignore_hidden */ true);
}
/* If any "best" edges were encountered during this move, snap motion */
if (rc.screen_edge_strength != 0) {
/* Find any relevant output edges encountered by this move */
- edges_find_outputs(&next_edges, view, *new_geom, NULL,
- check_edge_output, /* use_pending */ false);
+ edges_find_outputs(&next_edges, view,
+ view->current, *new_geom, NULL, check_edge_output);
}
if (rc.window_edge_strength != 0) {
/* Find any relevant window edges encountered by this move */
- edges_find_neighbors(&next_edges,
- view, *new_geom, NULL, check_edge_window,
- /* use_pending */ false, /* ignore_hidden */ true);
+ edges_find_neighbors(&next_edges, view, view->current, *new_geom,
+ NULL, check_edge_window, /* ignore_hidden */ true);
}
/* If any "best" edges were encountered during this move, snap motion */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+#include <assert.h>
+#include <wlr/util/box.h>
+#include "common/macros.h"
+#include "labwc.h"
+#include "snap-constraints.h"
+#include "view.h"
+
+/*
+ * When snapping to edges during a resize action, XDG clients can override the
+ * desired size to honor arbitrary sizing constraints (*e.g.*, honoring some
+ * aspect ratio or ensuring that a terminal window consists of an integer
+ * number of character cells). In that case, the final position of the view
+ * edge may fall short of the edge to which it's snapped.
+ *
+ * This can prevent a subsequent resize action in the same direction from ever
+ * crossing the "missed" edge, because the next action will keep trying to hit
+ * the edge and the client will overriding the desired size. To compensate,
+ * every snapped resize will update a "last snapped" cache, recording the
+ * view that was resized, the position (and orientation) of the resized edge,
+ * and the expected geometry resulting from the snapped resize. At first, the
+ * expected geometry is the "pending" geometry that will be sent in a configure
+ * request. However, if the client overrides this pending geometry with some
+ * other, constrained value, the expectation should be updated (only once!) to
+ * reflect the size that the client chooses to honor.
+ *
+ * In subsequent snapped resize actions, if:
+ *
+ * 1. The view is the same as in the last attemped snapped resize;
+ * 2. The direction of resizing is the same as in the last attempt; and
+ * 3. The geometry of the view matches that expected from the last attempt;
+ *
+ * then the view geometry will be modified to reflect the *original* intended
+ * geometry from last snapped resize, which will allow the current attempt to
+ * progress beyond the "sticky" edge.
+ */
+static struct {
+ struct view *view;
+ bool pending;
+ int offset;
+ enum view_edge direction;
+ struct wlr_box geom;
+} last_snap_hit;
+
+static void
+snap_constraints_reset(void)
+{
+ last_snap_hit.view = NULL;
+ last_snap_hit.pending = false;
+ last_snap_hit.offset = INT_MIN;
+ last_snap_hit.direction = VIEW_EDGE_INVALID;
+ memset(&last_snap_hit.geom, 0, sizeof(last_snap_hit.geom));
+}
+
+static bool
+snap_constraints_are_valid(struct view *view, enum view_edge direction)
+{
+ assert(view);
+
+ /* Cache is not valid if view has changed */
+ if (view != last_snap_hit.view) {
+ return false;
+ }
+
+ /* Cache is not valid if direction has changed */
+ if (direction != last_snap_hit.direction) {
+ return false;
+ }
+
+ /* Cache is not valid if offset is unbounded */
+ if (!BOUNDED_INT(last_snap_hit.offset)) {
+ return false;
+ }
+
+ /* Cache is valid iff pending view geometry matches expectation */
+ return wlr_box_equal(&view->pending, &last_snap_hit.geom);
+}
+
+void
+snap_constraints_set(struct view *view,
+ enum view_edge direction, struct wlr_box geom)
+{
+ assert(view);
+
+ int offset = INT_MIN;
+ switch (direction) {
+ case VIEW_EDGE_LEFT:
+ offset = geom.x;
+ break;
+ case VIEW_EDGE_RIGHT:
+ offset = geom.x + geom.width;
+ break;
+ case VIEW_EDGE_UP:
+ offset = geom.y;
+ break;
+ case VIEW_EDGE_DOWN:
+ offset = geom.y + geom.height;
+ break;
+ default:
+ break;
+ }
+
+ if (!BOUNDED_INT(offset)) {
+ snap_constraints_reset();
+ return;
+ }
+
+ /* Capture the current geometry and effective snapped edge */
+ last_snap_hit.view = view;
+ last_snap_hit.offset = offset;
+ last_snap_hit.direction = direction;
+ last_snap_hit.geom = geom;
+
+ /*
+ * Client geometry change is pending, and XDG clients may adjust the
+ * geometry to match arbitrary constraints. Allow the client to update
+ * our concept of constraints exactly once after the configure request.
+ */
+ last_snap_hit.pending = true;
+}
+
+void
+snap_constraints_invalidate(struct view *view)
+{
+ if (view == last_snap_hit.view) {
+ snap_constraints_reset();
+ }
+}
+
+void
+snap_constraints_update(struct view *view)
+{
+ assert(view);
+
+ /* Ignore update if this is the wrong view */
+ if (view != last_snap_hit.view) {
+ return;
+ }
+
+ /* Never update constraints more than once */
+ if (!last_snap_hit.pending) {
+ return;
+ }
+
+ /* Only update constraints when view geometry matches expectation */
+ if (!wlr_box_equal(&view->pending, &last_snap_hit.geom)) {
+ return;
+ }
+
+ last_snap_hit.geom = view->current;
+ last_snap_hit.pending = false;
+}
+
+struct wlr_box
+snap_constraints_effective(struct view *view, enum view_edge direction)
+{
+ assert(view);
+
+ /* Use actual geometry when constraints do not apply */
+ if (!snap_constraints_are_valid(view, direction)) {
+ return view->pending;
+ }
+
+ /* Override changing edge with constrained value */
+ struct wlr_box geom = view->pending;
+ switch (last_snap_hit.direction) {
+ case VIEW_EDGE_LEFT:
+ geom.x = last_snap_hit.offset;
+ break;
+ case VIEW_EDGE_RIGHT:
+ geom.width = last_snap_hit.offset - geom.x;
+ break;
+ case VIEW_EDGE_UP:
+ geom.y = last_snap_hit.offset;
+ break;
+ case VIEW_EDGE_DOWN:
+ geom.height = last_snap_hit.offset - geom.y;
+ break;
+ default:
+ return view->pending;
+ }
+
+ /* Fall back to actual geometry when constrained geometry is nonsense */
+ if (geom.width <= 0 || geom.height <= 0) {
+ return view->pending;
+ }
+
+ return geom;
+}
#include <wlr/util/box.h>
#include "common/border.h"
#include "common/macros.h"
-#include "config/rcxml.h"
#include "edges.h"
#include "labwc.h"
-#include "resistance.h"
+#include "snap-constraints.h"
#include "snap.h"
#include "view.h"
struct border next_edges;
edges_initialize(&next_edges);
- edges_find_neighbors(&next_edges,
- view, target, output, check_edge,
- /* use_pending */ true, /* ignore_hidden */ false);
+ edges_find_neighbors(&next_edges, view, view->pending, target,
+ output, check_edge, /* ignore_hidden */ false);
/* If any "best" edges were encountered, limit motion */
edges_adjust_move_coords(view, next_edges,
}
void
-snap_grow_to_next_edge(struct view *view, enum view_edge direction,
- struct wlr_box *geo)
+snap_grow_to_next_edge(struct view *view,
+ enum view_edge direction, struct wlr_box *geo)
{
assert(view);
assert(!view->shaded);
struct border next_edges;
edges_initialize(&next_edges);
+ /* Use a constrained, effective geometry for snapping if appropriate */
+ struct wlr_box origin = snap_constraints_effective(view, direction);
+
/* Limit motion to any intervening edge of other views on this output */
- edges_find_neighbors(&next_edges,
- view, *geo, output, check_edge,
- /* use_pending */ true, /* ignore_hidden */ false);
+ edges_find_neighbors(&next_edges, view, origin, *geo,
+ output, check_edge, /* ignore_hidden */ false);
+
edges_adjust_resize_geom(view, next_edges,
resize_edges, geo, /* use_pending */ true);
+
+ /*
+ * Record effective geometry after snapping in case the client opts to
+ * ignore or modify the configured geometry
+ */
+ snap_constraints_set(view, direction, *geo);
}
void
-snap_shrink_to_next_edge(struct view *view, enum view_edge direction,
- struct wlr_box *geo)
+snap_shrink_to_next_edge(struct view *view,
+ enum view_edge direction, struct wlr_box *geo)
{
assert(view);
assert(!view->shaded);
struct border next_edges;
edges_initialize(&next_edges);
+ /* Use a constrained, effective geometry for snapping if appropriate */
+ struct wlr_box origin = snap_constraints_effective(view, direction);
+
/* Snap to output edges if the moving edge started off-screen */
- edges_find_outputs(&next_edges, view, *geo,
- view->output, check_edge, /* use_pending */ true);
+ edges_find_outputs(&next_edges, view,
+ origin, *geo, view->output, check_edge);
/* Limit motion to any intervening edge of ther views on this output */
- edges_find_neighbors(&next_edges,
- view, *geo, view->output, check_edge,
- /* use_pending */ true, /* ignore_hidden */ false);
+ edges_find_neighbors(&next_edges, view, origin, *geo,
+ view->output, check_edge, /* ignore_hidden */ false);
edges_adjust_resize_geom(view, next_edges,
resize_edges, geo, /* use_pending */ true);
+
+ /*
+ * Record effective geometry after snapping in case the client opts to
+ * ignore or modify the configured geometry
+ */
+ snap_constraints_set(view, direction, *geo);
}
#include "placement.h"
#include "regions.h"
#include "resize_indicator.h"
+#include "snap-constraints.h"
#include "snap.h"
#include "ssd.h"
#include "view.h"
assert(view);
struct server *server = view->server;
+ snap_constraints_invalidate(view);
+
if (view->mappable.connected) {
mappable_disconnect(&view->mappable);
}
#include "decorations.h"
#include "labwc.h"
#include "node.h"
+#include "snap-constraints.h"
#include "view.h"
#include "view-impl-common.h"
#include "window-rules.h"
* actual view.
*/
if (!view->pending_configure_serial) {
+ snap_constraints_update(view);
view->pending = view->current;
/*
view->current.width, view->current.height);
/* Re-sync pending view with current state */
+ snap_constraints_update(view);
view->pending = view->current;
return 0; /* ignored per wl_event_loop docs */