swayfx-mosaic/tooling/probes/step20c-applied-state-input/applied-state-input.patch

417 lines
17 KiB
Diff

--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -444,7 +444,7 @@
// CONTRACT: one lab-owned reference at most; timeout and output teardown
// release even when no present event arrives. The lock prevents slot reuse.
// INVARIANT: bi_hold.buffer is non-NULL exactly while the probe owns a lock.
-static struct {
+struct bi_hold {
struct wlr_buffer *buffer;
struct wlr_output *output;
struct wl_event_source *timer;
@@ -452,23 +452,53 @@
uint64_t id;
uint64_t locked_ns;
int scroll;
-} bi_hold;
+};
+// WHY: a different output must never evict this output's probe reference.
+// CONTRACT: at most one owned slot for each fixed headless output.
+// INVARIANT: each non-NULL slot has exactly one matching unlock.
+static struct bi_hold bi_holds[2];
+static unsigned bi_select_phase;
static unsigned bi_reread_phase;
-
-static void bi_release_hold(const char *reason) {
- if (!bi_hold.buffer) return;
- sway_log(SWAY_INFO, "BI_RELEASE id=%" PRIu64 " reason=%s age_ns=%" PRIu64,
- bi_hold.id, reason, presentation_probe_now_ns() - bi_hold.locked_ns);
- if (bi_hold.timer) wl_event_source_remove(bi_hold.timer);
- wlr_buffer_unlock(bi_hold.buffer);
- memset(&bi_hold, 0, sizeof(bi_hold));
+static uint64_t asi_applied_epoch;
+
+// WHY: distinguish observed applied arrangements from newer logical state.
+// CONTRACT: this experimental counter covers only explicit call sites;
+// equal values never prove an exhaustive or native transaction generation.
+// INVARIANT: increment and read occur on the compositor event-loop thread.
+uint64_t asi_epoch(void) { return asi_applied_epoch; }
+void asi_mark(const char *reason) {
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ ++asi_applied_epoch;
+ sway_log(SWAY_INFO, "ASI_MUTATION epoch=%" PRIu64 " reason=%s ns=%" PRIu64,
+ asi_applied_epoch, reason,
+ (uint64_t)now.tv_sec * UINT64_C(1000000000) + now.tv_nsec);
+}
+
+static struct bi_hold *bi_slot(struct wlr_output *output) {
+ if (!output) return NULL;
+ if (strcmp(output->name, "HEADLESS-1") == 0) return &bi_holds[0];
+ if (strcmp(output->name, "HEADLESS-2") == 0) return &bi_holds[1];
+ return NULL;
+}
+
+static void bi_release_hold(struct bi_hold *hold, const char *reason) {
+ if (!hold || !hold->buffer) return;
+ struct wlr_buffer *buffer = hold->buffer;
+ uint64_t id = hold->id;
+ uint64_t age = presentation_probe_now_ns() - hold->locked_ns;
+ struct wl_event_source *timer = hold->timer;
+ memset(hold, 0, sizeof(*hold));
+ if (timer) wl_event_source_remove(timer);
+ sway_log(SWAY_INFO, "ASI_RELEASE id=%" PRIu64 " reason=%s age_ns=%" PRIu64,
+ id, reason, age);
+ wlr_buffer_unlock(buffer);
}
static int bi_timeout_hold(void *data) {
- bi_release_hold("timeout");
+ bi_release_hold(data, "timeout");
return 0;
}
-
static void presentation_probe_handle_commit(struct wl_listener *listener, void *data) {
struct sway_output *output = wl_container_of(listener, output, presentation_probe_commit);
@@ -509,14 +539,15 @@
}
struct sway_workspace *ws = output->current.active_workspace;
if (!ws || ws->current.layout != L_MOSAIC || !ws->mosaic ||
- !ws->mosaic->rows->length || bi_snapshots >= 80) {
+ !ws->mosaic->rows->length || bi_snapshots >= 3) {
return NULL;
}
struct mosaic_row *row = ws->mosaic->rows->items[0];
- bool transient = getenv("BI_INCLUDE_TRANSIENT") &&
- row->state.scroll_x == 1760 && ws->layers.tiling->node.x == -1120;
- if (row->state.scroll_x != 400 && row->state.scroll_x != 1280 &&
- !transient) {
+ int target = bi_select_phase == 1 ? 1280 : 400;
+ char trigger[PATH_MAX];
+ int tn = snprintf(trigger, sizeof(trigger), "%s/enable", dir);
+ if (bi_select_phase >= 3 || tn < 0 || (size_t)tn >= sizeof(trigger) ||
+ access(trigger, F_OK) != 0 || row->state.scroll_x != target) {
return NULL;
}
struct wlr_buffer *buffer = pending->buffer;
@@ -612,13 +643,13 @@
}
sway_log(SWAY_INFO, "BI_STAGE phase=%s ns=%" PRIu64
" output=%s workspace=%zu layout=%d scroll=%.0f tile_scene=%d,%d"
- " clip_valid=%d clip=%d,%d,%d,%d has_buffer=%d",
+ " clip_valid=%d clip=%d,%d,%d,%d has_buffer=%d epoch=%" PRIu64,
phase, presentation_probe_now_ns(), output->wlr_output->name,
ws ? ws->node.id : 0, ws ? (int)ws->current.layout : -1, scroll,
ws ? ws->layers.tiling->node.x : 0,
ws ? ws->layers.tiling->node.y : 0, clip_valid,
clip.x, clip.y, clip.width, clip.height,
- state && (state->committed & WLR_OUTPUT_STATE_BUFFER) && state->buffer);
+ state && (state->committed & WLR_OUTPUT_STATE_BUFFER) && state->buffer, asi_epoch());
}
static int output_repaint_timer_handler(void *data) {
@@ -659,8 +690,10 @@
}
}
- // INVARIANT: release an older probe lock before taking a new one.
- bi_release_hold("superseded");
+ // CONTRACT: observe first; only an accepted new-buffer commit on this
+ // same output may replace its older hold. Other-output and no-buffer
+ // commits leave the owned slot untouched.
+ struct bi_hold *hold = bi_slot(output->wlr_output);
uint64_t bi_id = 0;
struct wlr_buffer *bi_locked = bi_observe_buffer(output, &pending, &bi_id);
bi_expected_buffer = bi_locked;
@@ -678,25 +711,27 @@
wlr_buffer_unlock(bi_locked);
sway_log(SWAY_INFO, "BI_RELEASE id=%" PRIu64 " reason=commit_rejected", bi_id);
} else {
+ if (hold && hold->buffer) bi_release_hold(hold, "same_output_replaced");
struct wl_event_source *timer = wl_event_loop_add_timer(
- output->wlr_output->event_loop, bi_timeout_hold, NULL);
+ output->wlr_output->event_loop, bi_timeout_hold, hold);
if (!timer || wl_event_source_timer_update(timer, 2000) < 0) {
if (timer) wl_event_source_remove(timer);
wlr_buffer_unlock(bi_locked);
sway_log(SWAY_INFO, "BI_RELEASE id=%" PRIu64 " reason=timer_unavailable", bi_id);
} else {
- bi_hold.buffer = bi_locked;
- bi_hold.output = output->wlr_output;
- bi_hold.timer = timer;
- bi_hold.seq = output->wlr_output->commit_seq;
- bi_hold.id = bi_id;
- bi_hold.locked_ns = presentation_probe_now_ns();
+ hold->buffer = bi_locked;
+ hold->output = output->wlr_output;
+ hold->timer = timer;
+ hold->seq = output->wlr_output->commit_seq;
+ hold->id = bi_id;
+ hold->locked_ns = presentation_probe_now_ns();
+ ++bi_select_phase;
struct sway_workspace *ws = output->current.active_workspace;
struct mosaic_row *row = ws && ws->mosaic && ws->mosaic->rows->length ?
ws->mosaic->rows->items[0] : NULL;
- bi_hold.scroll = row ? row->state.scroll_x : -1;
- sway_log(SWAY_INFO, "BI_LOCKED id=%" PRIu64 " commit_seq=%" PRIu32,
- bi_id, bi_hold.seq);
+ hold->scroll = row ? row->state.scroll_x : -1;
+ sway_log(SWAY_INFO, "ASI_LOCKED id=%" PRIu64 " output=%s commit_seq=%" PRIu32,
+ bi_id, output->wlr_output->name, hold->seq);
}
}
}
@@ -834,8 +869,9 @@
}
static void begin_destroy(struct sway_output *output) {
- if (bi_hold.buffer && bi_hold.output == output->wlr_output) {
- bi_release_hold("output_destroy");
+ struct bi_hold *hold = bi_slot(output->wlr_output);
+ if (hold && hold->buffer && hold->output == output->wlr_output) {
+ bi_release_hold(hold, "output_destroy");
}
wl_list_remove(&output->layout_destroy.link);
@@ -887,23 +923,22 @@
output_event->when.tv_nsec,
output_event->presented, output_event->seq,
output_event->refresh, output_event->flags);
- if (bi_hold.buffer && bi_hold.output == output->wlr_output &&
- bi_hold.seq == output_event->commit_seq) {
+ struct bi_hold *hold = bi_slot(output->wlr_output);
+ if (hold && hold->buffer && hold->output == output->wlr_output &&
+ hold->seq == output_event->commit_seq) {
sway_log(SWAY_INFO, "BI_PRESENT_HOLD id=%" PRIu64
" commit_seq=%" PRIu32 " presented=%d valid=1",
- bi_hold.id, bi_hold.seq, output_event->presented);
+ hold->id, hold->seq, output_event->presented);
// CONTRACT: the owned lock is still held here. A second read is
// limited to visible, hidden, returned buffers that actually reach
// present while locked. A superseded buffer does not advance phase.
- bool reread = (bi_reread_phase == 0 && bi_hold.id >= 8 && bi_hold.scroll == 400) ||
- (bi_reread_phase == 1 && bi_hold.scroll == 1280) ||
- (bi_reread_phase == 2 && bi_hold.scroll == 400);
- if (reread) {
+ bool reread = bi_reread_phase < 3;
+ if (reread && getenv("ASI_MODE") && strcmp(getenv("ASI_MODE"), "2") == 0) {
const char *dir = getenv("BI_OUTPUT_DIR");
const size_t bytes = 800u * 600u * 4u;
unsigned char *pixels = malloc(bytes);
struct wlr_texture *tex = pixels ? wlr_texture_from_buffer(
- output->wlr_output->renderer, bi_hold.buffer) : NULL;
+ output->wlr_output->renderer, hold->buffer) : NULL;
bool read_ok = false;
if (tex) {
struct wlr_texture_read_pixels_options opts = {
@@ -915,7 +950,7 @@
}
char path[PATH_MAX];
int n = dir ? snprintf(path, sizeof(path),
- "%s/buffer-%" PRIu64 "-present.xrgb", dir, bi_hold.id) : -1;
+ "%s/buffer-%" PRIu64 "-present.xrgb", dir, hold->id) : -1;
size_t off = 0;
int fd = -1;
if (read_ok && n >= 0 && (size_t)n < sizeof(path)) {
@@ -931,12 +966,20 @@
}
}
sway_log(SWAY_INFO, "BI_PRESENT_READ id=%" PRIu64
- " status=%s bytes=%zu phase=%u", bi_hold.id,
+ " status=%s bytes=%zu phase=%u", hold->id,
off == bytes ? "READ" : "UNAVAILABLE", off, bi_reread_phase);
if (off == bytes) bi_reread_phase++;
free(pixels);
}
- bi_release_hold("present");
+ // CONTRACT: private fault injection retains one already-presented
+ // buffer until a later same-output commit or the two-second timeout.
+ // It exercises replacement ownership without changing render output.
+ if (getenv("ASI_TEST_HOLD_AFTER_PRESENT") && hold->id == 1) {
+ sway_log(SWAY_INFO, "ASI_TEST_HOLD id=%" PRIu64 " output=%s",
+ hold->id, output->wlr_output->name);
+ } else {
+ bi_release_hold(hold, "present");
+ }
}
if (!output->enabled || !output_event->presented) {
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -23,6 +23,8 @@
#include "sway/tree/workspace.h"
#include "list.h"
#include "log.h"
+
+extern void asi_mark(const char *reason);
#define POPIN_FACTOR 0.8f
@@ -1117,6 +1119,9 @@
wlr_scene_node_coords(popup->relative, &lx, &ly);
wlr_scene_node_set_position(node, lx, ly);
probe_clip_popup(node, popup);
+ // CONTRACT: mark only this applied popup arrangement site;
+ // other SceneFX and XDG mutations remain uncounted.
+ asi_mark("popup_position_clip_observed");
}
}
}
@@ -1244,6 +1249,7 @@
}
transaction_apply(server.queued_transaction);
arrange_root(root);
+ asi_mark("transaction_arrange_root_complete");
cursor_rebase_all();
transaction_destroy(server.queued_transaction);
start_animations();
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -4,6 +4,7 @@
#include <linux/input-event-codes.h>
#include <errno.h>
#include <time.h>
+#include <inttypes.h>
#include <strings.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_cursor_shape_v1.h>
@@ -27,10 +28,59 @@
#include "sway/scene_descriptor.h"
#include "sway/server.h"
#include "sway/tree/container.h"
+#include "sway/tree/mosaic.h"
#include "sway/tree/root.h"
#include "sway/tree/view.h"
#include "sway/tree/workspace.h"
#include "wlr-layer-shell-unstable-v1-protocol.h"
+
+extern uint64_t asi_epoch(void);
+
+// WHY: observe the executed scene hit, independently of grab delivery.
+// CONTRACT: pointers are logged only within the hit call; epoch covers only
+// the instrumented mutation sites and is not a native transaction ID.
+static void asi_hit(double lx, double ly, struct wlr_scene_node *node,
+ struct wlr_surface *surface) {
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ struct wlr_output *wlr_output = wlr_output_layout_output_at(root->output_layout, lx, ly);
+ struct sway_output *output = wlr_output ? wlr_output->data : NULL;
+ struct sway_workspace *ws = output ? output->current.active_workspace : NULL;
+ struct wlr_box clip = {0};
+ bool has_clip = ws && wlr_scene_tree_get_clip(ws->layers.tiling, &clip);
+ int logical_scroll = -1;
+ if (ws && ws->current.layout == L_MOSAIC && ws->mosaic &&
+ ws->mosaic->rows->length) {
+ struct mosaic_row *row = ws->mosaic->rows->items[0];
+ logical_scroll = row->state.scroll_x;
+ }
+ struct wlr_box popup_clip = {0};
+ bool popup_clip_valid = false;
+ bool root_popup_found = false;
+ struct wlr_scene_node *popup_node;
+ wl_list_for_each(popup_node, &root->layers.popup->children, link) {
+ if (scene_descriptor_try_get(popup_node, SWAY_SCENE_DESC_POPUP)) {
+ root_popup_found = true;
+ popup_clip_valid = wlr_scene_tree_get_clip(
+ wlr_scene_tree_from_node(popup_node), &popup_clip);
+ break;
+ }
+ }
+ sway_log(SWAY_INFO, "ASI_HIT ns=%" PRIu64 " epoch=%" PRIu64
+ " output=%s lx=%.2f ly=%.2f node=%p type=%d surface=%p"
+ " workspace=%zu logical_scroll=%d tile_scene=%d,%d"
+ " clip=%d,%d,%d,%d clip_valid=%d popup_root=%d"
+ " popup_clip=%d,%d,%d,%d popup_clip_valid=%d",
+ (uint64_t)now.tv_sec * UINT64_C(1000000000) + now.tv_nsec,
+ asi_epoch(), wlr_output ? wlr_output->name : "none", lx, ly,
+ (void *)node, node ? (int)node->type : -1, (void *)surface,
+ ws ? ws->node.id : 0, logical_scroll,
+ ws ? ws->layers.tiling->node.x : 0,
+ ws ? ws->layers.tiling->node.y : 0,
+ clip.x, clip.y, clip.width, clip.height, has_clip, root_popup_found,
+ popup_clip.x, popup_clip.y, popup_clip.width, popup_clip.height,
+ popup_clip_valid);
+}
/**
* Returns the node at the cursor's position. If there is a surface at that
@@ -40,6 +90,7 @@
struct sway_seat *seat, double lx, double ly,
struct wlr_surface **surface, double *sx, double *sy) {
struct wlr_scene_node *scene_node = NULL;
+ struct wlr_surface *asi_surface = NULL;
struct wlr_scene_node *node;
wl_list_for_each_reverse(node, &root->layer_tree->children, link) {
@@ -67,9 +118,11 @@
if (scene_surface) {
*surface = scene_surface->surface;
+ asi_surface = scene_surface->surface;
}
}
+ asi_hit(lx, ly, scene_node, asi_surface);
// determine what container we clicked on
struct wlr_scene_node *current = scene_node;
while (true) {
@@ -116,6 +169,7 @@
}
}
+ if (!scene_node) asi_hit(lx, ly, NULL, NULL);
// if we aren't on a container, determine what workspace we are on
struct wlr_output *wlr_output = wlr_output_layout_output_at(
root->output_layout, lx, ly);
@@ -347,6 +401,8 @@
pointer_motion(cursor, event->time_msec, &event->pointer->base, dx, dy,
dx, dy);
+ sway_log(SWAY_INFO, "ASI_WARP requested=%.6f,%.6f layout=%.2f,%.2f actual=%.2f,%.2f epoch=%" PRIu64,
+ event->x, event->y, lx, ly, cursor->cursor->x, cursor->cursor->y, asi_epoch());
}
void dispatch_cursor_button(struct sway_cursor *cursor,
@@ -356,6 +412,8 @@
time_msec = get_current_time_in_msec();
}
+ sway_log(SWAY_INFO, "ASI_BUTTON_REQUEST epoch=%" PRIu64 " x=%.2f y=%.2f button=%u state=%d",
+ asi_epoch(), cursor->cursor->x, cursor->cursor->y, button, (int)state);
seatop_button(cursor->seat, time_msec, device, button, state);
}
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -3,6 +3,7 @@
#include <string.h>
#include <strings.h>
#include <time.h>
+#include <inttypes.h>
#include <wlr/config.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
@@ -1540,10 +1541,22 @@
return NULL;
}
+extern uint64_t asi_epoch(void);
+
void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
uint32_t button, enum wl_pointer_button_state state) {
+ // WHY: an XDG grab can change final delivery independently of node-at.
+ // CONTRACT: this is focus before wlroots notification; the client log
+ // remains the evidence of actual receipt.
+ struct wlr_surface *focused = seat->wlr_seat->pointer_state.focused_surface;
seat->last_button_serial = wlr_seat_pointer_notify_button(seat->wlr_seat,
time_msec, button, state);
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ sway_log(SWAY_INFO, "ASI_DELIVERY ns=%" PRIu64 " epoch=%" PRIu64
+ " surface=%p serial=%u button=%u state=%d",
+ (uint64_t)now.tv_sec * UINT64_C(1000000000) + now.tv_nsec,
+ asi_epoch(), (void *)focused, seat->last_button_serial, button, (int)state);
}
void seat_consider_warp_to_focus(struct sway_seat *seat) {