add workaround for river wm rotation issue
This commit is contained in:
@@ -76,6 +76,32 @@ pixels. `render_output()` allocates a device-pixel buffer of `width * scale` and
|
|||||||
shorter output dimension (`att_grid()`), which is what makes rotation and arbitrary aspect ratios
|
shorter output dimension (`att_grid()`), which is what makes rotation and arbitrary aspect ratios
|
||||||
work with no special cases.
|
work with no special cases.
|
||||||
|
|
||||||
|
**Output resize / rotation** — the surface size comes from `configure` and *only* from `configure`:
|
||||||
|
ext-session-lock makes a buffer that does not match the acked size a `dimensions_mismatch` error,
|
||||||
|
which kills the client and leaves the session locked with nothing drawing on it. So the locker can
|
||||||
|
never resize itself on its own initiative.
|
||||||
|
|
||||||
|
That matters because not every compositor reconfigures. River 0.4.5 configures a lock surface once,
|
||||||
|
when it is created, and never again (`LockSurface.create()` is the only `configure()` call site, and
|
||||||
|
nothing in `Output.zig` re-issues one), so rotating or re-moding an output used to leave the locker
|
||||||
|
painting at the old size, clipped to the overlap. `att_lock` therefore watches `wl_output`
|
||||||
|
(`mode`/`geometry`/`scale`), and if the output changed shape but no configure arrives within
|
||||||
|
`RESIZE_GRACE_MS`, `recreate_lock_surface()` destroys the lock surface and makes a new one for the
|
||||||
|
same output, which forces a fresh configure at the current size. The lock object is untouched, so
|
||||||
|
the session stays locked across the swap.
|
||||||
|
|
||||||
|
Two guards keep this from misfiring, and both matter:
|
||||||
|
|
||||||
|
- The change detector compares `wl_output` values against a snapshot of `wl_output` values taken at
|
||||||
|
the last configure (`cfg_*`), never against the configured surface size. On a fractionally scaled
|
||||||
|
output the integer `wl_output.scale` cannot reproduce the logical size, so those two would never
|
||||||
|
match and every idle moment would look like a pending resize.
|
||||||
|
- `retry_*` records the geometry a recreate was already attempted for, so a compositor that ignores
|
||||||
|
the recreate is nagged once instead of being put in a destroy/create loop.
|
||||||
|
|
||||||
|
A compositor that does send a configure never reaches any of this: by the time the grace period
|
||||||
|
expires the snapshot matches again and `outputs_recheck_geometry()` does nothing.
|
||||||
|
|
||||||
**Pattern model** (`src/pattern.c`) — a sequence of dot indices (`row * 4 + col`). `pattern_add()`
|
**Pattern model** (`src/pattern.c`) — a sequence of dot indices (`row * 4 + col`). `pattern_add()`
|
||||||
implements the Android rule that unvisited dots lying exactly on the straight line between the
|
implements the Android rule that unvisited dots lying exactly on the straight line between the
|
||||||
previous and new dot get captured first, using the gcd of the row/col delta. `pattern_to_string()`
|
previous and new dot get captured first, using the gcd of the row/col delta. `pattern_to_string()`
|
||||||
|
|||||||
@@ -63,6 +63,20 @@ struct att_output {
|
|||||||
int32_t width, height; /* logical size from configure */
|
int32_t width, height; /* logical size from configure */
|
||||||
bool configured;
|
bool configured;
|
||||||
struct att_buffer buffers[2];
|
struct att_buffer buffers[2];
|
||||||
|
|
||||||
|
/* Output geometry as last advertised by wl_output. Only ever compared
|
||||||
|
* against the `cfg_*` snapshot below, never used to size a buffer: the
|
||||||
|
* buffer size must come from configure alone, and with fractional scale
|
||||||
|
* the integer wl_output.scale cannot reproduce the logical size anyway. */
|
||||||
|
int32_t mode_w, mode_h, transform;
|
||||||
|
|
||||||
|
/* Same four values as of the most recent configure. A difference means
|
||||||
|
* the output changed and the compositor has not reconfigured us. */
|
||||||
|
int32_t cfg_mode_w, cfg_mode_h, cfg_transform, cfg_scale;
|
||||||
|
|
||||||
|
/* Geometry we last recreated the lock surface for, so a compositor that
|
||||||
|
* ignores the recreate is nagged once rather than forever. */
|
||||||
|
int32_t retry_mode_w, retry_mode_h, retry_transform, retry_scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct att_seat {
|
struct att_seat {
|
||||||
@@ -111,6 +125,9 @@ struct att {
|
|||||||
int fail_count; /* failed unlock attempts, pattern or password */
|
int fail_count; /* failed unlock attempts, pattern or password */
|
||||||
int64_t lockout_until; /* CLOCK_MONOTONIC ms; 0 when not locked out */
|
int64_t lockout_until; /* CLOCK_MONOTONIC ms; 0 when not locked out */
|
||||||
|
|
||||||
|
/* CLOCK_MONOTONIC ms at which to re-examine output geometry, or 0. */
|
||||||
|
int64_t resize_recheck_at;
|
||||||
|
|
||||||
/* configuration */
|
/* configuration */
|
||||||
const char *image_path;
|
const char *image_path;
|
||||||
const char *pam_service;
|
const char *pam_service;
|
||||||
|
|||||||
+150
-6
@@ -20,6 +20,12 @@ static void schedule_redraw(struct att *app)
|
|||||||
app->dirty = true;
|
app->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* How long to wait for a compositor to send the configure that an output
|
||||||
|
* geometry change should produce, before forcing one. Long enough not to race a
|
||||||
|
* compositor that reconfigures in a later event batch, short enough that a
|
||||||
|
* rotated screen is not left unusable. */
|
||||||
|
#define RESIZE_GRACE_MS 250
|
||||||
|
|
||||||
/* --- failed-attempt throttling -------------------------------------------- */
|
/* --- failed-attempt throttling -------------------------------------------- */
|
||||||
|
|
||||||
/* The pattern is checked against a local hash and never goes through PAM, so it
|
/* The pattern is checked against a local hash and never goes through PAM, so it
|
||||||
@@ -522,15 +528,26 @@ static const struct wl_seat_listener seat_listener = {
|
|||||||
|
|
||||||
/* --- output --------------------------------------------------------------- */
|
/* --- output --------------------------------------------------------------- */
|
||||||
|
|
||||||
static void out_geometry(void *d, struct wl_output *o, int32_t x, int32_t y,
|
static void out_geometry(void *data, struct wl_output *o, int32_t x, int32_t y,
|
||||||
int32_t pw, int32_t ph, int32_t subpixel,
|
int32_t pw, int32_t ph, int32_t subpixel,
|
||||||
const char *make, const char *model, int32_t transform)
|
const char *make, const char *model, int32_t transform)
|
||||||
{ (void)d; (void)o; (void)x; (void)y; (void)pw; (void)ph; (void)subpixel;
|
{
|
||||||
(void)make; (void)model; (void)transform; }
|
(void)o; (void)x; (void)y; (void)pw; (void)ph; (void)subpixel;
|
||||||
|
(void)make; (void)model;
|
||||||
|
struct att_output *out = data;
|
||||||
|
out->transform = transform;
|
||||||
|
}
|
||||||
|
|
||||||
static void out_mode(void *d, struct wl_output *o, uint32_t flags,
|
static void out_mode(void *data, struct wl_output *o, uint32_t flags,
|
||||||
int32_t w, int32_t h, int32_t refresh)
|
int32_t w, int32_t h, int32_t refresh)
|
||||||
{ (void)d; (void)o; (void)flags; (void)w; (void)h; (void)refresh; }
|
{
|
||||||
|
(void)o; (void)refresh;
|
||||||
|
struct att_output *out = data;
|
||||||
|
if (!(flags & WL_OUTPUT_MODE_CURRENT))
|
||||||
|
return;
|
||||||
|
out->mode_w = w;
|
||||||
|
out->mode_h = h;
|
||||||
|
}
|
||||||
|
|
||||||
static void out_scale(void *data, struct wl_output *o, int32_t factor)
|
static void out_scale(void *data, struct wl_output *o, int32_t factor)
|
||||||
{
|
{
|
||||||
@@ -539,7 +556,47 @@ static void out_scale(void *data, struct wl_output *o, int32_t factor)
|
|||||||
out->scale = factor > 0 ? factor : 1;
|
out->scale = factor > 0 ? factor : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void out_done(void *d, struct wl_output *o) { (void)d; (void)o; }
|
/* Record the output geometry that the current configure corresponds to. */
|
||||||
|
static void out_snapshot(struct att_output *o)
|
||||||
|
{
|
||||||
|
o->cfg_mode_w = o->mode_w;
|
||||||
|
o->cfg_mode_h = o->mode_h;
|
||||||
|
o->cfg_transform = o->transform;
|
||||||
|
o->cfg_scale = o->scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* True if the output changed shape since the configure we are drawing to.
|
||||||
|
* Deliberately compares wl_output values against wl_output values rather than
|
||||||
|
* against the configured surface size: on a fractionally scaled output the two
|
||||||
|
* never match, and that must not be mistaken for a pending resize. */
|
||||||
|
static bool out_geometry_changed(const struct att_output *o)
|
||||||
|
{
|
||||||
|
return o->mode_w != o->cfg_mode_w ||
|
||||||
|
o->mode_h != o->cfg_mode_h ||
|
||||||
|
o->transform != o->cfg_transform ||
|
||||||
|
o->scale != o->cfg_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void out_done(void *data, struct wl_output *wl_out)
|
||||||
|
{
|
||||||
|
(void)wl_out;
|
||||||
|
struct att_output *o = data;
|
||||||
|
|
||||||
|
if (!o->configured)
|
||||||
|
return; /* nothing to compare against yet */
|
||||||
|
if (o->cfg_mode_w <= 0) {
|
||||||
|
/* First configure landed before wl_output described the output;
|
||||||
|
* adopt this geometry as the baseline instead of treating it as
|
||||||
|
* a change. */
|
||||||
|
out_snapshot(o);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (out_geometry_changed(o)) {
|
||||||
|
/* Give a well-behaved compositor a moment to send the configure
|
||||||
|
* this change should produce; only step in if it never does. */
|
||||||
|
o->app->resize_recheck_at = att_now_ms() + RESIZE_GRACE_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
static void out_name(void *d, struct wl_output *o, const char *name)
|
static void out_name(void *d, struct wl_output *o, const char *name)
|
||||||
{ (void)d; (void)o; (void)name; }
|
{ (void)d; (void)o; (void)name; }
|
||||||
static void out_description(void *d, struct wl_output *o, const char *desc)
|
static void out_description(void *d, struct wl_output *o, const char *desc)
|
||||||
@@ -562,9 +619,22 @@ static void lock_surface_configure(void *data,
|
|||||||
{
|
{
|
||||||
struct att_output *o = data;
|
struct att_output *o = data;
|
||||||
ext_session_lock_surface_v1_ack_configure(surf, serial);
|
ext_session_lock_surface_v1_ack_configure(surf, serial);
|
||||||
|
|
||||||
|
bool resized = o->configured &&
|
||||||
|
((int32_t)width != o->width || (int32_t)height != o->height);
|
||||||
|
|
||||||
o->width = (int32_t)width;
|
o->width = (int32_t)width;
|
||||||
o->height = (int32_t)height;
|
o->height = (int32_t)height;
|
||||||
o->configured = true;
|
o->configured = true;
|
||||||
|
out_snapshot(o);
|
||||||
|
|
||||||
|
/* The grid moves with the surface, so a stroke started before the resize
|
||||||
|
* would carry its already-captured dots onto a grid that is no longer
|
||||||
|
* under the user's finger -- and completing it would count as a failed
|
||||||
|
* attempt. Drop it and let them start over. */
|
||||||
|
if (resized && o->app->pattern.drawing && o->app->active_output == o)
|
||||||
|
pattern_reset(&o->app->pattern);
|
||||||
|
|
||||||
render_output(o); /* must commit a buffer for the compositor to lock */
|
render_output(o); /* must commit a buffer for the compositor to lock */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,6 +653,66 @@ static void create_lock_surface(struct att *app, struct att_output *o)
|
|||||||
&lock_surface_listener, o);
|
&lock_surface_listener, o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tear the lock surface down and build a fresh one for the same output.
|
||||||
|
*
|
||||||
|
* Some compositors (river 0.4.5, for one) configure a lock surface exactly once
|
||||||
|
* when it is created and never again, so rotating or re-moding an output leaves
|
||||||
|
* us committing buffers at the old size -- the surface is then clipped to the
|
||||||
|
* overlap and the unlock UI ends up half off-screen. We cannot simply commit a
|
||||||
|
* correctly sized buffer instead: ext-session-lock requires the buffer to match
|
||||||
|
* the acked configure exactly, and a `dimensions_mismatch` error kills the
|
||||||
|
* client, which leaves the session locked with nothing drawing on it. Creating
|
||||||
|
* a new lock surface is in-spec and makes the compositor issue a configure with
|
||||||
|
* the output's current size.
|
||||||
|
*
|
||||||
|
* The lock object itself is untouched, so the session stays locked throughout. */
|
||||||
|
static void recreate_lock_surface(struct att *app, struct att_output *o)
|
||||||
|
{
|
||||||
|
if (!app->lock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (o->lock_surface) {
|
||||||
|
ext_session_lock_surface_v1_destroy(o->lock_surface);
|
||||||
|
o->lock_surface = NULL;
|
||||||
|
}
|
||||||
|
if (o->surface) {
|
||||||
|
wl_surface_destroy(o->surface);
|
||||||
|
o->surface = NULL;
|
||||||
|
}
|
||||||
|
/* The buffers outlive the surface on purpose: the compositor may still be
|
||||||
|
* scanning one out, and it releases them in its own time. buffer_get()
|
||||||
|
* reaps them once they come back at the wrong size. */
|
||||||
|
o->configured = false;
|
||||||
|
|
||||||
|
create_lock_surface(app, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void outputs_recheck_geometry(struct att *app)
|
||||||
|
{
|
||||||
|
struct att_output *o;
|
||||||
|
|
||||||
|
wl_list_for_each(o, &app->outputs, link) {
|
||||||
|
if (!o->configured || o->cfg_mode_w <= 0)
|
||||||
|
continue;
|
||||||
|
if (!out_geometry_changed(o))
|
||||||
|
continue; /* the compositor reconfigured us after all */
|
||||||
|
|
||||||
|
/* Recreated for this exact geometry already and still nothing:
|
||||||
|
* stop, rather than loop on a compositor we cannot satisfy. */
|
||||||
|
if (o->retry_mode_w == o->mode_w &&
|
||||||
|
o->retry_mode_h == o->mode_h &&
|
||||||
|
o->retry_transform == o->transform &&
|
||||||
|
o->retry_scale == o->scale)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
o->retry_mode_w = o->mode_w;
|
||||||
|
o->retry_mode_h = o->mode_h;
|
||||||
|
o->retry_transform = o->transform;
|
||||||
|
o->retry_scale = o->scale;
|
||||||
|
recreate_lock_surface(app, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void output_destroy(struct att_output *o)
|
static void output_destroy(struct att_output *o)
|
||||||
{
|
{
|
||||||
wl_list_remove(&o->link);
|
wl_list_remove(&o->link);
|
||||||
@@ -814,6 +944,20 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Same trick as the countdown: an output that changed shape has
|
||||||
|
* to wake us even with no input pending, or a rotation made
|
||||||
|
* while idle would stay broken until the user touched something. */
|
||||||
|
if (app.resize_recheck_at) {
|
||||||
|
int64_t left = app.resize_recheck_at - att_now_ms();
|
||||||
|
if (left > 0) {
|
||||||
|
if (timeout < 0 || left < timeout)
|
||||||
|
timeout = (int)left;
|
||||||
|
} else {
|
||||||
|
app.resize_recheck_at = 0;
|
||||||
|
outputs_recheck_geometry(&app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (app.dirty) {
|
if (app.dirty) {
|
||||||
app.dirty = false;
|
app.dirty = false;
|
||||||
render_all(&app);
|
render_all(&app);
|
||||||
|
|||||||
+9
-1
@@ -245,8 +245,16 @@ void render_output(struct att_output *o)
|
|||||||
int ph = o->height * scale;
|
int ph = o->height * scale;
|
||||||
|
|
||||||
struct att_buffer *b = buffer_get(app, o, pw, ph);
|
struct att_buffer *b = buffer_get(app, o, pw, ph);
|
||||||
if (!b)
|
if (!b) {
|
||||||
|
/* Both buffers are still held by the compositor. Ask for another
|
||||||
|
* pass instead of dropping the frame: with nothing else pending
|
||||||
|
* the loop blocks indefinitely, so a frame lost here -- the one
|
||||||
|
* right after a resize, most of all -- would never be repainted
|
||||||
|
* and the surface would keep showing its pre-resize contents.
|
||||||
|
* The pending wl_buffer.release wakes the poll. */
|
||||||
|
app->dirty = true;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cairo_surface_t *surface = cairo_image_surface_create_for_data(
|
cairo_surface_t *surface = cairo_image_surface_create_for_data(
|
||||||
b->data, CAIRO_FORMAT_ARGB32, pw, ph, pw * 4);
|
b->data, CAIRO_FORMAT_ARGB32, pw, ph, pw * 4);
|
||||||
|
|||||||
Reference in New Issue
Block a user