test(mosaic): preserve focused CORE-C V2 OOM and GLES gates
This commit is contained in:
parent
d46f854b30
commit
c55373eab3
49 changed files with 9163 additions and 0 deletions
59
tooling/probes/step20c-corec-v2-followup/README.md
Normal file
59
tooling/probes/step20c-corec-v2-followup/README.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# CORE-C V2 native closure follow-up — experimental source-only kit
|
||||
|
||||
This is the new, separately budgeted
|
||||
`SWAYFX_MOSAIC_STEP20C_COREC_V2_FULLSCREEN_OOM_GLES_NATIVE_CLOSURE_BATCH_V1`
|
||||
ticket. Its parent is project commit
|
||||
`d46f854b3026e911659528fe94792aa13837a1d6`. The
|
||||
[parent kit](../step20c-corec-v2/README.md) and its closed runtime receipts
|
||||
remain unchanged. This directory does not add a production dependency.
|
||||
|
||||
The follow-up derives prospective fullscreen, migration/refocus and root/nested
|
||||
OOM fixtures in `graphics/`, and a distinct intrusive GLES diagnostic in `gpu/`.
|
||||
Each implementation has its own offline tests. Independent acceptance of the
|
||||
final source bytes and verified publication to both project remotes are
|
||||
required before a runtime reservation. A source test or a successful build is
|
||||
not native runtime evidence. No campaign is admitted by this README.
|
||||
|
||||
The G2 Wayland client sets `RLIMIT_CORE` to zero, sets
|
||||
`PR_SET_DUMPABLE=0`, and verifies `PR_GET_DUMPABLE=0` before any Wayland
|
||||
operation. This protects the client itself; the compositor's `exec` does not
|
||||
preserve a parent's `prctl` dumpable setting. The parent also applies
|
||||
`RLIMIT_CORE=0` to owned children, while compositor dumpability after `exec`
|
||||
remains an explicit limitation.
|
||||
|
||||
Generated sources, clients, binaries, builds, receipts, logs, raw readbacks and
|
||||
captures belong in a new mode-0700 LAB below `/var/tmp`. Redirect `TMPDIR`,
|
||||
`TMP`, `TEMP`, `XDG_CACHE_HOME`, `XDG_DATA_HOME`, `PYTHONPYCACHEPREFIX` and
|
||||
`CCACHE_DIR` there for every build/test. Runtime sockets must be private,
|
||||
short, checked for quota, and explicitly selected. Never use an inherited
|
||||
desktop socket or display. Keep heavy artifacts outside HOME and Git.
|
||||
|
||||
The eight named reservations have fixed, nonfungible ceilings:
|
||||
|
||||
| Run | Output captures maximum | Dependency |
|
||||
| --- | ---: | --- |
|
||||
| FS-P | 12 | Reviewed comparable pristine fullscreen fixture |
|
||||
| FS-C | 16 | Same reviewed client and actual FS-P comparison |
|
||||
| DY-C | 16 | Reviewed native-source refocus oracle |
|
||||
| OM-ROOT | 10 | Reviewed root-only closure and availability oracle |
|
||||
| OM-NESTED | 10 | Safe root result, or explicit independent dependency review |
|
||||
| GPU-G3 | 12 | Reviewed capability/readback source and distinct built ELF |
|
||||
| OM-EXTRA | 12 | New distinct discriminant and additional review |
|
||||
| GPU-CTRL | 12 | New attribution question and additional review |
|
||||
|
||||
Maximum eight runs, 150 seconds each, 18 captures per run, and 110 output
|
||||
captures globally; the named ceilings sum to 100. A reservation is consumed
|
||||
even on failure. No retry, reset, refund, historical token or scenario renaming
|
||||
is permitted. Optional entries are not admitted by default. The previous
|
||||
ticket's unused reservations are closed and cannot be transferred.
|
||||
|
||||
CORE and OOM production mechanisms remain unchanged. Runtime Row 0 borrows
|
||||
Sway's real tiling list. Pending IPC geometry, applied scene/input evidence,
|
||||
native commits, presentation and buffer lifetime remain distinct. Stable
|
||||
screenshots cannot establish atomic generation, complete damage or GPU
|
||||
completion. OOM production policy and offscreen-popup UX remain undecided.
|
||||
|
||||
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED.
|
||||
PRODUCTION_MULTI_OUTPUT=NOT_YET_SUPPORTED; PRODUCTION_AUTHORIZED=FALSE.
|
||||
No installation, live session operation, production source/patch, public ABI,
|
||||
package recipe or UX change is authorized by this kit.
|
||||
132
tooling/probes/step20c-corec-v2-followup/gpu/README.md
Normal file
132
tooling/probes/step20c-corec-v2-followup/gpu/README.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# GPU-G3 — private GLES admission follow-up
|
||||
|
||||
EXPERIMENTAL_SOURCE_ONLY. This kit derives a new diagnostic from the published
|
||||
[G2 sources](../../step20c-corec-v2/gpu/README.md). It carries no G2 binary or
|
||||
runtime qualification. B4 is INCONCLUSIVE until an independent reviewer accepts
|
||||
the exact final bytes and receipts. Compositor build, staging and native GPU
|
||||
run are NOT_RUN. STEP20C remains NEXT; production multi-output is
|
||||
NOT_YET_SUPPORTED and PRODUCTION_AUTHORIZED is FALSE.
|
||||
|
||||
## Normative basis and bounded contract
|
||||
|
||||
[EGL 1.5](https://registry.khronos.org/EGL/specs/eglspec.1.5.pdf),
|
||||
§3.7.1.1, printed pp52–53 (PDF pages58–59), permits a later backwards compatible
|
||||
context. Its footnote18 identifies CLIENT_VERSION as the major-version alias.
|
||||
However §3.7.4, printed p63 (PDF page69), describes the context query as the
|
||||
supported client version. Thus EGL2/GL3.2 remains an observed divergence; this
|
||||
kit does **not** relabel the queried EGL value as merely a creation request or
|
||||
assert universal conformance. The independent reviewer must assess the limited
|
||||
diagnostic admission separately from that divergence.
|
||||
|
||||
[OpenGL ES 3.2, May 5 2022](https://registry.khronos.org/OpenGL/specs/es/3.2/es_spec_3.2.pdf),
|
||||
§20.2, printed pp441–442 (PDF pages458–459), specifies the VERSION string and
|
||||
matching MAJOR_VERSION/MINOR_VERSION queries. The code first validates current
|
||||
EGL context/display and ES API, then bounded GL_VERSION attesting ES3.0–3.2,
|
||||
then these ES3 queries and GLSL string without errors. ES2, ES4, unqualified
|
||||
ES3 minors, forged framing and contradictory integer results are refused.
|
||||
Installed headers define EGL_CONTEXT_CLIENT_VERSION and MAJOR_VERSION as
|
||||
0x3098; GLES3 MAJOR_VERSION/MINOR_VERSION are 0x821b/0x821c. Their hashes are
|
||||
recorded in the offline receipt; headers alone prove no native capability.
|
||||
|
||||
`g2_gl.h` keeps the exact `g2_read` symbol used by the unchanged G2 hooks.
|
||||
`g2_native.h` adds observable lifecycle flags and G3 metadata. Queries consume
|
||||
the GL error queue in separate pre/version/capability/state/attachment/read/
|
||||
restore windows. Error consumption cannot be restored. State is null when
|
||||
unobserved; attempted restoration requires a clean error window and exact
|
||||
state/context/display/API equality. Bindings restored are READ/DRAW FBO, RBO,
|
||||
PBO and PACK alignment/row/skip. READ_BUFFER is observed on the target and is
|
||||
never modified; returning the original FBO restores its original selection.
|
||||
Other queried state is unchanged. Only complete, single-sample native RBO
|
||||
color attachments with supported byte channels/dimensions are read. Texture
|
||||
attachment dimensions remain NOT_PROVEN and are refused. Black RGBA with
|
||||
alpha zero is valid. Functional blur shaders, sampler and rendering policy
|
||||
are unchanged. GLSL string is evidence about the implementation; the linked
|
||||
program's mediump/ESSL100 requirements remain tied to frozen shader sources,
|
||||
actual program/type/location, precision capability and uniform bits.
|
||||
|
||||
DMA-BUF FDs are borrowed and fstat identities are recorded without closing,
|
||||
seeking or decoding. AMD modifier020000001046bb04 accepts image stride3584
|
||||
offset0 and auxiliary stride1024 offset2293760. Only the image plane requires
|
||||
width×4; auxiliary allocation bounds/decoding remain NOT_PROVEN. Unknown
|
||||
modifiers are refused. `halfpixel` records native bits and capability; there
|
||||
is no fitted tolerance or mandatory CPU-float32 equality for mediump.
|
||||
|
||||
## Source reconstruction and offline commands
|
||||
|
||||
Start with the [CORE assembler](../../step20c-corec-v2/README.md) from a fresh
|
||||
Git extraction and its verified 85-file SceneFX tree. Use a NEW LAB created by
|
||||
mkdtemp under `/var/tmp/swayfx-mosaic-step20c-native-closure-*`, UID owner,
|
||||
mode0700. All generated files and every temp/cache directory stay there.
|
||||
Run commands with an allowlisted environment, for example:
|
||||
|
||||
```sh
|
||||
env -i PATH=/usr/bin:/bin LANG=C.UTF-8 \
|
||||
TMPDIR="$LAB/scratch" TMP="$LAB/scratch" TEMP="$LAB/scratch" \
|
||||
XDG_CACHE_HOME="$LAB/cache" XDG_DATA_HOME="$LAB/data" \
|
||||
PYTHONPYCACHEPREFIX="$LAB/pycache" CCACHE_DIR="$LAB/ccache" \
|
||||
python "$KIT/reproduce.py" --lab "$LAB" --core "$CORE/scene" \
|
||||
--destination "$LAB/replay-g3-scene"
|
||||
```
|
||||
|
||||
Create the listed environment directories first, mode0700. `$KIT` is this
|
||||
directory, `$CORE` the verified assembler destination. `reproduce.py` verifies
|
||||
every CORE byte and mode against the versioned provenance. It applies the
|
||||
relative parent kit's `gpu/g2-native.patch` with fixed SHA256
|
||||
77d7445ceaf643fd5cc708d31c3f2c77feaeb80f8130c419c55e5514ecc8a75d, then applies the G3 delta and verifies both diagnostic headers against
|
||||
the supplied full new headers. The hook source SHA remains
|
||||
c2886ef259fc91431dfe97841387082420ab80a0fe2f07e7dcdb71fc95731181.
|
||||
`g3-admission.patch` is the review delta between the parent and new headers;
|
||||
full new headers are the assembly authority and are explicitly frozen.
|
||||
|
||||
With the same allowlisted environment, run:
|
||||
|
||||
```sh
|
||||
python "$KIT/test-targeted.py" --lab "$LAB" --scene "$LAB/replay-g3-scene"
|
||||
python "$KIT/freeze.py" --lab "$LAB" --output "$LAB/g3-source-freeze-v2.json"
|
||||
```
|
||||
|
||||
This performs C17 FakeGL execution of the exact header, controlled wrong
|
||||
native variants, exact arm-consumer C extraction, actual native helper C,
|
||||
Python parser/scenario negative tests, source syntax checking, Wayland header
|
||||
generation and client syntax compilation. The client is not executed. It
|
||||
does not require compile_commands from any historical LAB. C processes set
|
||||
RLIMIT_CORE=0 and PR_SET_DUMPABLE=0. Generated tests, logs and receipts remain
|
||||
under `LAB/g3-offline`; the relative source manifest is
|
||||
`LAB/g3-source-freeze.json`. The freezer never grants its own review.
|
||||
|
||||
## Prospective native wiring — requires parent admission
|
||||
|
||||
Define `G3_ROOT` at compile time as the reviewed new LAB's exact
|
||||
`gpu-work/acquisition` path. There is deliberately no default absolute LAB.
|
||||
That definition is part of the new binary's reviewed identity. The C hook
|
||||
requires MOSAIC_G3_DIR to equal both the macro and realpath, mode0700. Python
|
||||
ROOT comes from MOSAIC_G3_DIR supplied **before import** and verifies the new
|
||||
LAB prefix. No runtime directory is created by this kit.
|
||||
|
||||
The parent must bind `scenario.run(session,binding)` to a **GPU-G3** Session
|
||||
whose sockets, binary/maps, watchdog, capture limits and durable ledger have
|
||||
been independently reviewed. `binding` requires `G3_ledger_reserved=True`,
|
||||
gate_file, binary, client, client_sha256, nonce and source_manifest_sha256.
|
||||
The Session binary category is `gpu` and its actual executable must be the
|
||||
new reviewed G3 binary. Environment variables MOSAIC_G3_DIR/NONCE belong in
|
||||
the Session's explicit compositor allowlist. The caller owns finally/cleanup.
|
||||
The parent must provide a gate mode0600 containing source_review=ACCEPTED,
|
||||
gl_observability=PASS, runtime_authorized=true, diagnostic=GPU-G3,
|
||||
acquisition_root, and exact binary_sha256. This kit never produces that gate.
|
||||
|
||||
Epochs A/B/A-return are single use, strictly ordered, capped at three. All
|
||||
ten physical readbacks per epoch are full800×600 RGBA (the prospective ROI is
|
||||
the complete field, without a post-hoc mask), total57,600,000 bytes, cap256MiB.
|
||||
There are two H2 baselines then three H1/H2 pairs: eight output captures.
|
||||
Every arm/raw/metadata publication is exclusive, nofollow and bounded.
|
||||
Rejected/incomplete A forbids B and return. Input commands are G3-A/G3-B/
|
||||
G3-A-return; client content is the unchanged alternating 8-pixel RGB pattern.
|
||||
Whole H1/H2 comparisons report zero-tolerance differences and row counts.
|
||||
Synchronous reads may perturb timing; GPU completion, presented-buffer
|
||||
identity, object lifetime, complete damage, atomic generation and whole-field
|
||||
causality remain NOT_PROVEN. A future build/run requires B4 and C gates;
|
||||
offline success does not consume or authorize a GPU-G3 run. The freezer's
|
||||
default output name is `g3-source-freeze.json`; it creates a new mode-0600 file
|
||||
exclusively, refuses any existing destination, and syncs the file and LAB
|
||||
directory. Use a new explicit direct-child name for every later freeze; a prior
|
||||
freeze is immutable evidence.
|
||||
358
tooling/probes/step20c-corec-v2-followup/gpu/clients-g2.c
Normal file
358
tooling/probes/step20c-corec-v2-followup/gpu/clients-g2.c
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
#define _GNU_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "layer-shell-client.h"
|
||||
#include "xdg-shell-client.h"
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
/* WHY: one persistent background supplies real A/B commits below blur_tree.
|
||||
* CONTRACT: fixed opaque background, premultiplied ARGB glass and static H2.
|
||||
* INVARIANT: no animation, synthetic configure, popup or held transaction. */
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct zwlr_layer_shell_v1 *shell;
|
||||
static struct wl_output *h1;
|
||||
static struct wl_surface *surface;
|
||||
static struct xdg_surface *xdg;
|
||||
static struct xdg_toplevel *top;
|
||||
static struct zwlr_layer_surface_v1 *layer;
|
||||
static const char *mode;
|
||||
static int width = 420, height = 320;
|
||||
static unsigned commits, live_buffers, released_buffers;
|
||||
static char pattern = 'A';
|
||||
/* G3 new explicit source command protocol; first A bytes retained verbatim. */
|
||||
static unsigned char *source_a;
|
||||
static size_t source_a_size;
|
||||
static const char *source_command;
|
||||
static uint32_t source_crc(const unsigned char *p,size_t n) {
|
||||
uint32_t crc=~0u;
|
||||
for(size_t i=0;i<n;i++) {crc^=p[i];for(int b=0;b<8;b++)crc=(crc>>1)^((0u-(crc&1u))&0xedb88320u);}
|
||||
return ~crc;
|
||||
}
|
||||
static volatile sig_atomic_t running = 1;
|
||||
struct pixels {
|
||||
struct wl_buffer *buffer;
|
||||
void *map;
|
||||
size_t size;
|
||||
struct pixels *next;
|
||||
};
|
||||
static struct pixels *buffers;
|
||||
static long long stamp(void) {
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
|
||||
}
|
||||
static void free_pixels(struct pixels *p) {
|
||||
struct pixels **it = &buffers;
|
||||
while (*it && *it != p)
|
||||
it = &(*it)->next;
|
||||
if (*it)
|
||||
*it = p->next;
|
||||
wl_buffer_destroy(p->buffer);
|
||||
munmap(p->map, p->size);
|
||||
free(p);
|
||||
--live_buffers;
|
||||
}
|
||||
static void release(void *data, struct wl_buffer *b) {
|
||||
++released_buffers;
|
||||
fprintf(stderr, "RELEASE ns=%lld buffer=%u\n", stamp(),
|
||||
wl_proxy_get_id((struct wl_proxy *)b));
|
||||
free_pixels(data);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release = release};
|
||||
static void draw(void) {
|
||||
/* CONTRACT: exactly one runner command per reserved A/B image.
|
||||
* INVARIANT: configures cannot draw, and the sequence is A/B/A only. */
|
||||
if (!strcmp(mode, "background") &&
|
||||
(width != 800 || height != 600 || commits >= 3 ||
|
||||
pattern != (commits == 1 ? 'B' : 'A')))
|
||||
exit(2);
|
||||
if (width <= 0 || height <= 0 || width > 4096 || height > 4096 ||
|
||||
live_buffers >= 8)
|
||||
exit(2);
|
||||
size_t size = (size_t)width * (size_t)height * 4;
|
||||
/* CONTRACT: each mapping stays immutable until native wl_buffer.release.
|
||||
* Bounds keep pool/stride integers valid, with at most eight outstanding. */
|
||||
char name[512];
|
||||
const char *runtime = getenv("TMPDIR");
|
||||
if (!runtime || strcmp(runtime,"/var/tmp/swayfx-mosaic-corec-v2-i516p6po/scratch") || snprintf(name, sizeof(name), "%s/g2-shm-XXXXXX", runtime) >=
|
||||
(int)sizeof(name))
|
||||
exit(2);
|
||||
int fd = mkstemp(name);
|
||||
if (fd < 0)
|
||||
exit(2);
|
||||
unlink(name);
|
||||
if (ftruncate(fd, (off_t)size) < 0)
|
||||
exit(2);
|
||||
struct pixels *p = calloc(1, sizeof(*p));
|
||||
if (!p)
|
||||
exit(2);
|
||||
p->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
p->size = size;
|
||||
if (p->map == MAP_FAILED)
|
||||
exit(2);
|
||||
uint32_t *pixels = p->map;
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int x = 0; x < width; ++x) {
|
||||
uint32_t color = 0x00303038;
|
||||
if (!strcmp(mode, "background"))
|
||||
color = pattern == 'A' ? (x / 8 % 2 ? 0x000f192d : 0x00f5f5f5)
|
||||
: (x / 8 % 2 ? 0x00b02040 : 0x0020c0e0);
|
||||
else if (!strcmp(mode, "glass"))
|
||||
color = 0x88442010;
|
||||
else if (!strcmp(mode, "filler"))
|
||||
color = 0x00000000;
|
||||
pixels[(size_t)y * (size_t)width + (size_t)x] = color;
|
||||
}
|
||||
if (!strcmp(mode,"background")) {
|
||||
if (!commits) {
|
||||
source_a=malloc(size);if(!source_a)exit(2);
|
||||
memcpy(source_a,p->map,size);source_a_size=size;
|
||||
} else if (commits==2) {
|
||||
if(!source_a || source_a_size!=size)exit(2);
|
||||
/* A-return is the preserved original payload, not a fitted redraw. */
|
||||
memcpy(p->map,source_a,size);
|
||||
} else if(source_a_size==size && !memcmp(source_a,p->map,size)) exit(2);
|
||||
fprintf(stderr,"G3_SOURCE_BYTES command=%s bytes=%zu crc32=%08x exact_original=%s\n",
|
||||
source_command,size,source_crc(p->map,size),commits==2?"true":"not-return");
|
||||
}
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)size);
|
||||
p->buffer = wl_shm_pool_create_buffer(
|
||||
pool, 0, width, height, width * 4,
|
||||
(!strcmp(mode, "glass") || !strcmp(mode, "filler"))
|
||||
? WL_SHM_FORMAT_ARGB8888
|
||||
: WL_SHM_FORMAT_XRGB8888);
|
||||
p->next = buffers;
|
||||
buffers = p;
|
||||
++live_buffers;
|
||||
wl_buffer_add_listener(p->buffer, &buffer_listener, p);
|
||||
wl_surface_attach(surface, p->buffer, 0, 0);
|
||||
wl_surface_damage_buffer(surface, 0, 0, width, height);
|
||||
wl_surface_commit(surface);
|
||||
fprintf(stderr,
|
||||
"CLIENT_COMMIT ns=%lld mode=%s surface=%u buffer=%u index=%u "
|
||||
"pattern=%c size=%dx%d damage=0,0,%d,%d\n",
|
||||
stamp(), mode, wl_proxy_get_id((struct wl_proxy *)surface),
|
||||
wl_proxy_get_id((struct wl_proxy *)p->buffer), ++commits, pattern,
|
||||
width, height, width, height);
|
||||
wl_shm_pool_destroy(pool);
|
||||
close(fd);
|
||||
}
|
||||
static void ping(void *d, struct xdg_wm_base *b, uint32_t s) {
|
||||
xdg_wm_base_pong(b, s);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
|
||||
static void top_configure(void *d, struct xdg_toplevel *t, int32_t w, int32_t h,
|
||||
struct wl_array *a) {
|
||||
if (w > 0)
|
||||
width = w;
|
||||
if (h > 0)
|
||||
height = h;
|
||||
}
|
||||
static void top_close(void *d, struct xdg_toplevel *t) { running = 0; }
|
||||
static const struct xdg_toplevel_listener top_listener = {
|
||||
.configure = top_configure, .close = top_close};
|
||||
static void configure(void *d, struct xdg_surface *s, uint32_t serial) {
|
||||
xdg_surface_ack_configure(s, serial);
|
||||
draw();
|
||||
}
|
||||
static const struct xdg_surface_listener xdg_listener = {.configure =
|
||||
configure};
|
||||
/* CONTRACT: configures ACK geometry, never submit an unreserved image.
|
||||
* WHY: duplicate layer configures must not spend autonomous mutations.
|
||||
* INVARIANT: the runner reserves then sends every A/B draw command. */
|
||||
static void layer_configure(void *d, struct zwlr_layer_surface_v1 *s,
|
||||
uint32_t serial, uint32_t w, uint32_t h) {
|
||||
if (w == 0 || h == 0 || w > 4096 || h > 4096)
|
||||
exit(2);
|
||||
if (commits && ((int)w != width || (int)h != height))
|
||||
exit(2);
|
||||
width = (int)w;
|
||||
height = (int)h;
|
||||
zwlr_layer_surface_v1_ack_configure(s, serial);
|
||||
fprintf(stderr, "BACKGROUND_CONFIGURED ns=%lld serial=%u size=%dx%d\n",
|
||||
stamp(), serial, width, height);
|
||||
}
|
||||
static void layer_close(void *d, struct zwlr_layer_surface_v1 *s) {
|
||||
running = 0;
|
||||
}
|
||||
static const struct zwlr_layer_surface_v1_listener layer_listener = {
|
||||
.configure = layer_configure, .closed = layer_close};
|
||||
static void output_name(void *d, struct wl_output *o, const char *name) {
|
||||
if (!strcmp(name, "HEADLESS-1"))
|
||||
h1 = o;
|
||||
}
|
||||
static void output_geometry(void *d, struct wl_output *o, int32_t x, int32_t y,
|
||||
int32_t w, int32_t h, int32_t sp, const char *m,
|
||||
const char *n, int32_t tr) {}
|
||||
static void output_mode(void *d, struct wl_output *o, uint32_t flags, int32_t w,
|
||||
int32_t h, int32_t refresh) {}
|
||||
static void output_done(void *d, struct wl_output *o) {}
|
||||
static void output_scale(void *d, struct wl_output *o, int32_t scale) {}
|
||||
static void output_description(void *d, struct wl_output *o, const char *desc) {
|
||||
}
|
||||
static const struct wl_output_listener output_listener = {
|
||||
.geometry = output_geometry,
|
||||
.mode = output_mode,
|
||||
.done = output_done,
|
||||
.scale = output_scale,
|
||||
.name = output_name,
|
||||
.description = output_description};
|
||||
static void global(void *d, struct wl_registry *r, uint32_t id,
|
||||
const char *name, uint32_t v) {
|
||||
if (!strcmp(name, "wl_compositor"))
|
||||
compositor =
|
||||
wl_registry_bind(r, id, &wl_compositor_interface, v < 4 ? v : 4);
|
||||
else if (!strcmp(name, "wl_shm"))
|
||||
shm = wl_registry_bind(r, id, &wl_shm_interface, 1);
|
||||
else if (!strcmp(name, "xdg_wm_base"))
|
||||
wm = wl_registry_bind(r, id, &xdg_wm_base_interface, v < 3 ? v : 3);
|
||||
else if (!strcmp(name, "zwlr_layer_shell_v1"))
|
||||
shell =
|
||||
wl_registry_bind(r, id, &zwlr_layer_shell_v1_interface, v < 2 ? v : 2);
|
||||
else if (!strcmp(name, "wl_output") && v >= 4) {
|
||||
struct wl_output *o = wl_registry_bind(r, id, &wl_output_interface, 4);
|
||||
wl_output_add_listener(o, &output_listener, NULL);
|
||||
}
|
||||
}
|
||||
static void removed(void *d, struct wl_registry *r, uint32_t id) {}
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = global, .global_remove = removed};
|
||||
static void stop(int s) { running = 0; }
|
||||
int main(int argc, char **argv) {
|
||||
/* WHY: Linux may route core dumps through a pipe handler that execs another
|
||||
* process, so a zero RLIMIT_CORE alone does not protect this client.
|
||||
* CONTRACT/INVARIANT: establish both limits before any Wayland operation. */
|
||||
struct rlimit no_core = {0, 0};
|
||||
if (setrlimit(RLIMIT_CORE, &no_core) < 0) {
|
||||
perror("setrlimit(RLIMIT_CORE)");
|
||||
return 1;
|
||||
}
|
||||
if (prctl(PR_SET_DUMPABLE, 0) < 0) {
|
||||
perror("prctl(PR_SET_DUMPABLE, 0)");
|
||||
return 1;
|
||||
}
|
||||
int dumpable = prctl(PR_GET_DUMPABLE);
|
||||
if (dumpable < 0) {
|
||||
perror("prctl(PR_GET_DUMPABLE)");
|
||||
return 1;
|
||||
}
|
||||
if (dumpable != 0) {
|
||||
fprintf(stderr, "PR_GET_DUMPABLE returned %d; expected 0\n", dumpable);
|
||||
return 1;
|
||||
}
|
||||
if (argc != 2)
|
||||
return 2;
|
||||
mode = argv[1];
|
||||
if (strcmp(mode, "background") && strcmp(mode, "glass") &&
|
||||
strcmp(mode, "filler") && strcmp(mode, "reference"))
|
||||
return 2;
|
||||
signal(SIGTERM, stop);
|
||||
display = wl_display_connect(NULL);
|
||||
if (!display)
|
||||
return 1;
|
||||
struct wl_registry *r = wl_display_get_registry(display);
|
||||
wl_registry_add_listener(r, ®istry_listener, NULL);
|
||||
if (wl_display_roundtrip(display) < 0 || wl_display_roundtrip(display) < 0)
|
||||
return 1;
|
||||
if (!compositor || !shm || !wm)
|
||||
return 2;
|
||||
xdg_wm_base_add_listener(wm, &wm_listener, NULL);
|
||||
surface = wl_compositor_create_surface(compositor);
|
||||
if (!strcmp(mode, "background")) {
|
||||
if (!shell || !h1)
|
||||
return 2;
|
||||
layer = zwlr_layer_shell_v1_get_layer_surface(
|
||||
shell, surface, h1, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
|
||||
"mosaic-dynamic-background");
|
||||
zwlr_layer_surface_v1_add_listener(layer, &layer_listener, NULL);
|
||||
zwlr_layer_surface_v1_set_size(layer, 0, 0);
|
||||
zwlr_layer_surface_v1_set_anchor(layer,
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
|
||||
zwlr_layer_surface_v1_set_exclusive_zone(layer, -1);
|
||||
zwlr_layer_surface_v1_set_keyboard_interactivity(layer, 0);
|
||||
} else {
|
||||
xdg = xdg_wm_base_get_xdg_surface(wm, surface);
|
||||
xdg_surface_add_listener(xdg, &xdg_listener, NULL);
|
||||
top = xdg_surface_get_toplevel(xdg);
|
||||
xdg_toplevel_add_listener(top, &top_listener, NULL);
|
||||
const char *app = !strcmp(mode, "reference") ? "org.mosaic.StaticReference"
|
||||
: !strcmp(mode, "glass") ? "org.mosaic.BlurGlass"
|
||||
: "org.mosaic.BlurFiller";
|
||||
xdg_toplevel_set_app_id(top, app);
|
||||
xdg_toplevel_set_title(top, app);
|
||||
}
|
||||
wl_surface_commit(surface);
|
||||
while (running) {
|
||||
if (wl_display_dispatch_pending(display) < 0)
|
||||
break;
|
||||
if (wl_display_flush(display) < 0 && errno != EAGAIN)
|
||||
break;
|
||||
struct pollfd fds[2] = {
|
||||
{.fd = wl_display_get_fd(display), .events = POLLIN},
|
||||
{.fd = STDIN_FILENO, .events = POLLIN}};
|
||||
int n = poll(fds, 2, 100);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (fds[0].revents & (POLLIN | POLLERR | POLLHUP))
|
||||
if (wl_display_dispatch(display) < 0)
|
||||
break;
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (!fgets(cmd, sizeof(cmd), stdin))
|
||||
break;
|
||||
if (!strcmp(mode, "background") &&
|
||||
((!commits && !strcmp(cmd,"G3-A\n")) ||
|
||||
(commits==1 && !strcmp(cmd,"G3-B\n")) ||
|
||||
(commits==2 && !strcmp(cmd,"G3-A-return\n")))) {
|
||||
pattern = commits==1?'B':'A';
|
||||
source_command=commits==0?"G3-A":commits==1?"G3-B":"G3-A-return";
|
||||
draw();
|
||||
fprintf(stderr, "COMMAND %c DONE ns=%lld\n", pattern, stamp());
|
||||
} else if (!strcmp(cmd, "close\n")) {
|
||||
fprintf(stderr,"CLOSE_RECEIVED\n");running = 0;
|
||||
}
|
||||
else if(!strcmp(mode,"background"))return 2;
|
||||
}
|
||||
}
|
||||
int error = wl_display_get_error(display);
|
||||
fprintf(stderr, "EXIT ns=%lld mode=%s error=%d commits=%u\n", stamp(), mode,
|
||||
error, commits);
|
||||
if (layer)
|
||||
zwlr_layer_surface_v1_destroy(layer);
|
||||
if (top)
|
||||
xdg_toplevel_destroy(top);
|
||||
if (xdg)
|
||||
xdg_surface_destroy(xdg);
|
||||
wl_surface_destroy(surface);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",commits,released_buffers);
|
||||
bool all_released=commits==released_buffers;
|
||||
wl_display_flush(display);
|
||||
while (buffers)
|
||||
free_pixels(buffers);
|
||||
free(source_a);
|
||||
wl_registry_destroy(r);
|
||||
wl_display_disconnect(display);
|
||||
return error || !all_released ? 1 : 0;
|
||||
}
|
||||
43
tooling/probes/step20c-corec-v2-followup/gpu/freeze.py
Normal file
43
tooling/probes/step20c-corec-v2-followup/gpu/freeze.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""Write a relative G3 source manifest outside Git; grants no review."""
|
||||
import argparse
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from reproduce import KIT, sha, tree
|
||||
|
||||
if __name__ == '__main__':
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--lab', type=Path, required=True)
|
||||
p.add_argument('--output', type=Path)
|
||||
a = p.parse_args()
|
||||
lab = a.lab.resolve(strict=True)
|
||||
assert str(lab).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-')
|
||||
files = tree(KIT)
|
||||
result = {'schema': 'G3-relative-source-freeze-v1', 'status': 'EXPERIMENTAL_SOURCE_ONLY',
|
||||
'review': 'PENDING_INDEPENDENT', 'build': 'NOT_RUN', 'runtime': 'NOT_RUN',
|
||||
'no_inherited_G2_qualification': True, 'files': files}
|
||||
out = a.output if a.output is not None else Path('g3-source-freeze.json')
|
||||
if not out.is_absolute():
|
||||
out = lab / out
|
||||
if out.parent.resolve(strict=True) != lab or out.name in ('', '.', '..'):
|
||||
p.error('--output must name a direct file below --lab')
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
||||
if hasattr(os, 'O_NOFOLLOW'):
|
||||
flags |= os.O_NOFOLLOW
|
||||
fd = os.open(out, flags, 0o600)
|
||||
try:
|
||||
payload = (json.dumps(result, indent=2) + '\n').encode()
|
||||
with os.fdopen(fd, 'wb') as stream:
|
||||
fd = -1
|
||||
stream.write(payload)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
dirfd = os.open(lab, os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0))
|
||||
try:
|
||||
os.fsync(dirfd)
|
||||
finally:
|
||||
os.close(dirfd)
|
||||
finally:
|
||||
if fd >= 0:
|
||||
os.close(fd)
|
||||
print(sha(out))
|
||||
152
tooling/probes/step20c-corec-v2-followup/gpu/g2_gl.h
Normal file
152
tooling/probes/step20c-corec-v2-followup/gpu/g2_gl.h
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/* G3 private admission delta; G2 hook symbols retained. No production API. */
|
||||
#ifndef G2_GL_H
|
||||
#define G2_GL_H
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#define G2_CAP (256u * 1024u * 1024u)
|
||||
#define G2_ERRORS 16
|
||||
struct g2_errors { GLenum values[G2_ERRORS]; unsigned count; bool exhausted; };
|
||||
struct g2_state {
|
||||
GLint read_fbo, draw_fbo, rbo, pack, pbo, row, skip_rows, skip_pixels;
|
||||
GLint viewport[4], scissor[4], program, active, texture;
|
||||
GLboolean scissor_on;
|
||||
};
|
||||
struct g2_sample {
|
||||
EGLContext context; EGLint client_version; EGLenum api;
|
||||
EGLDisplay display; char version[160], glsl[160]; GLint major, minor;
|
||||
bool capability_queried, version_queried, state_saved, bound, read_attempted, restore_attempted; struct g2_state before, after;
|
||||
struct g2_errors pre, version_errors, capability, state_errors, query, read, restore;
|
||||
GLint object_type, object, level, width, height, format, channels[4];
|
||||
GLint read_buffer, samples; GLenum status; size_t bytes; unsigned char *pixels;
|
||||
bool success; const char *reason;
|
||||
};
|
||||
static struct g2_errors g2_drain(void) {
|
||||
struct g2_errors e = {0};
|
||||
for (unsigned i=0;i<G2_ERRORS;i++) {
|
||||
GLenum v=glGetError(); if (v==GL_NO_ERROR) return e;
|
||||
e.values[e.count++]=v;
|
||||
}
|
||||
/* No unbounded probing: sixteen errors means attribution rejected. */
|
||||
e.exhausted=true; return e;
|
||||
}
|
||||
static bool g2_clean(struct g2_errors e) { return !e.count && !e.exhausted; }
|
||||
static bool g2_size(int w,int h,size_t used,size_t *n) {
|
||||
if(w<=0 || h<=0 || used>G2_CAP || (size_t)w>SIZE_MAX/4/(size_t)h) return false;
|
||||
*n=(size_t)w*(size_t)h*4; return *n<=G2_CAP-used;
|
||||
}
|
||||
static void g2_state_get(struct g2_state *s) {
|
||||
memset(s,0,sizeof(*s));
|
||||
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING,&s->read_fbo);
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING,&s->draw_fbo);
|
||||
glGetIntegerv(GL_RENDERBUFFER_BINDING,&s->rbo);
|
||||
glGetIntegerv(GL_PACK_ALIGNMENT,&s->pack);
|
||||
glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING,&s->pbo);
|
||||
glGetIntegerv(GL_PACK_ROW_LENGTH,&s->row);
|
||||
glGetIntegerv(GL_PACK_SKIP_ROWS,&s->skip_rows);
|
||||
glGetIntegerv(GL_PACK_SKIP_PIXELS,&s->skip_pixels);
|
||||
glGetIntegerv(GL_VIEWPORT,s->viewport); glGetIntegerv(GL_SCISSOR_BOX,s->scissor);
|
||||
s->scissor_on=glIsEnabled(GL_SCISSOR_TEST);
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM,&s->program);
|
||||
glGetIntegerv(GL_ACTIVE_TEXTURE,&s->active);
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D,&s->texture);
|
||||
}
|
||||
static void g2_state_restore(const struct g2_state *s) {
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER,(GLuint)s->read_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,(GLuint)s->draw_fbo);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER,(GLuint)s->rbo);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER,(GLuint)s->pbo);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT,s->pack);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH,s->row);
|
||||
glPixelStorei(GL_PACK_SKIP_ROWS,s->skip_rows);
|
||||
glPixelStorei(GL_PACK_SKIP_PIXELS,s->skip_pixels);
|
||||
}
|
||||
/* WHY: EGL creation allows a newer compatible ES context (§3.7.1), while
|
||||
* EGL's context query describes supported version (§3.7.4). Preserve any
|
||||
* EGL/GL divergence as evidence; never relabel EGL as a creation request.
|
||||
* CONTRACT: attest bounded GL_VERSION first, then safely query ES3 enums.
|
||||
* INVARIANT: only ES 3.0..3.2 native RBO reads; no shader or error-queue neutrality. */
|
||||
static bool g2_version(const char *v, GLint *minor) {
|
||||
if(!v || strnlen(v,160)>=160 || strncmp(v,"OpenGL ES 3.",12))return false;
|
||||
if(v[12]<'0' || v[12]>'2' || (v[13] && v[13]!=' '))return false;
|
||||
for(size_t i=13;v[i];i++)if((unsigned char)v[i]<32 || (unsigned char)v[i]>126)return false;
|
||||
*minor=v[12]-'0';return true;
|
||||
}
|
||||
static struct g2_sample g2_read(GLuint fbo,int width,int height,size_t used) {
|
||||
struct g2_sample s={.reason="NOT_PROVEN"};
|
||||
s.context=eglGetCurrentContext(); s.api=eglQueryAPI(); s.display=eglGetCurrentDisplay();
|
||||
if(s.context==EGL_NO_CONTEXT || s.display==EGL_NO_DISPLAY || s.api!=EGL_OPENGL_ES_API) {s.reason="UNKNOWN_CONTEXT"; return s;}
|
||||
if(!eglQueryContext(s.display,s.context,EGL_CONTEXT_CLIENT_VERSION,&s.client_version)) {
|
||||
s.reason="EGL_QUERY_FAILED"; return s;
|
||||
}
|
||||
s.pre=g2_drain(); if(!g2_clean(s.pre)) {s.reason="PREEXISTING_GL_ERRORS"; return s;}
|
||||
s.version_queried=true;
|
||||
const char *v=(const char *)glGetString(GL_VERSION);
|
||||
if(v && strnlen(v,sizeof(s.version))<sizeof(s.version))snprintf(s.version,sizeof(s.version),"%s",v);
|
||||
s.version_errors=g2_drain(); if(!g2_clean(s.version_errors)) {s.reason="VERSION_QUERY_ERROR"; return s;}
|
||||
GLint parsed_minor=0;
|
||||
if(s.client_version<2 || s.client_version>3 || !g2_version(v,&parsed_minor)) {
|
||||
s.reason="GLES_STATE_NOT_SUPPORTED"; return s;
|
||||
}
|
||||
s.capability_queried=true;
|
||||
glGetIntegerv(GL_MAJOR_VERSION,&s.major);glGetIntegerv(GL_MINOR_VERSION,&s.minor);
|
||||
const char *glsl=(const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
if(glsl && strnlen(glsl,sizeof(s.glsl))<sizeof(s.glsl))snprintf(s.glsl,sizeof(s.glsl),"%s",glsl);
|
||||
s.capability=g2_drain();
|
||||
if(!g2_clean(s.capability)) {s.reason="CAPABILITY_QUERY_ERROR";return s;}
|
||||
if(s.major!=3 || s.minor!=parsed_minor || !s.glsl[0]) {s.reason="VERSION_INCONSISTENT";return s;}
|
||||
if(!g2_size(width,height,used,&s.bytes)) {s.reason="RAW_BUDGET_OR_OVERFLOW"; return s;}
|
||||
g2_state_get(&s.before); s.state_errors=g2_drain();
|
||||
if(!g2_clean(s.state_errors)) {s.reason="STATE_QUERY_ERROR"; return s;}
|
||||
s.state_saved=true; s.bound=true;
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo);
|
||||
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
|
||||
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,&s.object_type);
|
||||
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
|
||||
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,&s.object);
|
||||
if(s.object_type==GL_RENDERBUFFER && s.object>0) {
|
||||
glBindRenderbuffer(GL_RENDERBUFFER,(GLuint)s.object);
|
||||
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_WIDTH,&s.width);
|
||||
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_HEIGHT,&s.height);
|
||||
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_INTERNAL_FORMAT,&s.format);
|
||||
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_SAMPLES,&s.samples);
|
||||
const GLenum c[4]={GL_RENDERBUFFER_RED_SIZE,GL_RENDERBUFFER_GREEN_SIZE,GL_RENDERBUFFER_BLUE_SIZE,GL_RENDERBUFFER_ALPHA_SIZE};
|
||||
for(int i=0;i<4;i++) glGetRenderbufferParameteriv(GL_RENDERBUFFER,c[i],&s.channels[i]);
|
||||
} else if(s.object_type==GL_TEXTURE) {
|
||||
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
|
||||
GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL,&s.level);
|
||||
}
|
||||
glGetIntegerv(GL_READ_BUFFER,&s.read_buffer);
|
||||
s.status=glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
|
||||
s.query=g2_drain();
|
||||
if(!g2_clean(s.query)) {s.reason="ATTACHMENT_QUERY_ERROR"; goto done;}
|
||||
if(s.object_type!=GL_RENDERBUFFER) {s.reason="TEXTURE_ATTACHMENT_DIMENSIONS_NOT_PROVEN"; goto done;}
|
||||
if(s.read_buffer!=GL_COLOR_ATTACHMENT0 || s.samples!=0 || s.status!=GL_FRAMEBUFFER_COMPLETE || s.width!=width || s.height!=height ||
|
||||
s.channels[0]!=8 || s.channels[1]!=8 || s.channels[2]!=8 || (s.channels[3]!=0 && s.channels[3]!=8)) {
|
||||
s.reason="ATTACHMENT_UNSUPPORTED"; goto done;
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER,0); glPixelStorei(GL_PACK_ALIGNMENT,1);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH,0); glPixelStorei(GL_PACK_SKIP_ROWS,0); glPixelStorei(GL_PACK_SKIP_PIXELS,0);
|
||||
s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="PACK_QUERY_ERROR"; goto done;}
|
||||
s.pixels=malloc(s.bytes); if(!s.pixels) {s.reason="ALLOCATION_FAILED"; goto done;}
|
||||
/* glReadPixels returns void. NO alpha sentinel or file hash success gate. */
|
||||
s.read_attempted=true;
|
||||
glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,s.pixels);
|
||||
s.read=g2_drain();
|
||||
if(!g2_clean(s.read)) {s.reason="READ_GL_ERROR"; goto done;}
|
||||
s.success=true; s.reason="READ_SUCCESS";
|
||||
done:
|
||||
if(s.state_saved && s.bound) {
|
||||
s.restore_attempted=true;
|
||||
g2_state_restore(&s.before); g2_state_get(&s.after); s.restore=g2_drain();
|
||||
if(!g2_clean(s.restore) || memcmp(&s.before,&s.after,sizeof(s.before)) || eglGetCurrentContext()!=s.context || eglGetCurrentDisplay()!=s.display || eglQueryAPI()!=s.api) {
|
||||
s.success=false; s.reason="RESTORE_FAILED";
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
228
tooling/probes/step20c-corec-v2-followup/gpu/g2_native.h
Normal file
228
tooling/probes/step20c-corec-v2-followup/gpu/g2_native.h
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
/* PRIVATE G3 admission; G2 symbols/hooks preserved. No public API. */
|
||||
#ifndef G2_NATIVE_H
|
||||
#define G2_NATIVE_H
|
||||
#include "g2_gl.h"
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <wlr/render/dmabuf.h>
|
||||
#include <drm_fourcc.h>
|
||||
#include <wlr/types/wlr_buffer.h>
|
||||
#ifndef G3_ROOT
|
||||
#error "Define G3_ROOT as the reviewed new LAB acquisition path"
|
||||
#endif
|
||||
#define G2_ROOT G3_ROOT
|
||||
static struct {
|
||||
bool stopped, active; unsigned epoch, point; size_t used;
|
||||
int dir; FILE *meta; const void *pass, *output;
|
||||
uintptr_t pass_sequence; char nonce[65];
|
||||
} g2={.dir=-1};
|
||||
static const char *g2_names[]={"source","down0","down1","down2","up2","up1","up0","optimized-cache","no-blur-cache","composition-presubmit"};
|
||||
static void g2_fail(const char *reason) {
|
||||
g2.stopped=true;
|
||||
if(g2.meta) {fprintf(g2.meta,"{\"kind\":\"rejected\",\"reason\":\"%s\",\"point\":%u}\n",reason,g2.point); fflush(g2.meta); fsync(fileno(g2.meta)); fclose(g2.meta); g2.meta=NULL;}
|
||||
g2.active=false;
|
||||
}
|
||||
static uint32_t g2_crc(const unsigned char *p,size_t n) {
|
||||
uint32_t crc=~0u;
|
||||
for(size_t i=0;i<n;i++) {crc^=p[i]; for(int b=0;b<8;b++) crc=(crc>>1)^((0u-(crc&1u))&0xedb88320u);}
|
||||
return ~crc;
|
||||
}
|
||||
static bool g2_write(int fd,const void *ptr,size_t n) {
|
||||
const char *p=ptr; while(n) {ssize_t k=write(fd,p,n); if(k<0 && errno==EINTR) continue; if(k<=0)return false; p+=k;n-=(size_t)k;} return true;
|
||||
}
|
||||
static void g2_error_json(const char *name,struct g2_errors e) {
|
||||
fprintf(g2.meta,",\"%s\":{\"exhausted\":%s,\"codes\":[",name,e.exhausted?"true":"false");
|
||||
for(unsigned i=0;i<e.count;i++) {fprintf(g2.meta,"%s%u",i?",":"",e.values[i]);}
|
||||
fputs("]}",g2.meta);
|
||||
}
|
||||
static void g2_state_json(const char *name,const struct g2_state *s) {
|
||||
fprintf(g2.meta,",\"%s\":{\"read_draw_rbo_pack_pbo_row_skip\":[%d,%d,%d,%d,%d,%d,%d,%d],\"viewport\":[%d,%d,%d,%d],\"scissor\":[%d,%d,%d,%d],\"scissor_enabled\":%u,\"program_active_texture\":[%d,%d,%d]}",name,s->read_fbo,s->draw_fbo,s->rbo,s->pack,s->pbo,s->row,s->skip_rows,s->skip_pixels,s->viewport[0],s->viewport[1],s->viewport[2],s->viewport[3],s->scissor[0],s->scissor[1],s->scissor[2],s->scissor[3],s->scissor_on,s->program,s->active,s->texture);
|
||||
}
|
||||
static void g2_region(const pixman_region32_t *region) {
|
||||
int n=0; pixman_box32_t *r=region?pixman_region32_rectangles(region,&n):NULL;
|
||||
fputs(",\"fresh_rectangles\":[",g2.meta);
|
||||
if(n>256) {fputs("],\"fresh_region_overflow\":true",g2.meta);return;}
|
||||
for(int i=0;i<n;i++)fprintf(g2.meta,"%s[%d,%d,%d,%d]",i?",":"",r[i].x1,r[i].y1,r[i].x2,r[i].y2);
|
||||
fputs("]",g2.meta);
|
||||
}
|
||||
static bool g2_dmabuf_supported(struct fx_framebuffer *b) {
|
||||
struct wlr_dmabuf_attributes a={0};
|
||||
if(!wlr_buffer_get_dmabuf(b->buffer,&a))return false;
|
||||
bool format=a.format==DRM_FORMAT_ABGR8888 || a.format==DRM_FORMAT_XBGR8888 || a.format==DRM_FORMAT_ARGB8888 || a.format==DRM_FORMAT_XRGB8888;
|
||||
bool amd=a.modifier==UINT64_C(0x020000001046bb04);
|
||||
if(!format || (a.modifier!=0 && !amd) || a.n_planes!=(amd?2:1) || a.width!=b->buffer->width || a.height!=b->buffer->height || a.width<=0 || a.height<=0)return false;
|
||||
if((uint64_t)a.stride[0]<(uint64_t)a.width*4)return false;
|
||||
for(int i=0;i<a.n_planes;i++) {struct stat st;if(!a.stride[i] || a.fd[i]<0 || fstat(a.fd[i],&st))return false;}
|
||||
return true; /* Metadata sanity, NEVER modifier decoding/allocation proof. */
|
||||
}
|
||||
static void g2_native_object(struct fx_framebuffer *b) {
|
||||
fprintf(g2.meta,",\"native\":{\"fx\":\"%"PRIxPTR"\",\"wlr\":\"%"PRIxPTR"\",\"fbo_rbo_tex\":[%u,%u,%u],\"dimensions\":[%d,%d],\"image\":\"%"PRIxPTR"\"}",(uintptr_t)b,(uintptr_t)b->buffer,b->fbo,b->rbo,b->tex,b->buffer->width,b->buffer->height,(uintptr_t)b->image);
|
||||
struct wlr_dmabuf_attributes a={0};
|
||||
if(!wlr_buffer_get_dmabuf(b->buffer,&a)) {fputs(",\"dmabuf\":null",g2.meta);return;}
|
||||
/* Borrowed descriptors: NEVER finish(), close(), duplicate or seek these FDs.
|
||||
* fstat exposes descriptor identity, not a trustworthy DCC allocation length. */
|
||||
fprintf(g2.meta,",\"dmabuf\":{\"format\":%u,\"modifier\":\"%016"PRIx64"\",\"dimensions\":[%d,%d],\"borrowed\":true,\"allocation_length\":\"NOT_PROVEN\",\"planes\":[",a.format,a.modifier,a.width,a.height);
|
||||
if(a.n_planes<1 || a.n_planes>4) {fputs("]}",g2.meta);return;}
|
||||
for(int i=0;i<a.n_planes;i++) {struct stat st={0};bool ok=fstat(a.fd[i],&st)==0;
|
||||
fprintf(g2.meta,"%s{\"stride\":%u,\"offset\":%u,\"fd\":%d,\"fstat_ok\":%s,\"dev\":%ju,\"ino\":%ju}",i?",":"",a.stride[i],a.offset[i],a.fd[i],ok?"true":"false",(uintmax_t)st.st_dev,(uintmax_t)st.st_ino);}
|
||||
fputs("]}",g2.meta);
|
||||
}
|
||||
static bool g2_owned(int fd,mode_t mode) {struct stat s;return fstat(fd,&s)==0 && s.st_uid==geteuid() && (s.st_mode&0777)==mode;}
|
||||
static bool g2_open(void) {
|
||||
if(g2.dir>=0)return true;
|
||||
const char *d=getenv("MOSAIC_G3_DIR"),*nonce=getenv("MOSAIC_G3_NONCE"); char real[PATH_MAX];
|
||||
if(!d || !nonce)return false;
|
||||
if(strlen(nonce)!=64 || !realpath(d,real) || strcmp(real,G2_ROOT) || strcmp(d,G2_ROOT)) {g2.stopped=true;return false;}
|
||||
for(unsigned i=0;i<64;i++)if(!((nonce[i]>='0'&&nonce[i]<='9')||(nonce[i]>='a'&&nonce[i]<='f'))) {g2.stopped=true;return false;}
|
||||
g2.dir=open(d,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if(g2.dir<0 || !g2_owned(g2.dir,0700)) {if(g2.dir>=0)close(g2.dir);g2.dir=-1;g2.stopped=true;return false;}
|
||||
memcpy(g2.nonce,nonce,65);return true;
|
||||
}
|
||||
static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options);
|
||||
static bool g2_expected_source(const struct g2_sample *s,unsigned epoch) {
|
||||
if(s->width!=800 || s->height!=600 || !s->pixels)return false;
|
||||
const unsigned char a[2][3]={{245,245,245},{15,25,45}};
|
||||
const unsigned char b[2][3]={{32,192,224},{176,32,64}};
|
||||
for(int y=0;y<600;y++)for(int x=0;x<800;x++) {
|
||||
const unsigned char *expected=(epoch==2?b:a)[(x/8)%2];
|
||||
if(memcmp(s->pixels+((size_t)y*800+(size_t)x)*4,expected,3))return false;
|
||||
}
|
||||
return true; /* RGB source admission only; alpha is recorded, never sentinel. */
|
||||
}
|
||||
static void g2_sample_native(struct fx_gles_render_pass *pass,struct fx_framebuffer *b,unsigned point,const pixman_region32_t *region) {
|
||||
if(!g2.active || g2.pass!=pass)return;
|
||||
if(point!=g2.point || point>=10) {g2_fail("STAGE_ORDER_OR_DUPLICATE");return;}
|
||||
if(region && pixman_region32_n_rects(region)>256) {g2_fail("REGION_LIMIT");return;}
|
||||
if(!g2_dmabuf_supported(b)) {g2_fail("DMABUF_METADATA_UNSUPPORTED");return;}
|
||||
struct g2_sample s=g2_read(b->fbo,b->buffer->width,b->buffer->height,g2.used);
|
||||
fprintf(g2.meta,"{\"kind\":\"sample\",\"epoch\":%u,\"pass_sequence\":%"PRIuPTR",\"point\":%u,\"stage\":\"%s\",\"context\":\"%"PRIxPTR"\",\"api\":%u,\"client_version\":%d,\"result\":\"%s\",\"bytes\":%zu,\"attachment\":[%d,%d,%d,%d,%d,%d],\"channels\":[%d,%d,%d,%d],\"status\":%u",g2.epoch,g2.pass_sequence,point,g2_names[point],(uintptr_t)s.context,s.api,s.client_version,s.reason,s.bytes,s.object_type,s.object,s.level,s.width,s.height,s.format,s.channels[0],s.channels[1],s.channels[2],s.channels[3],s.status);
|
||||
fprintf(g2.meta,",\"read_success\":%s,\"read_buffer\":%d,\"samples\":%d",s.success?"true":"false",s.read_buffer,s.samples);
|
||||
fputs(",\"gl_version_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.version);i++)fprintf(g2.meta,"%02x",(unsigned char)s.version[i]);fputc('"',g2.meta);
|
||||
g2_error_json("pre_errors",s.pre);g2_error_json("query_errors",s.query);g2_error_json("read_errors",s.read);g2_error_json("restore_errors",s.restore);
|
||||
fprintf(g2.meta,",\"version_queried\":%s,\"state_saved\":%s,\"bound\":%s,\"read_attempted\":%s,\"restore_attempted\":%s",s.version_queried?"true":"false",s.state_saved?"true":"false",s.bound?"true":"false",s.read_attempted?"true":"false",s.restore_attempted?"true":"false");
|
||||
if(s.capability_queried)fprintf(g2.meta,",\"gl_major\":%d,\"gl_minor\":%d",s.major,s.minor);else fputs(",\"gl_major\":null,\"gl_minor\":null",g2.meta);
|
||||
fputs(",\"glsl_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.glsl);i++)fprintf(g2.meta,"%02x",(unsigned char)s.glsl[i]);fputc('"',g2.meta);
|
||||
g2_error_json("version_errors",s.version_errors);g2_error_json("capability_errors",s.capability);g2_error_json("state_errors",s.state_errors);
|
||||
if(s.state_saved)g2_state_json("before",&s.before);else fputs(",\"before\":null",g2.meta);
|
||||
if(s.restore_attempted)g2_state_json("after",&s.after);else fputs(",\"after\":null",g2.meta);g2_native_object(b);g2_region(region);
|
||||
if(!s.success) {fputs("}\n",g2.meta);free(s.pixels);g2_fail(s.reason);return;}
|
||||
char name[64];snprintf(name,sizeof(name),"epoch-%u-point-%u.rgba",g2.epoch,point);
|
||||
int fd=openat(g2.dir,name,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
|
||||
bool ok=fd>=0 && g2_write(fd,s.pixels,s.bytes) && fsync(fd)==0;
|
||||
if(fd>=0 && close(fd))ok=false;
|
||||
fprintf(g2.meta,",\"crc32\":\"%08x\",\"file\":\"%s\",\"published\":%s}\n",g2_crc(s.pixels,s.bytes),name,ok?"true":"false");
|
||||
g2.used+=s.bytes;
|
||||
if(!ok || fflush(g2.meta) || fsync(fileno(g2.meta))) {free(s.pixels);g2_fail("PUBLICATION_FAILED");return;}
|
||||
g2.point++;
|
||||
if(point==0 && !g2_expected_source(&s,g2.epoch)) {free(s.pixels);g2_fail("SOURCE_PATTERN_NOT_CURRENT_EPOCH");return;}
|
||||
free(s.pixels);
|
||||
if(point==9) {
|
||||
fprintf(g2.meta,"{\"kind\":\"end\",\"epoch\":%u,\"points\":10,\"raw_total\":%zu,\"accepted\":true}\n",g2.epoch,g2.used);
|
||||
bool final_ok=fflush(g2.meta)==0 && fsync(fileno(g2.meta))==0;
|
||||
if(fclose(g2.meta)) {final_ok=false;}
|
||||
g2.meta=NULL;g2.active=false;
|
||||
if(!final_ok)g2.stopped=true;
|
||||
if(g2.epoch==3)g2.stopped=true;
|
||||
}
|
||||
}
|
||||
/* Explicit intended-output mapping; bounded eight outputs, no public ABI. */
|
||||
static struct {const void *pass;bool intended;} g2_outputs[8];
|
||||
static void g2_forget_output(struct fx_gles_render_pass *pass) {
|
||||
for(unsigned i=0;i<8;i++)if(g2_outputs[i].pass==pass) {
|
||||
g2_outputs[i].pass=NULL;g2_outputs[i].intended=false;
|
||||
}
|
||||
}
|
||||
static bool g2_intended_output(struct fx_gles_render_pass *pass) {
|
||||
for(unsigned i=0;i<8;i++)if(g2_outputs[i].pass==pass)return g2_outputs[i].intended;
|
||||
return false;
|
||||
}
|
||||
static void g2_track_output(struct fx_gles_render_pass *pass,struct wlr_output *output) {
|
||||
/* A native pass owns its mapping only until submit. Address reuse overwrites
|
||||
* prior identity. Overflow never silently associates another output. */
|
||||
g2_forget_output(pass);
|
||||
for(unsigned i=0;i<8;i++)if(!g2_outputs[i].pass) {
|
||||
g2_outputs[i].pass=pass;
|
||||
g2_outputs[i].intended=strcmp(output->name,"HEADLESS-1")==0 && output->scale==1.0f && output->transform==WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
return;
|
||||
}
|
||||
g2_fail("TOO_MANY_SIMULTANEOUS_NATIVE_PASSES");
|
||||
}
|
||||
static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options) {
|
||||
if(g2.stopped)return;
|
||||
if(g2.active) {if(g2.pass==pass)g2_fail("MULTIPLE_OPTIMIZED_CANDIDATES");return;}
|
||||
if(!g2_intended_output(pass) || !g2_open())return;
|
||||
unsigned e=g2.epoch+1;if(e>3)return;
|
||||
char arm[40],consumed[40],meta[40],expected[80],data[81]={0};
|
||||
snprintf(arm,sizeof(arm),"epoch-%u.arm",e);snprintf(consumed,sizeof(consumed),"epoch-%u.consumed",e);
|
||||
snprintf(expected,sizeof(expected),"%s %u\n",g2.nonce,e);
|
||||
/* WHY: O_NONBLOCK makes non-regular substitutions (especially a FIFO with
|
||||
* no writer) fail closed without stalling the compositor callback.
|
||||
* CONTRACT: validate identity, permissions, link count and exact bounded
|
||||
* framing size before reading or consuming the arm token. */
|
||||
int fd=openat(g2.dir,arm,O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);if(fd<0) {if(errno!=ENOENT)g2.stopped=true;return;}
|
||||
struct stat st;
|
||||
bool ok=fstat(fd,&st)==0 && S_ISREG(st.st_mode) && st.st_uid==geteuid() &&
|
||||
(st.st_mode&0777)==0600 && st.st_nlink==1 && st.st_size==(off_t)strlen(expected);
|
||||
if(!ok) {close(fd);g2.stopped=true;return;}
|
||||
ssize_t n=read(fd,data,sizeof(data)-1);
|
||||
if(n!=(ssize_t)strlen(expected) || memcmp(data,expected,(size_t)n)) {close(fd);g2.stopped=true;return;}
|
||||
/* Consume BEFORE any query, readback or publication. Existing consumed token
|
||||
* is fatal, even if a prior process died or first A was rejected. */
|
||||
struct stat linked;
|
||||
if(linkat(g2.dir,arm,g2.dir,consumed,0) || fstatat(g2.dir,consumed,&linked,AT_SYMLINK_NOFOLLOW) ||
|
||||
!S_ISREG(linked.st_mode) || linked.st_dev!=st.st_dev || linked.st_ino!=st.st_ino || linked.st_nlink!=2 ||
|
||||
unlinkat(g2.dir,arm,0) || fsync(g2.dir)) {close(fd);g2.stopped=true;return;}
|
||||
close(fd);
|
||||
g2.epoch=e;g2.point=0;g2.pass=pass;g2.pass_sequence++;g2.active=true;
|
||||
snprintf(meta,sizeof(meta),"epoch-%u.jsonl",e);fd=openat(g2.dir,meta,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
|
||||
if(fd<0 || !(g2.meta=fdopen(fd,"w"))) {if(fd>=0)close(fd);g2.stopped=true;g2.active=false;return;}
|
||||
fprintf(g2.meta,"{\"kind\":\"begin\",\"schema\":\"G3-followup-v1\",\"epoch\":%u,\"label\":\"%s\",\"nonce\":\"%s\",\"output\":\"HEADLESS-1\",\"pass\":\"%"PRIxPTR"\",\"offscreen_output_key\":\"%"PRIxPTR"\",\"intrusive_error_queue\":true,\"error_queue_restorable\":false,\"readback_may_synchronize\":true,\"rows\":\"GL_LOW_TO_HIGH\",\"present_buffer\":\"NOT_PROVEN\",\"gpu_completion\":\"NOT_PROVEN\",\"lifetime\":\"NOT_PROVEN\"}\n",e,e==1?"A":e==2?"B":"A-return",g2.nonce,(uintptr_t)pass,(uintptr_t)pass->fx_offscreen_buffers);
|
||||
if(options->blur_data->num_passes!=3 || options->blur_strength!=1.0f || blur_data_should_parameters_blur_effects(options->blur_data)) {g2_fail("NON_PROSPECTIVE_BLUR_CONFIGURATION");return;}
|
||||
g2_sample_native(pass,source,0,options->tex_options.base.clip);
|
||||
}
|
||||
/* Executed while the actual blur program and source sampler remain bound. */
|
||||
static void g2_uniform(struct fx_gles_render_pass *pass,struct blur_shader *shader,struct fx_texture *texture,const pixman_region32_t *region) {
|
||||
if(!g2.active || g2.pass!=pass || g2.point<1 || g2.point>6)return;
|
||||
if(region && pixman_region32_n_rects(region)>256) {g2_fail("REGION_LIMIT");return;}
|
||||
struct g2_errors pre=g2_drain();if(!g2_clean(pre)) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("pre_errors",pre);fputs("}\n",g2.meta);g2_fail("PRE_UNIFORM_GL_ERROR");return;}
|
||||
struct g2_state draw_state;GLboolean color_mask[4]={0};
|
||||
g2_state_get(&draw_state);glGetBooleanv(GL_COLOR_WRITEMASK,color_mask);
|
||||
GLboolean blend=glIsEnabled(GL_BLEND),stencil=glIsEnabled(GL_STENCIL_TEST);
|
||||
GLint program=0,min=0,mag=0,wraps=0,wrapt=0;GLfloat value[2]={0};GLint range[2]={0},precision=0;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM,&program);
|
||||
struct g2_errors initial=g2_drain();
|
||||
if(!g2_clean(initial)) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_PROGRAM_QUERY_ERROR");return;}
|
||||
if(program!=(GLint)shader->program || shader->halfpixel<0 || texture->target!=GL_TEXTURE_2D) {g2_fail("UNIFORM_PROGRAM_OR_TARGET");return;}
|
||||
GLint actual=glGetUniformLocation((GLuint)program,"halfpixel");
|
||||
initial=g2_drain();
|
||||
if(!g2_clean(initial) || actual!=shader->halfpixel) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_LOCATION");return;}
|
||||
GLint count=0;glGetProgramiv((GLuint)program,GL_ACTIVE_UNIFORMS,&count);bool typed=false;
|
||||
initial=g2_drain();
|
||||
if(!g2_clean(initial) || count<0 || count>128) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_COUNT");return;}
|
||||
for(GLuint i=0;i<(GLuint)count;i++) {char name[128];GLsizei len=0;GLint size=0;GLenum type=0;glGetActiveUniform((GLuint)program,i,sizeof(name),&len,&size,&type,name);
|
||||
if(len==9 && !memcmp(name,"halfpixel",9) && size==1 && type==GL_FLOAT_VEC2)typed=true;
|
||||
}
|
||||
GLfloat radius=0,proj[9]={0},tex_proj[9]={0};
|
||||
glGetUniformfv((GLuint)program,shader->radius,&radius);
|
||||
glGetUniformfv((GLuint)program,shader->proj,proj);glGetUniformfv((GLuint)program,shader->tex_proj,tex_proj);
|
||||
glGetUniformfv((GLuint)program,actual,value);
|
||||
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER,GL_MEDIUM_FLOAT,range,&precision);
|
||||
glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,&min);glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,&mag);
|
||||
glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,&wraps);glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,&wrapt);
|
||||
struct g2_errors errors=g2_drain();if(!g2_clean(errors)||!typed) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",errors);fputs("}\n",g2.meta);g2_fail("UNIFORM_QUERY_OR_TYPE");return;}
|
||||
uint32_t bits[2];memcpy(bits,value,sizeof(bits));
|
||||
fprintf(g2.meta,"{\"kind\":\"uniform\",\"point\":%u,\"program\":%d,\"location\":%d,\"type\":%u,\"actual_bits\":[%u,%u],\"shader_default_float\":\"mediump\",\"varying\":\"mediump\",\"precision_capability\":[%d,%d,%d],\"operation_precision\":\"NOT_PROVEN\",\"sampler\":[%d,%d,%d,%d],\"source_texture\":%u,\"native_texture_dimensions\":[%u,%u],\"gl_level_dimensions\":\"NOT_PROVEN\"",g2.point,program,actual,GL_FLOAT_VEC2,bits[0],bits[1],range[0],range[1],precision,min,mag,wraps,wrapt,texture->tex,texture->wlr_texture.width,texture->wlr_texture.height);
|
||||
fprintf(g2.meta,",\"qualifier_basis\":\"FROZEN_LINK_INPUT_SOURCE\",\"shader_source_sha256\":\"%s\"",g2.point<=3?"01664694e4201efb35ec698e89783e220ed930972b5396b86fa842814cd82d4d":"1db325237c9a6c70c2d97518e2f690d448cd006139e9f410cd16e097a1677ad1");
|
||||
uint32_t radius_bits,proj_bits[9],tex_proj_bits[9];memcpy(&radius_bits,&radius,4);memcpy(proj_bits,proj,sizeof(proj));memcpy(tex_proj_bits,tex_proj,sizeof(tex_proj));
|
||||
fprintf(g2.meta,",\"other_uniforms\":{\"radius_location\":%d,\"radius_bits\":%u,\"proj_location\":%d,\"proj_bits\":[",shader->radius,radius_bits,shader->proj);
|
||||
for(unsigned i=0;i<9;i++)fprintf(g2.meta,"%s%u",i?",":"",proj_bits[i]);
|
||||
fprintf(g2.meta,"],\"tex_proj_location\":%d,\"tex_proj_bits\":[",shader->tex_proj);
|
||||
for(unsigned i=0;i<9;i++)fprintf(g2.meta,"%s%u",i?",":"",tex_proj_bits[i]);
|
||||
fputs("]}",g2.meta);g2_state_json("draw_state",&draw_state);
|
||||
fprintf(g2.meta,",\"blend_stencil\":[%u,%u],\"color_mask\":[%u,%u,%u,%u]",blend,stencil,color_mask[0],color_mask[1],color_mask[2],color_mask[3]);
|
||||
g2_region(region);fputs("}\n",g2.meta);
|
||||
}
|
||||
#endif
|
||||
155
tooling/probes/step20c-corec-v2-followup/gpu/g3-admission.patch
Normal file
155
tooling/probes/step20c-corec-v2-followup/gpu/g3-admission.patch
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
--- a/render/fx_renderer/g2_gl.h
|
||||
+++ b/render/fx_renderer/g2_gl.h
|
||||
@@ -1,4 +1,4 @@
|
||||
-/* G2 new source-only intrusive diagnostic. No production API. */
|
||||
+/* G3 private admission delta; G2 hook symbols retained. No production API. */
|
||||
#ifndef G2_GL_H
|
||||
#define G2_GL_H
|
||||
#include <EGL/egl.h>
|
||||
@@ -18,8 +18,9 @@
|
||||
};
|
||||
struct g2_sample {
|
||||
EGLContext context; EGLint client_version; EGLenum api;
|
||||
- char version[160]; struct g2_state before, after;
|
||||
- struct g2_errors pre, query, read, restore;
|
||||
+ EGLDisplay display; char version[160], glsl[160]; GLint major, minor;
|
||||
+ bool capability_queried, version_queried, state_saved, bound, read_attempted, restore_attempted; struct g2_state before, after;
|
||||
+ struct g2_errors pre, version_errors, capability, state_errors, query, read, restore;
|
||||
GLint object_type, object, level, width, height, format, channels[4];
|
||||
GLint read_buffer, samples; GLenum status; size_t bytes; unsigned char *pixels;
|
||||
bool success; const char *reason;
|
||||
@@ -64,29 +65,44 @@
|
||||
glPixelStorei(GL_PACK_SKIP_ROWS,s->skip_rows);
|
||||
glPixelStorei(GL_PACK_SKIP_PIXELS,s->skip_pixels);
|
||||
}
|
||||
-/* Called with the actual current native target. ES2 is explicitly unsupported:
|
||||
- * no ES3-only enum is ever queried before both EGL and GL attest ES3.
|
||||
- * Only native RBO color attachments are accepted. Texture attachments retain
|
||||
- * type/name/level, but safe dimensions on this pinned API are NOT_PROVEN.
|
||||
- * Error queue consumption and synchronous readback cannot be restored. */
|
||||
+/* WHY: EGL creation allows a newer compatible ES context (§3.7.1), while
|
||||
+ * EGL's context query describes supported version (§3.7.4). Preserve any
|
||||
+ * EGL/GL divergence as evidence; never relabel EGL as a creation request.
|
||||
+ * CONTRACT: attest bounded GL_VERSION first, then safely query ES3 enums.
|
||||
+ * INVARIANT: only ES 3.0..3.2 native RBO reads; no shader or error-queue neutrality. */
|
||||
+static bool g2_version(const char *v, GLint *minor) {
|
||||
+ if(!v || strnlen(v,160)>=160 || strncmp(v,"OpenGL ES 3.",12))return false;
|
||||
+ if(v[12]<'0' || v[12]>'2' || (v[13] && v[13]!=' '))return false;
|
||||
+ for(size_t i=13;v[i];i++)if((unsigned char)v[i]<32 || (unsigned char)v[i]>126)return false;
|
||||
+ *minor=v[12]-'0';return true;
|
||||
+}
|
||||
static struct g2_sample g2_read(GLuint fbo,int width,int height,size_t used) {
|
||||
- struct g2_sample s={.reason="NOT_PROVEN"}; bool saved=false, changed=false;
|
||||
- s.context=eglGetCurrentContext(); s.api=eglQueryAPI();
|
||||
- if(s.context==EGL_NO_CONTEXT || s.api!=EGL_OPENGL_ES_API) {s.reason="UNKNOWN_CONTEXT"; return s;}
|
||||
- if(!eglQueryContext(eglGetCurrentDisplay(),s.context,EGL_CONTEXT_CLIENT_VERSION,&s.client_version)) {
|
||||
+ struct g2_sample s={.reason="NOT_PROVEN"};
|
||||
+ s.context=eglGetCurrentContext(); s.api=eglQueryAPI(); s.display=eglGetCurrentDisplay();
|
||||
+ if(s.context==EGL_NO_CONTEXT || s.display==EGL_NO_DISPLAY || s.api!=EGL_OPENGL_ES_API) {s.reason="UNKNOWN_CONTEXT"; return s;}
|
||||
+ if(!eglQueryContext(s.display,s.context,EGL_CONTEXT_CLIENT_VERSION,&s.client_version)) {
|
||||
s.reason="EGL_QUERY_FAILED"; return s;
|
||||
}
|
||||
s.pre=g2_drain(); if(!g2_clean(s.pre)) {s.reason="PREEXISTING_GL_ERRORS"; return s;}
|
||||
+ s.version_queried=true;
|
||||
const char *v=(const char *)glGetString(GL_VERSION);
|
||||
- if(v) snprintf(s.version,sizeof(s.version),"%s",v);
|
||||
- s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="VERSION_QUERY_ERROR"; return s;}
|
||||
- if(s.client_version<3 || !v || strncmp(v,"OpenGL ES 3.",12)!=0) {
|
||||
+ if(v && strnlen(v,sizeof(s.version))<sizeof(s.version))snprintf(s.version,sizeof(s.version),"%s",v);
|
||||
+ s.version_errors=g2_drain(); if(!g2_clean(s.version_errors)) {s.reason="VERSION_QUERY_ERROR"; return s;}
|
||||
+ GLint parsed_minor=0;
|
||||
+ if(s.client_version<2 || s.client_version>3 || !g2_version(v,&parsed_minor)) {
|
||||
s.reason="GLES_STATE_NOT_SUPPORTED"; return s;
|
||||
}
|
||||
+ s.capability_queried=true;
|
||||
+ glGetIntegerv(GL_MAJOR_VERSION,&s.major);glGetIntegerv(GL_MINOR_VERSION,&s.minor);
|
||||
+ const char *glsl=(const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
+ if(glsl && strnlen(glsl,sizeof(s.glsl))<sizeof(s.glsl))snprintf(s.glsl,sizeof(s.glsl),"%s",glsl);
|
||||
+ s.capability=g2_drain();
|
||||
+ if(!g2_clean(s.capability)) {s.reason="CAPABILITY_QUERY_ERROR";return s;}
|
||||
+ if(s.major!=3 || s.minor!=parsed_minor || !s.glsl[0]) {s.reason="VERSION_INCONSISTENT";return s;}
|
||||
if(!g2_size(width,height,used,&s.bytes)) {s.reason="RAW_BUDGET_OR_OVERFLOW"; return s;}
|
||||
- g2_state_get(&s.before); s.query=g2_drain();
|
||||
- if(!g2_clean(s.query)) {s.reason="STATE_QUERY_ERROR"; return s;}
|
||||
- saved=true; changed=true;
|
||||
+ g2_state_get(&s.before); s.state_errors=g2_drain();
|
||||
+ if(!g2_clean(s.state_errors)) {s.reason="STATE_QUERY_ERROR"; return s;}
|
||||
+ s.state_saved=true; s.bound=true;
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo);
|
||||
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
|
||||
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,&s.object_type);
|
||||
@@ -118,14 +134,16 @@
|
||||
s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="PACK_QUERY_ERROR"; goto done;}
|
||||
s.pixels=malloc(s.bytes); if(!s.pixels) {s.reason="ALLOCATION_FAILED"; goto done;}
|
||||
/* glReadPixels returns void. NO alpha sentinel or file hash success gate. */
|
||||
+ s.read_attempted=true;
|
||||
glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,s.pixels);
|
||||
s.read=g2_drain();
|
||||
if(!g2_clean(s.read)) {s.reason="READ_GL_ERROR"; goto done;}
|
||||
s.success=true; s.reason="READ_SUCCESS";
|
||||
done:
|
||||
- if(saved && changed) {
|
||||
+ if(s.state_saved && s.bound) {
|
||||
+ s.restore_attempted=true;
|
||||
g2_state_restore(&s.before); g2_state_get(&s.after); s.restore=g2_drain();
|
||||
- if(!g2_clean(s.restore) || memcmp(&s.before,&s.after,sizeof(s.before)) || eglGetCurrentContext()!=s.context) {
|
||||
+ if(!g2_clean(s.restore) || memcmp(&s.before,&s.after,sizeof(s.before)) || eglGetCurrentContext()!=s.context || eglGetCurrentDisplay()!=s.display || eglQueryAPI()!=s.api) {
|
||||
s.success=false; s.reason="RESTORE_FAILED";
|
||||
}
|
||||
}
|
||||
--- a/render/fx_renderer/g2_native.h
|
||||
+++ b/render/fx_renderer/g2_native.h
|
||||
@@ -1,4 +1,4 @@
|
||||
-/* PRIVATE G2. Included only by the new diagnostic fx_pass.c. */
|
||||
+/* PRIVATE G3 admission; G2 symbols/hooks preserved. No public API. */
|
||||
#ifndef G2_NATIVE_H
|
||||
#define G2_NATIVE_H
|
||||
#include "g2_gl.h"
|
||||
@@ -10,7 +10,10 @@
|
||||
#include <wlr/render/dmabuf.h>
|
||||
#include <drm_fourcc.h>
|
||||
#include <wlr/types/wlr_buffer.h>
|
||||
-#define G2_ROOT "/var/tmp/swayfx-mosaic-corec-v2-i516p6po/gpu-work/acquisition"
|
||||
+#ifndef G3_ROOT
|
||||
+#error "Define G3_ROOT as the reviewed new LAB acquisition path"
|
||||
+#endif
|
||||
+#define G2_ROOT G3_ROOT
|
||||
static struct {
|
||||
bool stopped, active; unsigned epoch, point; size_t used;
|
||||
int dir; FILE *meta; const void *pass, *output;
|
||||
@@ -70,7 +73,7 @@
|
||||
static bool g2_owned(int fd,mode_t mode) {struct stat s;return fstat(fd,&s)==0 && s.st_uid==geteuid() && (s.st_mode&0777)==mode;}
|
||||
static bool g2_open(void) {
|
||||
if(g2.dir>=0)return true;
|
||||
- const char *d=getenv("MOSAIC_G2_DIR"),*nonce=getenv("MOSAIC_G2_NONCE"); char real[PATH_MAX];
|
||||
+ const char *d=getenv("MOSAIC_G3_DIR"),*nonce=getenv("MOSAIC_G3_NONCE"); char real[PATH_MAX];
|
||||
if(!d || !nonce)return false;
|
||||
if(strlen(nonce)!=64 || !realpath(d,real) || strcmp(real,G2_ROOT) || strcmp(d,G2_ROOT)) {g2.stopped=true;return false;}
|
||||
for(unsigned i=0;i<64;i++)if(!((nonce[i]>='0'&&nonce[i]<='9')||(nonce[i]>='a'&&nonce[i]<='f'))) {g2.stopped=true;return false;}
|
||||
@@ -96,10 +99,15 @@
|
||||
if(!g2_dmabuf_supported(b)) {g2_fail("DMABUF_METADATA_UNSUPPORTED");return;}
|
||||
struct g2_sample s=g2_read(b->fbo,b->buffer->width,b->buffer->height,g2.used);
|
||||
fprintf(g2.meta,"{\"kind\":\"sample\",\"epoch\":%u,\"pass_sequence\":%"PRIuPTR",\"point\":%u,\"stage\":\"%s\",\"context\":\"%"PRIxPTR"\",\"api\":%u,\"client_version\":%d,\"result\":\"%s\",\"bytes\":%zu,\"attachment\":[%d,%d,%d,%d,%d,%d],\"channels\":[%d,%d,%d,%d],\"status\":%u",g2.epoch,g2.pass_sequence,point,g2_names[point],(uintptr_t)s.context,s.api,s.client_version,s.reason,s.bytes,s.object_type,s.object,s.level,s.width,s.height,s.format,s.channels[0],s.channels[1],s.channels[2],s.channels[3],s.status);
|
||||
- fprintf(g2.meta,",\"read_buffer\":%d,\"samples\":%d",s.read_buffer,s.samples);
|
||||
+ fprintf(g2.meta,",\"read_success\":%s,\"read_buffer\":%d,\"samples\":%d",s.success?"true":"false",s.read_buffer,s.samples);
|
||||
fputs(",\"gl_version_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.version);i++)fprintf(g2.meta,"%02x",(unsigned char)s.version[i]);fputc('"',g2.meta);
|
||||
g2_error_json("pre_errors",s.pre);g2_error_json("query_errors",s.query);g2_error_json("read_errors",s.read);g2_error_json("restore_errors",s.restore);
|
||||
- g2_state_json("before",&s.before);g2_state_json("after",&s.after);g2_native_object(b);g2_region(region);
|
||||
+ fprintf(g2.meta,",\"version_queried\":%s,\"state_saved\":%s,\"bound\":%s,\"read_attempted\":%s,\"restore_attempted\":%s",s.version_queried?"true":"false",s.state_saved?"true":"false",s.bound?"true":"false",s.read_attempted?"true":"false",s.restore_attempted?"true":"false");
|
||||
+ if(s.capability_queried)fprintf(g2.meta,",\"gl_major\":%d,\"gl_minor\":%d",s.major,s.minor);else fputs(",\"gl_major\":null,\"gl_minor\":null",g2.meta);
|
||||
+ fputs(",\"glsl_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.glsl);i++)fprintf(g2.meta,"%02x",(unsigned char)s.glsl[i]);fputc('"',g2.meta);
|
||||
+ g2_error_json("version_errors",s.version_errors);g2_error_json("capability_errors",s.capability);g2_error_json("state_errors",s.state_errors);
|
||||
+ if(s.state_saved)g2_state_json("before",&s.before);else fputs(",\"before\":null",g2.meta);
|
||||
+ if(s.restore_attempted)g2_state_json("after",&s.after);else fputs(",\"after\":null",g2.meta);g2_native_object(b);g2_region(region);
|
||||
if(!s.success) {fputs("}\n",g2.meta);free(s.pixels);g2_fail(s.reason);return;}
|
||||
char name[64];snprintf(name,sizeof(name),"epoch-%u-point-%u.rgba",g2.epoch,point);
|
||||
int fd=openat(g2.dir,name,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
|
||||
@@ -171,7 +179,7 @@
|
||||
g2.epoch=e;g2.point=0;g2.pass=pass;g2.pass_sequence++;g2.active=true;
|
||||
snprintf(meta,sizeof(meta),"epoch-%u.jsonl",e);fd=openat(g2.dir,meta,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
|
||||
if(fd<0 || !(g2.meta=fdopen(fd,"w"))) {if(fd>=0)close(fd);g2.stopped=true;g2.active=false;return;}
|
||||
- fprintf(g2.meta,"{\"kind\":\"begin\",\"schema\":\"G2-new-v1\",\"epoch\":%u,\"label\":\"%s\",\"nonce\":\"%s\",\"output\":\"HEADLESS-1\",\"pass\":\"%"PRIxPTR"\",\"offscreen_output_key\":\"%"PRIxPTR"\",\"intrusive_error_queue\":true,\"error_queue_restorable\":false,\"readback_may_synchronize\":true,\"rows\":\"GL_LOW_TO_HIGH\",\"present_buffer\":\"NOT_PROVEN\",\"gpu_completion\":\"NOT_PROVEN\",\"lifetime\":\"NOT_PROVEN\"}\n",e,e==1?"A":e==2?"B":"A-return",g2.nonce,(uintptr_t)pass,(uintptr_t)pass->fx_offscreen_buffers);
|
||||
+ fprintf(g2.meta,"{\"kind\":\"begin\",\"schema\":\"G3-followup-v1\",\"epoch\":%u,\"label\":\"%s\",\"nonce\":\"%s\",\"output\":\"HEADLESS-1\",\"pass\":\"%"PRIxPTR"\",\"offscreen_output_key\":\"%"PRIxPTR"\",\"intrusive_error_queue\":true,\"error_queue_restorable\":false,\"readback_may_synchronize\":true,\"rows\":\"GL_LOW_TO_HIGH\",\"present_buffer\":\"NOT_PROVEN\",\"gpu_completion\":\"NOT_PROVEN\",\"lifetime\":\"NOT_PROVEN\"}\n",e,e==1?"A":e==2?"B":"A-return",g2.nonce,(uintptr_t)pass,(uintptr_t)pass->fx_offscreen_buffers);
|
||||
if(options->blur_data->num_passes!=3 || options->blur_strength!=1.0f || blur_data_should_parameters_blur_effects(options->blur_data)) {g2_fail("NON_PROSPECTIVE_BLUR_CONFIGURATION");return;}
|
||||
g2_sample_native(pass,source,0,options->tex_options.base.clip);
|
||||
}
|
||||
98
tooling/probes/step20c-corec-v2-followup/gpu/protocol.py
Normal file
98
tooling/probes/step20c-corec-v2-followup/gpu/protocol.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
"""G3 protocol tooling; never spawns compositor/client/GPU or grants review.
|
||||
prepare-plan prints prospective framing only. arm requires an external accepted
|
||||
review and runtime authorization. No retry is implemented. All files exclusive.
|
||||
"""
|
||||
import argparse,hashlib,json,os,stat,zlib,re
|
||||
from pathlib import Path
|
||||
from validate_source import need,metadata,halfpixel
|
||||
CAP=256*1024*1024
|
||||
FRAME=(800,600)
|
||||
STAGES=['source','down0','down1','down2','up2','up1','up0','optimized-cache','no-blur-cache','composition-presubmit']
|
||||
ROOT=Path(os.environ.get('MOSAIC_G3_DIR','/G3_ROOT_NOT_BOUND'))
|
||||
def owned(p,mode):
|
||||
s=p.lstat();need(not stat.S_ISLNK(s.st_mode) and s.st_uid==os.geteuid() and stat.S_IMODE(s.st_mode)==mode,'owned path/mode')
|
||||
return s
|
||||
def bounded(p,cap):
|
||||
s=owned(p,0o600);need(stat.S_ISREG(s.st_mode) and s.st_size<=cap and s.st_nlink==1,'bounded regular exclusive file')
|
||||
fd=os.open(p,os.O_RDONLY|os.O_NOFOLLOW|os.O_CLOEXEC)
|
||||
try:
|
||||
after=os.fstat(fd);need((s.st_dev,s.st_ino)==(after.st_dev,after.st_ino),'path identity')
|
||||
data=os.read(fd,cap+1);need(len(data)==s.st_size and len(data)<=cap,'bounded stable framing');return data
|
||||
finally:os.close(fd)
|
||||
def exclusive(p,b):
|
||||
fd=os.open(p,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_CLOEXEC,0o600)
|
||||
try:
|
||||
with os.fdopen(fd,'wb',closefd=False) as f:f.write(b);f.flush();os.fsync(fd)
|
||||
finally:os.close(fd)
|
||||
d=os.open(p.parent,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW)
|
||||
try:os.fsync(d)
|
||||
finally:os.close(d)
|
||||
def epoch(directory,e,nonce,prior=0):
|
||||
need(e in (1,2,3),'epoch')
|
||||
raw=bounded(directory/f'epoch-{e}.jsonl',1024*1024);need(raw.endswith(b'\n'),'metadata line framing')
|
||||
rows=[json.loads(x) for x in raw.splitlines()];need(len(rows)==18,'bounded exact event count')
|
||||
begin,end=rows[0],rows[-1]
|
||||
need(begin['kind']=='begin' and begin['schema']=='G3-followup-v1' and begin['epoch']==e and begin['nonce']==nonce and begin['label']==('A','B','A-return')[e-1],'begin identity')
|
||||
need(begin['output']=='HEADLESS-1' and begin['intrusive_error_queue'] is True and begin['error_queue_restorable'] is False,'intrusion/output')
|
||||
need(end['kind']=='end' and end['epoch']==e and end['points']==10 and end['accepted'] is True,'complete accepted epoch; rejection or incomplete A stops all')
|
||||
# Consumed files intentionally had one hard link during atomic consumption;
|
||||
# publication is accepted only after arm unlink gives a single link again.
|
||||
need(bounded(directory/f'epoch-{e}.consumed',80)==f'{nonce} {e}\n'.encode(),'consumed token')
|
||||
samples=[x for x in rows if x['kind']=='sample'];uniforms=[x for x in rows if x['kind']=='uniform']
|
||||
need(len(samples)==10 and len(uniforms)==6,'stage/uniform count')
|
||||
need([x['point'] for x in samples]==list(range(10)) and [x['point'] for x in uniforms]==list(range(1,7)),'strict point order')
|
||||
need([x['kind'] for x in rows[1:-1]]==['sample']+['uniform','sample']*6+['sample']*3,'query/draw/read sequence')
|
||||
total=prior;context=samples[0]['context'];source_analysis=[]
|
||||
for p,s in enumerate(samples):
|
||||
need(s['point']==p and s['stage']==STAGES[p] and s['epoch']==e and s['pass_sequence']==e and s['context']==context and context!='0','pass/context identity')
|
||||
need(s['result']=='READ_SUCCESS' and s['read_success'] is True and s['published'] is True and s['before']==s['after'],'GL/read/state acceptance')
|
||||
for key in ('pre_errors','query_errors','read_errors','restore_errors'):need(s[key]=={'exhausted':False,'codes':[]},'error queue ambiguity')
|
||||
need(s['client_version'] in (2,3) and re.fullmatch(rb'OpenGL ES 3\.([0-2])(?: [ -~]*)?',bytes.fromhex(s['gl_version_hex'])) is not None,'bounded actual GLES3 version')
|
||||
need(s['gl_major']==3 and s['gl_minor']==int(bytes.fromhex(s['gl_version_hex'])[12:13]) and bool(bytes.fromhex(s['glsl_hex'])),'consistent native GL version/GLSL')
|
||||
need(all(s.get(k) is True for k in ('version_queried','state_saved','bound','read_attempted','restore_attempted')),'observable exact read lifecycle')
|
||||
for key in ('version_errors','capability_errors','state_errors'):need(s[key]=={'exhausted':False,'codes':[]},'capability/state error attribution')
|
||||
a=s['attachment'];need(a[0]==0x8d41 and a[1]>0 and a[2]==0 and a[3:5]==s['native']['dimensions'] and s['status']==0x8cd5 and s['read_buffer']==0x8ce0 and s['samples']==0,'supported RBO attachment')
|
||||
need(s['channels'][:3]==[8,8,8] and s['channels'][3] in (0,8),'actual alpha storage')
|
||||
native=s['native'];need(native['fbo_rbo_tex'][1]==a[1] and all(int(native[key],16)>0 for key in ('fx','wlr','image')),'native attachment identity')
|
||||
d=s['dmabuf'];need(d is not None and d['borrowed'] is True and d['dimensions']==native['dimensions'],'actual borrowed dmabuf')
|
||||
metadata(dict(d,width=d['dimensions'][0],height=d['dimensions'][1]))
|
||||
need(all(p.get('fstat_ok') is True and all(type(p.get(k)) is int and p[k]>=0 for k in ('fd','dev','ino')) for p in d['planes']),'borrowed FD fstat identity observed')
|
||||
w,h=native['dimensions'];need((w,h)==FRAME,'prospective physical full-frame dimensions');n=w*h*4;need(0<n<=CAP-total and s['bytes']==n,'checked full-frame raw budget');total+=n
|
||||
need(s['file']==f'epoch-{e}-point-{p}.rgba','fixed exclusive raw filename')
|
||||
b=bounded(directory/s['file'],CAP);need(len(b)==n and f'{zlib.crc32(b):08x}'==s['crc32'],'raw framing/CRC')
|
||||
if p==0:
|
||||
colors=((32,192,224),(176,32,64)) if e==2 else ((245,245,245),(15,25,45))
|
||||
for y in range(h):
|
||||
for x in range(w):need(tuple(b[(y*w+x)*4:(y*w+x)*4+3])==colors[(x//8)%2],'native RGB source does not belong to current epoch')
|
||||
need(s.get('fresh_region_overflow') is not True and len(s['fresh_rectangles'])<=256 and all(len(r)==4 and 0<=r[0]<=r[2]<=w and 0<=r[1]<=r[3]<=h for r in s['fresh_rectangles']),'actual fresh region rectangles')
|
||||
for u in uniforms:
|
||||
need(u['type']==0x8b50 and u['program']>0 and u['location']>=0 and u['shader_default_float']=='mediump' and u['varying']=='mediump','uniform native program/type/qualifier')
|
||||
need(u['sampler']==[9729,9729,33071,33071] and len(u['precision_capability'])==3,'actual sampler/capability')
|
||||
source_analysis.append(halfpixel(*u['native_texture_dimensions'],u['point']<=3,u['actual_bits']))
|
||||
need(end['raw_total']==total,'cumulative raw budget')
|
||||
return {'epoch':e,'raw_total':total,'context':context,'observed_versions':[dict(egl_client_version=s['client_version'],gl_version=bytes.fromhex(s['gl_version_hex']).decode('ascii'),gl_major=s['gl_major'],gl_minor=s['gl_minor'],egl_gl_divergence=s['client_version']!=s['gl_major']) for s in samples],'uniform_comparison':source_analysis,'whole_field_causality':'NOT_PROVEN','native_present_buffer':'NOT_PROVEN','gpu_completion':'NOT_PROVEN'}
|
||||
def gate(directory,gatefile,binary):
|
||||
need(directory==ROOT and directory.resolve(strict=True)==ROOT,'fixed real acquisition path')
|
||||
need(str(ROOT).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-') and ROOT.name=='acquisition','new LAB acquisition only')
|
||||
owned(directory,0o700)
|
||||
g=json.loads(bounded(gatefile,65536));need(g['source_review']=='ACCEPTED' and g['gl_observability']=='PASS' and g['runtime_authorized'] is True and g['diagnostic']=='GPU-G3' and g['acquisition_root']==str(ROOT),'independent external review/authorization gate')
|
||||
need(hashlib.sha256(binary.read_bytes()).hexdigest()==g['binary_sha256'],'distinct reviewed binary identity')
|
||||
return g
|
||||
|
||||
def arm(directory,e,nonce,gatefile,binary):
|
||||
gate(directory,gatefile,binary);need(len(nonce)==64 and all(x in '0123456789abcdef' for x in nonce),'nonce')
|
||||
# Prior complete success is mandatory. An existing consumed/current epoch,
|
||||
# failed A, or any unexpected future epoch prevents every subsequent arm.
|
||||
total=0
|
||||
for prior in range(1,e):total=epoch(directory,prior,nonce,total)['raw_total']
|
||||
need(e in (1,2,3),'one-shot three epochs')
|
||||
for future in range(e,5):need(not any(directory.glob(f'epoch-{future}.*')),'no retry/future token')
|
||||
exclusive(directory/f'epoch-{e}.arm',f'{nonce} {e}\n'.encode())
|
||||
|
||||
def plan():
|
||||
return {'schema':'G3-followup-v1','status':'EXPERIMENTAL_SOURCE_ONLY_REVIEW_REQUIRED','diagnostic':'GPU-G3','max_raw_bytes':CAP,'epochs':['A','B','A-return'],'points':STAGES,'full_field':[0,0,800,600],'ROI':[0,0,800,600],'ROI_policy':'FULL_FIELD_NO_POSTHOC_MASK','requirements':['Independent R-GPU exact-source acceptance','GL observability acceptance','Explicit new pair and distinct binary identity','External one-shot ledger reservation before compositor spawn','Explicit source A/B/A client commands with original A byte identity','If A incomplete/rejected STOP; preserve bytes, no B, no retry'],'intrusive':['glGetError consumes nonrestorable queue','glReadPixels may synchronize timing'],'unobserved':['native presented physical buffer','GPU completion','GPU object lifetime','atomic generation'],'launches_gpu':False}
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('operation',choices=['prepare-plan','verify-epoch','arm']);p.add_argument('--epoch',type=int);p.add_argument('--nonce');p.add_argument('--gate',type=Path);p.add_argument('--binary',type=Path);a=p.parse_args()
|
||||
if a.operation=='prepare-plan':print(json.dumps(plan(),indent=2))
|
||||
elif a.operation=='verify-epoch':print(json.dumps(epoch(ROOT,a.epoch,a.nonce),indent=2))
|
||||
else:arm(ROOT,a.epoch,a.nonce,a.gate,a.binary)
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_layer_shell_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2017 Drew DeVault
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_layer_shell_v1" version="4">
|
||||
<description summary="create surfaces that are layers of the desktop">
|
||||
Clients can use this interface to assign the surface_layer role to
|
||||
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
rendered with a defined z-depth respective to each other. They may also be
|
||||
anchored to the edges and corners of a screen and specify input handling
|
||||
semantics. This interface should be suitable for the implementation of
|
||||
many desktop shell components, and a broad number of other applications
|
||||
that interact with the desktop.
|
||||
</description>
|
||||
|
||||
<request name="get_layer_surface">
|
||||
<description summary="create a layer_surface from a surface">
|
||||
Create a layer surface for an existing surface. This assigns the role of
|
||||
layer_surface, or raises a protocol error if another role is already
|
||||
assigned.
|
||||
|
||||
Creating a layer surface from a wl_surface which has a buffer attached
|
||||
or committed is a client error, and any attempts by a client to attach
|
||||
or manipulate a buffer prior to the first layer_surface.configure call
|
||||
must also be treated as errors.
|
||||
|
||||
After creating a layer_surface object and setting it up, the client
|
||||
must perform an initial commit without any buffer attached.
|
||||
The compositor will reply with a layer_surface.configure event.
|
||||
The client must acknowledge it and is then allowed to attach a buffer
|
||||
to map the surface.
|
||||
|
||||
You may pass NULL for output to allow the compositor to decide which
|
||||
output to use. Generally this will be the one that the user most
|
||||
recently interacted with.
|
||||
|
||||
Clients can specify a namespace that defines the purpose of the layer
|
||||
surface.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
|
||||
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
|
||||
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
|
||||
</enum>
|
||||
|
||||
<enum name="layer">
|
||||
<description summary="available layers for surfaces">
|
||||
These values indicate which layers a surface can be rendered in. They
|
||||
are ordered by z depth, bottom-most first. Traditional shell surfaces
|
||||
will typically be rendered between the bottom and top layers.
|
||||
Fullscreen shell surfaces are typically rendered at the top layer.
|
||||
Multiple surfaces can share a single layer, and ordering within a
|
||||
single layer is undefined.
|
||||
</description>
|
||||
|
||||
<entry name="background" value="0"/>
|
||||
<entry name="bottom" value="1"/>
|
||||
<entry name="top" value="2"/>
|
||||
<entry name="overlay" value="3"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<request name="destroy" type="destructor" since="3">
|
||||
<description summary="destroy the layer_shell object">
|
||||
This request indicates that the client will not use the layer_shell
|
||||
object any more. Objects that have been created through this instance
|
||||
are not affected.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_layer_surface_v1" version="4">
|
||||
<description summary="layer metadata interface">
|
||||
An interface that may be implemented by a wl_surface, for surfaces that
|
||||
are designed to be rendered as a layer of a stacked desktop-like
|
||||
environment.
|
||||
|
||||
Layer surface state (layer, size, anchor, exclusive zone,
|
||||
margin, interactivity) is double-buffered, and will be applied at the
|
||||
time wl_surface.commit of the corresponding wl_surface is called.
|
||||
|
||||
Attaching a null buffer to a layer surface unmaps it.
|
||||
|
||||
Unmapping a layer_surface means that the surface cannot be shown by the
|
||||
compositor until it is explicitly mapped again. The layer_surface
|
||||
returns to the state it had right after layer_shell.get_layer_surface.
|
||||
The client can re-map the surface by performing a commit without any
|
||||
buffer attached, waiting for a configure event and handling it as usual.
|
||||
</description>
|
||||
|
||||
<request name="set_size">
|
||||
<description summary="sets the size of the surface">
|
||||
Sets the size of the surface in surface-local coordinates. The
|
||||
compositor will display the surface centered with respect to its
|
||||
anchors.
|
||||
|
||||
If you pass 0 for either value, the compositor will assign it and
|
||||
inform you of the assignment in the configure event. You must set your
|
||||
anchor to opposite edges in the dimensions you omit; not doing so is a
|
||||
protocol error. Both values are 0 by default.
|
||||
|
||||
Size is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="set_anchor">
|
||||
<description summary="configures the anchor point of the surface">
|
||||
Requests that the compositor anchor the surface to the specified edges
|
||||
and corners. If two orthogonal edges are specified (e.g. 'top' and
|
||||
'left'), then the anchor point will be the intersection of the edges
|
||||
(e.g. the top left corner of the output); otherwise the anchor point
|
||||
will be centered on that edge, or in the center if none is specified.
|
||||
|
||||
Anchor is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="anchor" type="uint" enum="anchor"/>
|
||||
</request>
|
||||
|
||||
<request name="set_exclusive_zone">
|
||||
<description summary="configures the exclusive geometry of this surface">
|
||||
Requests that the compositor avoids occluding an area with other
|
||||
surfaces. The compositor's use of this information is
|
||||
implementation-dependent - do not assume that this region will not
|
||||
actually be occluded.
|
||||
|
||||
A positive value is only meaningful if the surface is anchored to one
|
||||
edge or an edge and both perpendicular edges. If the surface is not
|
||||
anchored, anchored to only two perpendicular edges (a corner), anchored
|
||||
to only two parallel edges or anchored to all edges, a positive value
|
||||
will be treated the same as zero.
|
||||
|
||||
A positive zone is the distance from the edge in surface-local
|
||||
coordinates to consider exclusive.
|
||||
|
||||
Surfaces that do not wish to have an exclusive zone may instead specify
|
||||
how they should interact with surfaces that do. If set to zero, the
|
||||
surface indicates that it would like to be moved to avoid occluding
|
||||
surfaces with a positive exclusive zone. If set to -1, the surface
|
||||
indicates that it would not like to be moved to accommodate for other
|
||||
surfaces, and the compositor should extend it all the way to the edges
|
||||
it is anchored to.
|
||||
|
||||
For example, a panel might set its exclusive zone to 10, so that
|
||||
maximized shell surfaces are not shown on top of it. A notification
|
||||
might set its exclusive zone to 0, so that it is moved to avoid
|
||||
occluding the panel, but shell surfaces are shown underneath it. A
|
||||
wallpaper or lock screen might set their exclusive zone to -1, so that
|
||||
they stretch below or over the panel.
|
||||
|
||||
The default value is 0.
|
||||
|
||||
Exclusive zone is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="zone" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="set_margin">
|
||||
<description summary="sets a margin from the anchor point">
|
||||
Requests that the surface be placed some distance away from the anchor
|
||||
point on the output, in surface-local coordinates. Setting this value
|
||||
for edges you are not anchored to has no effect.
|
||||
|
||||
The exclusive zone includes the margin.
|
||||
|
||||
Margin is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="top" type="int"/>
|
||||
<arg name="right" type="int"/>
|
||||
<arg name="bottom" type="int"/>
|
||||
<arg name="left" type="int"/>
|
||||
</request>
|
||||
|
||||
<enum name="keyboard_interactivity">
|
||||
<description summary="types of keyboard interaction possible for a layer shell surface">
|
||||
Types of keyboard interaction possible for layer shell surfaces. The
|
||||
rationale for this is twofold: (1) some applications are not interested
|
||||
in keyboard events and not allowing them to be focused can improve the
|
||||
desktop experience; (2) some applications will want to take exclusive
|
||||
keyboard focus.
|
||||
</description>
|
||||
|
||||
<entry name="none" value="0">
|
||||
<description summary="no keyboard focus is possible">
|
||||
This value indicates that this surface is not interested in keyboard
|
||||
events and the compositor should never assign it the keyboard focus.
|
||||
|
||||
This is the default value, set for newly created layer shell surfaces.
|
||||
|
||||
This is useful for e.g. desktop widgets that display information or
|
||||
only have interaction with non-keyboard input devices.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="exclusive" value="1">
|
||||
<description summary="request exclusive keyboard focus">
|
||||
Request exclusive keyboard focus if this surface is above the shell surface layer.
|
||||
|
||||
For the top and overlay layers, the seat will always give
|
||||
exclusive keyboard focus to the top-most layer which has keyboard
|
||||
interactivity set to exclusive. If this layer contains multiple
|
||||
surfaces with keyboard interactivity set to exclusive, the compositor
|
||||
determines the one receiving keyboard events in an implementation-
|
||||
defined manner. In this case, no guarantee is made when this surface
|
||||
will receive keyboard focus (if ever).
|
||||
|
||||
For the bottom and background layers, the compositor is allowed to use
|
||||
normal focus semantics.
|
||||
|
||||
This setting is mainly intended for applications that need to ensure
|
||||
they receive all keyboard events, such as a lock screen or a password
|
||||
prompt.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="on_demand" value="2" since="4">
|
||||
<description summary="request regular keyboard focus semantics">
|
||||
This requests the compositor to allow this surface to be focused and
|
||||
unfocused by the user in an implementation-defined manner. The user
|
||||
should be able to unfocus this surface even regardless of the layer
|
||||
it is on.
|
||||
|
||||
Typically, the compositor will want to use its normal mechanism to
|
||||
manage keyboard focus between layer shell surfaces with this setting
|
||||
and regular toplevels on the desktop layer (e.g. click to focus).
|
||||
Nevertheless, it is possible for a compositor to require a special
|
||||
interaction to focus or unfocus layer shell surfaces (e.g. requiring
|
||||
a click even if focus follows the mouse normally, or providing a
|
||||
keybinding to switch focus between layers).
|
||||
|
||||
This setting is mainly intended for desktop shell components (e.g.
|
||||
panels) that allow keyboard interaction. Using this option can allow
|
||||
implementing a desktop shell that can be fully usable without the
|
||||
mouse.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<request name="set_keyboard_interactivity">
|
||||
<description summary="requests keyboard events">
|
||||
Set how keyboard events are delivered to this surface. By default,
|
||||
layer shell surfaces do not receive keyboard events; this request can
|
||||
be used to change this.
|
||||
|
||||
This setting is inherited by child surfaces set by the get_popup
|
||||
request.
|
||||
|
||||
Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||
you do not want to receive them, set the input region on your surface
|
||||
to an empty region.
|
||||
|
||||
Keyboard interactivity is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
|
||||
</request>
|
||||
|
||||
<request name="get_popup">
|
||||
<description summary="assign this layer_surface as an xdg_popup parent">
|
||||
This assigns an xdg_popup's parent to this layer_surface. This popup
|
||||
should have been created via xdg_surface::get_popup with the parent set
|
||||
to NULL, and this request must be invoked before committing the popup's
|
||||
initial state.
|
||||
|
||||
See the documentation of xdg_popup for more details about what an
|
||||
xdg_popup is and how it is used.
|
||||
</description>
|
||||
<arg name="popup" type="object" interface="xdg_popup"/>
|
||||
</request>
|
||||
|
||||
<request name="ack_configure">
|
||||
<description summary="ack a configure event">
|
||||
When a configure event is received, if a client commits the
|
||||
surface in response to the configure event, then the client
|
||||
must make an ack_configure request sometime before the commit
|
||||
request, passing along the serial of the configure event.
|
||||
|
||||
If the client receives multiple configure events before it
|
||||
can respond to one, it only has to ack the last configure event.
|
||||
|
||||
A client is not required to commit immediately after sending
|
||||
an ack_configure request - it may even ack_configure several times
|
||||
before its next surface commit.
|
||||
|
||||
A client may send multiple ack_configure requests before committing, but
|
||||
only the last request sent before a commit indicates which configure
|
||||
event the client really is responding to.
|
||||
</description>
|
||||
<arg name="serial" type="uint" summary="the serial from the configure event"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the layer_surface">
|
||||
This request destroys the layer surface.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="configure">
|
||||
<description summary="suggest a surface change">
|
||||
The configure event asks the client to resize its surface.
|
||||
|
||||
Clients should arrange their surface for the new states, and then send
|
||||
an ack_configure request with the serial sent in this configure event at
|
||||
some point before committing the new surface.
|
||||
|
||||
The client is free to dismiss all but the last configure event it
|
||||
received.
|
||||
|
||||
The width and height arguments specify the size of the window in
|
||||
surface-local coordinates.
|
||||
|
||||
The size is a hint, in the sense that the client is free to ignore it if
|
||||
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
||||
resize in steps of NxM pixels). If the client picks a smaller size and
|
||||
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
|
||||
surface will be centered on this axis.
|
||||
|
||||
If the width or height arguments are zero, it means the client should
|
||||
decide its own window dimension.
|
||||
</description>
|
||||
<arg name="serial" type="uint"/>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</event>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="surface should be closed">
|
||||
The closed event is sent by the compositor when the surface will no
|
||||
longer be shown. The output may have been destroyed or the user may
|
||||
have asked for it to be removed. Further changes to the surface will be
|
||||
ignored. The client should destroy the resource after receiving this
|
||||
event, and create a new surface if they so choose.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
|
||||
<entry name="invalid_size" value="1" summary="size is invalid"/>
|
||||
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
|
||||
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
|
||||
</enum>
|
||||
|
||||
<enum name="anchor" bitfield="true">
|
||||
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
|
||||
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
|
||||
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
|
||||
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<request name="set_layer" since="2">
|
||||
<description summary="change the layer of the surface">
|
||||
Change the layer that the surface is rendered on.
|
||||
|
||||
Layer is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
1420
tooling/probes/step20c-corec-v2-followup/gpu/protocols/xdg-shell.xml
Normal file
1420
tooling/probes/step20c-corec-v2-followup/gpu/protocols/xdg-shell.xml
Normal file
File diff suppressed because it is too large
Load diff
73
tooling/probes/step20c-corec-v2-followup/gpu/reproduce.py
Normal file
73
tooling/probes/step20c-corec-v2-followup/gpu/reproduce.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""Assemble private G3 sources, never build or launch a compositor."""
|
||||
import argparse
|
||||
import ctypes
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import resource
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
KIT = Path(__file__).resolve().parent
|
||||
PARENT = KIT.parents[1] / 'step20c-corec-v2'
|
||||
PATCH_SHA = '77d7445ceaf643fd5cc708d31c3f2c77feaeb80f8130c419c55e5514ecc8a75d'
|
||||
|
||||
|
||||
def sha(p):
|
||||
return hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def tree(p):
|
||||
return {str(f.relative_to(p)): {'sha256': sha(f), 'mode': oct(stat.S_IMODE(f.stat().st_mode))}
|
||||
for f in sorted(p.rglob('*')) if f.is_file() and '.git' not in f.relative_to(p).parts}
|
||||
|
||||
|
||||
def main():
|
||||
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
||||
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--lab', type=Path, required=True)
|
||||
parser.add_argument('--core', type=Path, required=True)
|
||||
parser.add_argument('--destination', type=Path, required=True)
|
||||
a = parser.parse_args()
|
||||
lab, core, dest = a.lab.resolve(strict=True), a.core.resolve(strict=True), a.destination.resolve()
|
||||
assert str(lab).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-')
|
||||
assert lab.stat().st_uid == os.geteuid() and stat.S_IMODE(lab.stat().st_mode) == 0o700
|
||||
assert dest.is_relative_to(lab) and not dest.exists() and not dest.is_relative_to(core)
|
||||
before = tree(core)
|
||||
expected = json.loads((PARENT / 'provenance.json').read_text())['components']['scene']['files']
|
||||
assert before == expected, 'complete CORE bytes/modes differ from versioned provenance'
|
||||
patch = PARENT / 'gpu/g2-native.patch'
|
||||
assert sha(patch) == PATCH_SHA
|
||||
shutil.copytree(core, dest, ignore=shutil.ignore_patterns('.git'))
|
||||
result = subprocess.run(['patch', '--batch', '--forward', '-p1', '-i', str(patch)],
|
||||
cwd=dest, capture_output=True, text=True)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
# WHY: hook placement is frozen G2 instrumentation; only admission and
|
||||
# observability headers differ. CONTRACT: no shader or CORE source mutation.
|
||||
native = dest / 'render/fx_renderer'
|
||||
delta = KIT / 'g3-admission.patch'
|
||||
result = subprocess.run(['patch', '--batch', '--forward', '-p1', '-i', str(delta)],
|
||||
cwd=dest, capture_output=True, text=True)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
for name in ('g2_gl.h', 'g2_native.h'):
|
||||
assert sha(KIT / name) == sha(native / name), 'delta/header mismatch'
|
||||
assert sha(native / 'fx_pass.c') == 'c2886ef259fc91431dfe97841387082420ab80a0fe2f07e7dcdb71fc95731181'
|
||||
after = tree(dest)
|
||||
changed = sorted(n for n in before.keys() | after.keys() if before.get(n) != after.get(n))
|
||||
assert changed == ['render/fx_renderer/fx_pass.c', 'render/fx_renderer/g2_gl.h', 'render/fx_renderer/g2_native.h']
|
||||
assert tree(core) == before
|
||||
receipt = {'schema': 'G3-source-assembly-v1', 'status': 'EXPERIMENTAL_SOURCE_ONLY',
|
||||
'review': 'PENDING_INDEPENDENT', 'runtime': 'NOT_RUN', 'build': 'NOT_RUN',
|
||||
'core_manifest_sha256': sha(PARENT / 'provenance.json'),
|
||||
'g2_patch_sha256': sha(patch), 'g3_delta_sha256': sha(delta), 'changed': {n: after[n] for n in changed},
|
||||
'core_unchanged': True, 'acquisition_root': str(lab / 'gpu-work/acquisition'),
|
||||
'G3_ROOT_compile_definition_required': True}
|
||||
(dest.parent / (dest.name + '-receipt.json')).write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print(json.dumps(receipt, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
68
tooling/probes/step20c-corec-v2-followup/gpu/scenario.py
Normal file
68
tooling/probes/step20c-corec-v2-followup/gpu/scenario.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Executable G3 scenario adapter. Import has NO run/arm/spawn side effect.
|
||||
The parent binds an accepted new diagnostic pair Session and reserves its own G3 ledger.
|
||||
The common Session owns its private sockets, process groups and timeout cleanup.
|
||||
"""
|
||||
import hashlib,json,time
|
||||
from pathlib import Path
|
||||
from PIL import ImageChops
|
||||
from protocol import ROOT,arm,epoch,gate,need
|
||||
|
||||
def exact_rgb(a,b):
|
||||
need(a.mode==b.mode=='RGB' and a.size==b.size,'whole-output homologous RGB')
|
||||
d=ImageChops.difference(a,b)
|
||||
# Exact all-output zero tolerance. This is a result, not an admission mask.
|
||||
return {'equal':d.getbbox() is None,'bbox':list(d.getbbox()) if d.getbbox() else None,'extrema':[list(x) for x in d.getextrema()],'pixels':a.width*a.height,'difference_count':sum(any(p) for p in d.getdata()),'differences_per_row':[sum(any(d.getpixel((x,y))) for x in range(a.width)) for y in range(a.height)],'tolerance':0}
|
||||
def run(s,binding):
|
||||
"""Must be called once AFTER external review+ledger binding, BEFORE s.start.
|
||||
binding: gate_file, binary, client, nonce, source_manifest_sha256,
|
||||
G3_ledger_reserved=True, reviewed_scene_sha256. All operations debit s.ledger.
|
||||
No retry path; an exception before epoch A completes bypasses B/A-return.
|
||||
"""
|
||||
need(binding.get('G3_ledger_reserved') is True,'external closed-ledger reservation absent')
|
||||
need(s.spec['id']=='GPU-G3' and s.spec['binary']=='gpu','distinct G3 Session identity/static pair')
|
||||
need(s.auth['binary']==binding['binary'],'accepted exact third executable')
|
||||
need(s.auth['manifest_sha256']==binding['source_manifest_sha256'],'frozen G3 manifest identity')
|
||||
gate(ROOT,Path(binding['gate_file']),Path(binding['binary']))
|
||||
need(not any(ROOT.glob('epoch-*')),'fresh one-shot publication directory')
|
||||
nonce=binding['nonce'];s.env.update(MOSAIC_G3_DIR=str(ROOT),MOSAIC_G3_NONCE=nonce,WLR_RENDERER='gles2')
|
||||
s.start()
|
||||
# Session captures actual executable hash, mapped libraries, renderer and
|
||||
# explicit private sockets. Its caller owns finally/cleanup and verdict.
|
||||
client=Path(binding['client']);need(client.is_file() and hashlib.sha256(client.read_bytes()).hexdigest()==binding['client_sha256'],'new client identity')
|
||||
s.cmd('workspace 2');s.spawn('g2-reference',[client,'reference']);s.wait(lambda:s.find('g2-reference'));rid=s.find('g2-reference')['id'];s.cmd(f'[con_id={rid}] border none')
|
||||
s.cmd('workspace 1');s.cmd('layout mosaic');s.cmd('gaps inner 0')
|
||||
s.spawn('g2-background',[client,'background']);s.wait(lambda:'BACKGROUND_CONFIGURED ' in s.log('g2-background'))
|
||||
s.spawn('g2-glass',[client,'glass']);s.wait(lambda:s.find('g2-glass'));gid=s.find('g2-glass')['id'];s.cmd(f'[con_id={gid}] border none')
|
||||
s.cmd('blur enable');s.cmd('blur_xray disable');s.cmd('blur_passes 3');s.cmd('blur_radius 5');s.cmd('blur_brightness 1');s.cmd('blur_contrast 1');s.cmd('blur_noise 0');s.cmd('blur_saturation 1')
|
||||
# Settle setup before the first arm. This sleep carries no proof of GPU
|
||||
# completion; exact native source admission rejects any wrong candidate.
|
||||
time.sleep(.35)
|
||||
reference=s.capture('G3-H2-baseline1','HEADLESS-2');time.sleep(.15)
|
||||
reference2=s.capture('G3-H2-baseline2','HEADLESS-2')
|
||||
baseline=exact_rgb(reference,reference2);s.save('G3-H2-baseline',baseline)
|
||||
need(baseline['equal'],'static whole H2 baseline changed before A')
|
||||
captures=[];validated=[];total=0
|
||||
for e,command in enumerate(('G3-A','G3-B','G3-A-return'),1):
|
||||
s.debit('mutation');s.record('G3-epoch-reservation',epoch=e,raw_cap=256*1024*1024)
|
||||
arm(ROOT,e,nonce,Path(binding['gate_file']),Path(binding['binary']))
|
||||
s.proc['g2-background'].stdin.write(command+'\n');s.proc['g2-background'].stdin.flush()
|
||||
s.wait(lambda:f'G3_SOURCE_BYTES command={command} ' in s.log('g2-background'))
|
||||
path=ROOT/f'epoch-{e}.jsonl'
|
||||
def complete_or_rejected():
|
||||
if not path.exists():return False
|
||||
data=path.read_bytes();need(len(data)<=1024*1024,'bounded metadata while waiting')
|
||||
if b'"kind":"rejected"' in data:raise RuntimeError(f'epoch {e} rejected; STOP, no next command/retry')
|
||||
return data.endswith(b'\n') and b'"kind":"end"' in data
|
||||
s.wait(complete_or_rejected)
|
||||
# Failure here exits before subsequent epoch token or source command.
|
||||
result=epoch(ROOT,e,nonce,total);total=result['raw_total'];validated.append(result)
|
||||
pictures=s.pair_capture(f'G3-{command}');captures.append(pictures);s.snapshot(f'G3-{command}')
|
||||
h2=exact_rgb(reference,pictures[1]);s.save(f'G3-{command}-wholeH2',h2)
|
||||
need(h2['equal'],'whole H2 changed: rejected epoch, STOP without retry')
|
||||
s.record('G3-epoch-accepted',epoch=e,command=command,native_raw_total=total)
|
||||
whole_h1=exact_rgb(captures[0][0],captures[2][0]);whole_h2=[exact_rgb(captures[0][1],x[1]) for x in captures[1:]]
|
||||
sa=(ROOT/'epoch-1-point-0.rgba').read_bytes();sr=(ROOT/'epoch-3-point-0.rgba').read_bytes()
|
||||
result={'schema':'G3-followup-v1','epochs':validated,'native_source_A_Areturn_exact':sa==sr,'whole_H1_A_Areturn':whole_h1,'whole_H2_baseline':baseline,'whole_H2_A_B_Areturn':whole_h2,'whole_field_causality':'NOT_PROVEN','physical_present_buffer':'NOT_PROVEN','gpu_completion':'NOT_PROVEN','atomic_generation':'NOT_PROVEN','instrumentation':'INTRUSIVE_ERROR_QUEUE_AND_READBACK_SYNC','source_commands':['G3-A','G3-B','G3-A-return']}
|
||||
s.save('G3-result',result)
|
||||
for name in ('g2-glass','g2-background','g2-reference'):s.close_client(name,'close')
|
||||
return result
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
"""G3 offline C17/FakeGL/protocol/client checks; no compositor build/run."""
|
||||
import argparse
|
||||
import ctypes
|
||||
import json
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import resource
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
KIT = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def main():
|
||||
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
||||
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--lab', required=True, type=Path)
|
||||
p.add_argument('--scene', required=True, type=Path)
|
||||
a = p.parse_args()
|
||||
lab, scene = a.lab.resolve(strict=True), a.scene.resolve(strict=True)
|
||||
assert str(lab).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-')
|
||||
assert scene.is_relative_to(lab)
|
||||
work = lab / 'g3-offline'
|
||||
work.mkdir(mode=0o700, exist_ok=True)
|
||||
env = {'PATH': '/usr/bin:/bin', 'LANG': 'C.UTF-8'}
|
||||
for key, name in [('TMPDIR','scratch'),('TMP','scratch'),('TEMP','scratch'),
|
||||
('XDG_CACHE_HOME','cache'),('XDG_DATA_HOME','data'),
|
||||
('PYTHONPYCACHEPREFIX','pycache'),('CCACHE_DIR','ccache')]:
|
||||
path = work / name
|
||||
path.mkdir(mode=0o700, exist_ok=True)
|
||||
env[key] = str(path)
|
||||
flags = shlex.split(subprocess.check_output(['pkg-config','--cflags','wlroots-0.20','pixman-1','egl','glesv2','libdrm','wayland-server'],env=env,text=True))
|
||||
base = ['cc','-std=c17','-D_GNU_SOURCE','-DWLR_USE_UNSTABLE','-DWLR_PRIVATE=',
|
||||
'-DWLR_LITTLE_ENDIAN=1','-DWLR_BIG_ENDIAN=0','-Wall','-Wextra','-Werror',
|
||||
'-Wno-unused-parameter','-Wno-missing-braces','-I'+str(scene/'include')]
|
||||
root = '-DG3_ROOT="'+str(lab/'gpu-work/acquisition')+'"'
|
||||
commands = [
|
||||
['cc','-std=c17','-Wall','-Wextra','-Werror','-I'+str(KIT),str(KIT/'test_gl.c'),'-o',str(work/'test_gl')],
|
||||
[str(work/'test_gl')],
|
||||
[sys.executable,str(KIT/'test_mutations.py')],
|
||||
[sys.executable,str(KIT/'validate_source.py')],
|
||||
[sys.executable,str(KIT/'test_protocol.py')],
|
||||
[sys.executable,str(KIT/'test_begin.py')],
|
||||
base + flags + [root,'-I'+str(KIT),'-Wno-unused-function','-ffunction-sections','-fdata-sections',str(KIT/'test_native.c'),'-Wl,--gc-sections','-o',str(work/'test_native')],
|
||||
[str(work/'test_native')],
|
||||
base + flags + [root,'-fsyntax-only',str(scene/'render/fx_renderer/fx_pass.c')],
|
||||
]
|
||||
for xml, header in [('wlr-layer-shell-unstable-v1.xml','layer-shell-client.h'),('xdg-shell.xml','xdg-shell-client.h')]:
|
||||
commands.append(['wayland-scanner','client-header',str(KIT/'protocols'/xml),str(work/header)])
|
||||
commands.append(['cc','-std=c17','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-Wno-missing-braces','-I'+str(work),'-fsyntax-only',str(KIT/'clients-g2.c')])
|
||||
rows=[]
|
||||
for i, command in enumerate(commands):
|
||||
r = subprocess.run(command, cwd=work, env=env, text=True, capture_output=True, timeout=60)
|
||||
log = work / f'test-{i}.log'
|
||||
log.write_text(r.stdout+r.stderr)
|
||||
rows.append({'command':command,'exit':r.returncode,'log':log.name})
|
||||
print(f'{i}: exit={r.returncode}\n{r.stdout}{r.stderr}',flush=True)
|
||||
if r.returncode:
|
||||
break
|
||||
passed=len(rows)==len(commands) and all(r['exit']==0 for r in rows)
|
||||
(work/'receipt.json').write_text(json.dumps({'schema':'G3-offline-v1','pass':passed,
|
||||
'review':'PENDING_INDEPENDENT','runtime':'NOT_RUN','compositor_build':'NOT_RUN',
|
||||
'environment':env,'scene':str(scene),'native_sha256':{n:hashlib.sha256((scene/'render/fx_renderer'/n).read_bytes()).hexdigest() for n in ('fx_pass.c','g2_gl.h','g2_native.h')},'headers':{n:hashlib.sha256(Path(n).read_bytes()).hexdigest() for n in ('/usr/include/EGL/egl.h','/usr/include/GLES3/gl3.h','/usr/include/libdrm/drm_fourcc.h')},'commands':rows},indent=2)+'\n')
|
||||
raise SystemExit(0 if passed else 1)
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
73
tooling/probes/step20c-corec-v2-followup/gpu/test_begin.py
Normal file
73
tooling/probes/step20c-corec-v2-followup/gpu/test_begin.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""Compile the exact native arm consumer against bounded filesystem fixtures."""
|
||||
import ctypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
import resource
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
||||
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
|
||||
KIT = Path(__file__).resolve().parent
|
||||
header = (KIT/'g2_native.h').read_text()
|
||||
marker = 'static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options) {'
|
||||
start = header.index(marker)
|
||||
end = header.index('\n}\n',start)+3
|
||||
body = header[start:end]
|
||||
prefix = r'''
|
||||
#define _GNU_SOURCE
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <errno.h>
|
||||
struct fx_gles_render_pass {void *fx_offscreen_buffers;};
|
||||
struct fx_framebuffer {int unused;};
|
||||
struct bd {int num_passes;};
|
||||
struct fx_render_blur_pass_options {struct bd *blur_data;float blur_strength;struct {struct {void *clip;}base;}tex_options;};
|
||||
static struct {bool stopped,active;unsigned epoch,point;size_t used;int dir;FILE *meta;const void *pass;uintptr_t pass_sequence;char nonce[65];}g2;
|
||||
static bool g2_intended_output(void *p){(void)p;return true;}
|
||||
static bool g2_open(void){return true;}
|
||||
static void g2_fail(const char *s){(void)s;g2.stopped=true;}
|
||||
static bool blur_data_should_parameters_blur_effects(void *p){(void)p;return false;}
|
||||
static void g2_sample_native(void *p,void *b,unsigned x,void *r){(void)p;(void)b;(void)x;(void)r;}
|
||||
'''
|
||||
suffix = r'''
|
||||
int main(int argc,char **argv) {
|
||||
(void)argc;struct rlimit r={0,0};assert(!setrlimit(RLIMIT_CORE,&r));assert(!prctl(PR_SET_DUMPABLE,0));
|
||||
g2.dir=open(argv[1],O_RDONLY|O_DIRECTORY|O_NOFOLLOW);assert(g2.dir>=0);memset(g2.nonce,'a',64);
|
||||
struct fx_gles_render_pass pass={0};struct bd bd={.num_passes=3};
|
||||
struct fx_render_blur_pass_options options={.blur_data=&bd,.blur_strength=1};
|
||||
g2_begin(&pass,NULL,&options);
|
||||
if(!strcmp(argv[2],"accept")){assert(g2.active&&!g2.stopped&&g2.epoch==1);assert(!fclose(g2.meta));}
|
||||
else assert(g2.stopped&&!g2.active&&g2.epoch==0);
|
||||
assert(!close(g2.dir));return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix='g3-begin-',dir=os.environ['TMPDIR']) as name:
|
||||
work=Path(name);work.chmod(0o700)
|
||||
source=work/'begin.c';source.write_text(prefix+body+suffix)
|
||||
binary=work/'begin'
|
||||
subprocess.run(['cc','-std=c17','-Wall','-Wextra','-Werror',str(source),'-o',str(binary)],check=True)
|
||||
for case in ('fifo','symlink','wrong-mode','hardlink','oversize','consumed','accept'):
|
||||
d=work/case;d.mkdir(mode=0o700);arm=d/'epoch-1.arm'
|
||||
if case=='fifo':os.mkfifo(arm,0o600)
|
||||
elif case=='symlink':arm.symlink_to('/dev/null')
|
||||
else:
|
||||
arm.write_bytes(b'a'*64+b' 1\n');arm.chmod(0o600)
|
||||
if case=='wrong-mode':arm.chmod(0o644)
|
||||
if case=='hardlink':os.link(arm,d/'alias')
|
||||
if case=='oversize':arm.write_bytes(b'a'*1000)
|
||||
if case=='consumed':(d/'epoch-1.consumed').write_bytes(b'old')
|
||||
subprocess.run([str(binary),str(d),case],check=True,timeout=3)
|
||||
if case=='accept':assert not arm.exists() and (d/'epoch-1.consumed').exists()
|
||||
else:assert arm.exists() or arm.is_symlink()
|
||||
print('PASS exact native g2_begin',case)
|
||||
87
tooling/probes/step20c-corec-v2-followup/gpu/test_gl.c
Normal file
87
tooling/probes/step20c-corec-v2-followup/gpu/test_gl.c
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#define _GNU_SOURCE
|
||||
#include "g2_gl.h"
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/prctl.h>
|
||||
static struct g2_state state;
|
||||
static int mode, read_calls, unsupported, pending, query_count;
|
||||
static bool read_done;
|
||||
static const char *version="OpenGL ES 3.2 FakeGL";
|
||||
static void reset(int m) {
|
||||
memset(&state,0,sizeof(state));state.read_fbo=11;state.draw_fbo=12;state.rbo=13;
|
||||
state.pack=8;state.pbo=15;state.row=32;state.skip_rows=2;state.skip_pixels=3;
|
||||
state.viewport[2]=2;state.viewport[3]=3;state.scissor[2]=2;state.scissor[3]=3;
|
||||
state.program=21;state.active=GL_TEXTURE0;state.texture=22;
|
||||
mode=m;read_calls=unsupported=pending=query_count=0;read_done=false;
|
||||
version=(m==6||m==14)?"OpenGL ES 2.0 FakeGL":m==20?"OpenGL ES 3.20 FakeGL":m==21?"OpenGL ES 3.3 FakeGL":m==22?"OpenGL 3.2":m==23?"OpenGL ES 4.0":"OpenGL ES 3.2 FakeGL";
|
||||
}
|
||||
EGLContext EGLAPIENTRY eglGetCurrentContext(void) {return mode==5?EGL_NO_CONTEXT:(EGLContext)(uintptr_t)(mode==17&&read_done?7:5);}
|
||||
EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void) {return mode==24?EGL_NO_DISPLAY:(EGLDisplay)(uintptr_t)6;}
|
||||
EGLenum EGLAPIENTRY eglQueryAPI(void) {return mode==12?EGL_OPENGL_API:EGL_OPENGL_ES_API;}
|
||||
EGLBoolean EGLAPIENTRY eglQueryContext(EGLDisplay d,EGLContext c,EGLint a,EGLint *v) {(void)d;(void)c;(void)a;*v=(mode==6||mode==18)?2:3;return mode==13?EGL_FALSE:EGL_TRUE;}
|
||||
GLenum GL_APIENTRY glGetError(void) {
|
||||
if(mode==1 && query_count==0) {query_count++;return GL_INVALID_OPERATION;}
|
||||
if(mode==8)return GL_INVALID_OPERATION;
|
||||
if(pending){GLenum e=(GLenum)pending;pending=0;return e;}return GL_NO_ERROR;
|
||||
}
|
||||
const GLubyte *GL_APIENTRY glGetString(GLenum e) {if(mode==25)pending=GL_INVALID_ENUM;return (const GLubyte *)(e==GL_SHADING_LANGUAGE_VERSION?"OpenGL ES GLSL ES 3.20 FakeGL":version);}
|
||||
void GL_APIENTRY glGetIntegerv(GLenum e,GLint *v) {
|
||||
if(mode==6)unsupported++;
|
||||
if(mode==2 && e==GL_READ_FRAMEBUFFER_BINDING)pending=GL_INVALID_ENUM;
|
||||
switch(e) {
|
||||
case GL_MAJOR_VERSION:*v=mode==19?2:3;if(mode==26)pending=GL_INVALID_ENUM;break;
|
||||
case GL_MINOR_VERSION:*v=mode==27?1:2;break;
|
||||
#define FIELD(e,f) case e:*v=state.f;break
|
||||
case GL_READ_BUFFER:*v=mode==16?GL_NONE:GL_COLOR_ATTACHMENT0;break;
|
||||
FIELD(GL_READ_FRAMEBUFFER_BINDING,read_fbo);FIELD(GL_DRAW_FRAMEBUFFER_BINDING,draw_fbo);
|
||||
FIELD(GL_RENDERBUFFER_BINDING,rbo);FIELD(GL_PACK_ALIGNMENT,pack);FIELD(GL_PIXEL_PACK_BUFFER_BINDING,pbo);
|
||||
FIELD(GL_PACK_ROW_LENGTH,row);FIELD(GL_PACK_SKIP_ROWS,skip_rows);FIELD(GL_PACK_SKIP_PIXELS,skip_pixels);
|
||||
FIELD(GL_CURRENT_PROGRAM,program);FIELD(GL_ACTIVE_TEXTURE,active);FIELD(GL_TEXTURE_BINDING_2D,texture);
|
||||
case GL_VIEWPORT:memcpy(v,state.viewport,sizeof(state.viewport));break;
|
||||
case GL_SCISSOR_BOX:memcpy(v,state.scissor,sizeof(state.scissor));break;
|
||||
default:unsupported++;pending=GL_INVALID_ENUM;break;
|
||||
}
|
||||
}
|
||||
GLboolean GL_APIENTRY glIsEnabled(GLenum e) {(void)e;return state.scissor_on;}
|
||||
void GL_APIENTRY glBindFramebuffer(GLenum e,GLuint v) {if(e==GL_READ_FRAMEBUFFER)state.read_fbo=(GLint)v;else state.draw_fbo=(GLint)v;}
|
||||
void GL_APIENTRY glBindRenderbuffer(GLenum e,GLuint v) {(void)e;state.rbo=(GLint)v;}
|
||||
void GL_APIENTRY glBindBuffer(GLenum e,GLuint v) {(void)e;if(mode==11&&read_done&&v==15)pending=GL_INVALID_OPERATION;if(!(mode==4&&read_done))state.pbo=(GLint)v;}
|
||||
void GL_APIENTRY glPixelStorei(GLenum e,GLint v) {
|
||||
if(mode==9&&e==GL_PACK_ALIGNMENT&&v==1)pending=GL_INVALID_OPERATION;
|
||||
switch(e) {case GL_PACK_ALIGNMENT:state.pack=v;break;case GL_PACK_ROW_LENGTH:state.row=v;break;
|
||||
case GL_PACK_SKIP_ROWS:state.skip_rows=v;break;case GL_PACK_SKIP_PIXELS:state.skip_pixels=v;break;default:unsupported++;}
|
||||
}
|
||||
void GL_APIENTRY glGetFramebufferAttachmentParameteriv(GLenum t,GLenum a,GLenum e,GLint *v) {
|
||||
(void)t;(void)a;if(mode==10)pending=GL_INVALID_ENUM;*v=e==GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE?(mode==7?GL_TEXTURE:GL_RENDERBUFFER):e==GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME?44:0;
|
||||
}
|
||||
void GL_APIENTRY glGetRenderbufferParameteriv(GLenum t,GLenum e,GLint *v) {
|
||||
(void)t;*v=e==GL_RENDERBUFFER_WIDTH?2:e==GL_RENDERBUFFER_HEIGHT?3:e==GL_RENDERBUFFER_INTERNAL_FORMAT?GL_RGBA8:e==GL_RENDERBUFFER_SAMPLES?(mode==15?4:0):8;
|
||||
}
|
||||
GLenum GL_APIENTRY glCheckFramebufferStatus(GLenum e) {(void)e;return mode==28?GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:GL_FRAMEBUFFER_COMPLETE;}
|
||||
void GL_APIENTRY glReadPixels(GLint x,GLint y,GLsizei w,GLsizei h,GLenum f,GLenum t,void *p) {
|
||||
(void)x;(void)y;assert(f==GL_RGBA&&t==GL_UNSIGNED_BYTE);assert(state.pbo==0&&state.pack==1&&state.row==0);
|
||||
read_calls++;read_done=true;if(mode==3)pending=GL_INVALID_OPERATION;else memset(p,0,(size_t)w*(size_t)h*4);
|
||||
}
|
||||
int main(void) {
|
||||
struct rlimit r={0,0};assert(!setrlimit(RLIMIT_CORE,&r));assert(!prctl(PR_SET_DUMPABLE,0));
|
||||
const char *reasons[]={"READ_SUCCESS","PREEXISTING_GL_ERRORS","STATE_QUERY_ERROR","READ_GL_ERROR","RESTORE_FAILED","UNKNOWN_CONTEXT","GLES_STATE_NOT_SUPPORTED","TEXTURE_ATTACHMENT_DIMENSIONS_NOT_PROVEN","PREEXISTING_GL_ERRORS","PACK_QUERY_ERROR","ATTACHMENT_QUERY_ERROR","RESTORE_FAILED","UNKNOWN_CONTEXT","EGL_QUERY_FAILED","GLES_STATE_NOT_SUPPORTED","ATTACHMENT_UNSUPPORTED","ATTACHMENT_UNSUPPORTED","RESTORE_FAILED","READ_SUCCESS","VERSION_INCONSISTENT","GLES_STATE_NOT_SUPPORTED","GLES_STATE_NOT_SUPPORTED","GLES_STATE_NOT_SUPPORTED","GLES_STATE_NOT_SUPPORTED","UNKNOWN_CONTEXT","VERSION_QUERY_ERROR","CAPABILITY_QUERY_ERROR","VERSION_INCONSISTENT","ATTACHMENT_UNSUPPORTED"};
|
||||
for(int i=0;i<29;i++) {
|
||||
reset(i);struct g2_state before=state;struct g2_sample s=g2_read(99,2,3,0);
|
||||
assert(s.success==(i==0||i==18));assert(!strcmp(s.reason,reasons[i]));assert(!unsupported);
|
||||
if(i==0||i==3||i==7)assert(!memcmp(&state,&before,sizeof(state)));
|
||||
if(i==0||i==18){assert(s.version_queried&&s.state_saved&&s.bound&&s.read_attempted&&s.restore_attempted);assert(read_calls==1&&s.bytes==24);for(size_t n=0;n<s.bytes;n++)assert(s.pixels[n]==0);}
|
||||
if(i==1||i==2||i==5||i==6||i==7||i==8||i==9||i==10||i==12||i==13||i==14||i==15||i==16)assert(!read_calls);
|
||||
if(i==8)assert(s.pre.exhausted&&s.pre.count==G2_ERRORS);
|
||||
if(!s.state_saved)assert(!s.bound&&!s.read_attempted&&!s.restore_attempted);
|
||||
if(i>=19)assert(!read_calls);
|
||||
printf("PASS exact g2_read mode=%d reason=%s\n",i,s.reason);free(s.pixels);
|
||||
}
|
||||
GLint minor=0;char too_long[170];memset(too_long,'x',sizeof(too_long));memcpy(too_long,"OpenGL ES 3.2 ",14);too_long[169]=0;
|
||||
assert(!g2_version(NULL,&minor));assert(!g2_version(too_long,&minor));assert(!g2_version("OpenGL ES 3.2\nforged",&minor));
|
||||
assert(g2_version("OpenGL ES 3.0",&minor)&&minor==0);assert(g2_version("OpenGL ES 3.1 vendor",&minor)&&minor==1);
|
||||
size_t n=0;assert(!g2_size(INT_MAX,INT_MAX,0,&n));assert(!g2_size(2,3,G2_CAP-23,&n));assert(!g2_size(-1,3,0,&n));
|
||||
assert(g2_size(2,3,G2_CAP-24,&n)&&n==24);
|
||||
reset(0);struct g2_sample s=g2_read(99,INT_MAX,INT_MAX,0);assert(!s.success&&!read_calls);free(s.pixels);
|
||||
puts("PASS size/overflow/exact-cap; black RGBA alpha zero is valid, no sentinel gate");return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
"""Wrong native variants must fail the unchanged exact-body FakeGL tests."""
|
||||
import ctypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
import resource
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
||||
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
|
||||
KIT=Path(__file__).resolve().parent
|
||||
header=(KIT/'g2_gl.h').read_text()
|
||||
variants={
|
||||
'major-contradiction':('s.major!=3 || s.minor!=parsed_minor','s.minor!=parsed_minor'),
|
||||
'minor-contradiction':('s.major!=3 || s.minor!=parsed_minor','s.major!=3'),
|
||||
'false-read-lifecycle':('s.read_attempted=true;','s.read_attempted=false;'),
|
||||
'missing-pack-restore':('glPixelStorei(GL_PACK_ALIGNMENT,s->pack);','/* deliberately broken restore */'),
|
||||
}
|
||||
with tempfile.TemporaryDirectory(prefix='g3-mutants-',dir=os.environ['TMPDIR']) as name:
|
||||
work=Path(name)
|
||||
shutil.copyfile(KIT/'test_gl.c',work/'test_gl.c')
|
||||
for label,(old,new) in variants.items():
|
||||
assert header.count(old)==1
|
||||
(work/'g2_gl.h').write_text(header.replace(old,new))
|
||||
binary=work/label
|
||||
subprocess.run(['cc','-std=c17','-Wall','-Wextra','-Werror','-I'+str(work),str(work/'test_gl.c'),'-o',str(binary)],check=True)
|
||||
result=subprocess.run([str(binary)],capture_output=True,text=True,timeout=5)
|
||||
assert result.returncode != 0, label
|
||||
print(f'PASS wrong native variant rejected {label}: raw_exit={result.returncode}')
|
||||
86
tooling/probes/step20c-corec-v2-followup/gpu/test_native.c
Normal file
86
tooling/probes/step20c-corec-v2-followup/gpu/test_native.c
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include <GLES3/gl3.h> /* private G2 state observability */
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <pixman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <wlr/render/allocator.h>
|
||||
#include <wlr/render/drm_syncobj.h>
|
||||
#include <wlr/util/transform.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include <wlr/util/region.h>
|
||||
|
||||
#include "render/egl.h"
|
||||
#include "render/fx_renderer/fx_renderer.h"
|
||||
#include "render/fx_renderer/shaders.h"
|
||||
#include "render/pass.h"
|
||||
#include "render/tracy.h"
|
||||
#include "scenefx/render/fx_renderer/fx_offscreen_buffers.h"
|
||||
#include "scenefx/render/fx_renderer/fx_renderer.h"
|
||||
#include "scenefx/types/fx/blur_data.h"
|
||||
#include "util/matrix.h"
|
||||
|
||||
#include "g2_native.h"
|
||||
|
||||
|
||||
static struct wlr_dmabuf_attributes controlled_dmabuf;
|
||||
bool wlr_buffer_get_dmabuf(struct wlr_buffer *buffer, struct wlr_dmabuf_attributes *a) {
|
||||
(void)buffer; *a=controlled_dmabuf;return true;
|
||||
}
|
||||
#include <sys/resource.h>
|
||||
#include <sys/prctl.h>
|
||||
int main(void) {
|
||||
struct rlimit r={0,0};assert(!setrlimit(RLIMIT_CORE,&r));assert(!prctl(PR_SET_DUMPABLE,0));
|
||||
/* WHY: DCC auxiliary pitch is not an RGBA image pitch. CONTRACT:
|
||||
* exercise the exact native predicate with borrowed real descriptors. */
|
||||
FILE *plane=tmpfile();assert(plane);int fd=fileno(plane);
|
||||
controlled_dmabuf=(struct wlr_dmabuf_attributes){.width=800,.height=600,
|
||||
.format=DRM_FORMAT_ABGR8888,.modifier=UINT64_C(0x020000001046bb04),.n_planes=2,
|
||||
.stride={3584,1024},.offset={0,2293760},.fd={fd,fd}};
|
||||
struct wlr_buffer buffer={.width=800,.height=600};struct fx_framebuffer fb={.buffer=&buffer};
|
||||
assert(g2_dmabuf_supported(&fb));assert(fcntl(fd,F_GETFD)>=0);
|
||||
controlled_dmabuf.stride[1]=0;assert(!g2_dmabuf_supported(&fb));controlled_dmabuf.stride[1]=1024;
|
||||
controlled_dmabuf.modifier=UINT64_MAX;assert(!g2_dmabuf_supported(&fb));
|
||||
controlled_dmabuf.modifier=0;controlled_dmabuf.n_planes=1;assert(g2_dmabuf_supported(&fb));
|
||||
controlled_dmabuf.stride[0]=3199;assert(!g2_dmabuf_supported(&fb));controlled_dmabuf.stride[0]=3584;
|
||||
controlled_dmabuf.fd[0]=-1;assert(!g2_dmabuf_supported(&fb));
|
||||
assert(!fclose(plane));puts("PASS exact C DMA-BUF DCC/linear metadata and borrowed FD; allocation bounds NOT_PROVEN");
|
||||
struct fx_gles_render_pass passes[9]={0};
|
||||
struct wlr_output h1={.name="HEADLESS-1",.scale=1,.transform=WL_OUTPUT_TRANSFORM_NORMAL};
|
||||
struct wlr_output h2={.name="HEADLESS-2",.scale=1,.transform=WL_OUTPUT_TRANSFORM_NORMAL};
|
||||
for(unsigned i=0;i<9;i++) {
|
||||
g2_track_output(&passes[i],i%2?&h2:&h1);
|
||||
assert(g2_intended_output(&passes[i])==(i%2==0));
|
||||
g2_forget_output(&passes[i]);assert(!g2_intended_output(&passes[i]));
|
||||
}
|
||||
assert(!g2.stopped);
|
||||
g2_track_output(&passes[0],&h1);assert(g2_intended_output(&passes[0]));
|
||||
g2_track_output(&passes[0],&h2);assert(!g2_intended_output(&passes[0]));
|
||||
g2_forget_output(&passes[0]);g2_track_output(&passes[0],&h1);assert(g2_intended_output(&passes[0]));g2_forget_output(&passes[0]);
|
||||
for(unsigned i=0;i<9;i++)g2_track_output(&passes[i],i%2?&h2:&h1);
|
||||
assert(g2.stopped && !g2_intended_output(&passes[8]));
|
||||
for(unsigned i=0;i<9;i++) {g2_forget_output(&passes[i]);}
|
||||
g2.stopped=false;
|
||||
puts("PASS nine sequential passes/output forget, pointer reuse H1/H2 and nine-simultaneous fail-closed overflow");
|
||||
struct g2_sample s={.width=800,.height=600,.bytes=800*600*4};s.pixels=calloc(1,s.bytes);assert(s.pixels);
|
||||
for(int y=0;y<600;y++)for(int x=0;x<800;x++) {
|
||||
const unsigned char a[2][3]={{245,245,245},{15,25,45}};
|
||||
memcpy(s.pixels+((size_t)y*800+(size_t)x)*4,a[(x/8)%2],3);
|
||||
}
|
||||
assert(g2_expected_source(&s,1));assert(g2_expected_source(&s,3));assert(!g2_expected_source(&s,2));
|
||||
s.pixels[4]^=1;assert(!g2_expected_source(&s,1));s.pixels[4]^=1;
|
||||
for(int y=0;y<600;y++)for(int x=0;x<800;x++) {
|
||||
const unsigned char b[2][3]={{32,192,224},{176,32,64}};
|
||||
memcpy(s.pixels+((size_t)y*800+(size_t)x)*4,b[(x/8)%2],3);
|
||||
}
|
||||
assert(g2_expected_source(&s,2));assert(!g2_expected_source(&s,3));free(s.pixels);
|
||||
assert(g2_crc((const unsigned char *)"123456789",9)==UINT32_C(0xcbf43926));
|
||||
FILE *f=tmpfile();assert(f && g2_owned(fileno(f),0600));assert(g2_write(fileno(f),"frame",5));assert(!fseek(f,0,SEEK_SET));char b[6]={0};assert(fread(b,1,5,f)==5 && !strcmp(b,"frame"));assert(!fclose(f));
|
||||
assert(!setenv("MOSAIC_G3_DIR","/tmp",1));assert(!setenv("MOSAIC_G3_NONCE","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",1));
|
||||
assert(!g2_open() && g2.stopped && g2.dir==-1);
|
||||
puts("PASS exact native source epoch RGB admission A/B/A-return and negative substitution");
|
||||
puts("PASS exact native CRC framing/writer/owned FD cleanup and wrong realpath refusal");
|
||||
return 0;
|
||||
}
|
||||
110
tooling/probes/step20c-corec-v2-followup/gpu/test_protocol.py
Normal file
110
tooling/probes/step20c-corec-v2-followup/gpu/test_protocol.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
"""Controlled synthetic framing and scenario tests; NO GPU/native evidence."""
|
||||
import resource,ctypes
|
||||
resource.setrlimit(resource.RLIMIT_CORE,(0,0))
|
||||
assert ctypes.CDLL(None).prctl(4,0,0,0,0)==0
|
||||
|
||||
import json,os,shutil,tempfile,zlib,copy
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from PIL import Image
|
||||
import protocol,scenario
|
||||
from validate_source import Rejected
|
||||
NONCE='a'*64
|
||||
BASE=Path(os.environ['TMPDIR'])
|
||||
def write_epoch(d,e,total):
|
||||
raw=bytes(([32,192,224,255] if e==2 else [245,245,245,255])*6);total+=len(raw)*10
|
||||
rows=[dict(kind='begin',schema='G3-followup-v1',epoch=e,nonce=NONCE,label=('A','B','A-return')[e-1],output='HEADLESS-1',intrusive_error_queue=True,error_queue_restorable=False)]
|
||||
for p,stage in enumerate(protocol.STAGES):
|
||||
if 1<=p<=6:rows.append(dict(kind='uniform',point=p,type=0x8b50,program=5,location=3,shader_default_float='mediump',varying='mediump',sampler=[9729,9729,33071,33071],precision_capability=[15,15,10],native_texture_dimensions=[2,3],actual_bits=[1,2]))
|
||||
filename=f'epoch-{e}-point-{p}.rgba';protocol.exclusive(d/filename,raw)
|
||||
rows.append(dict(kind='sample',epoch=e,point=p,stage=stage,pass_sequence=e,context='5',result='READ_SUCCESS',read_success=True,published=True,before={'pack':8},after={'pack':8},pre_errors={'exhausted':False,'codes':[]},query_errors={'exhausted':False,'codes':[]},read_errors={'exhausted':False,'codes':[]},restore_errors={'exhausted':False,'codes':[]},client_version=2,gl_major=3,gl_minor=2,glsl_hex=b'OpenGL ES GLSL ES 3.20'.hex(),version_queried=True,state_saved=True,bound=True,read_attempted=True,restore_attempted=True,version_errors={'exhausted':False,'codes':[]},capability_errors={'exhausted':False,'codes':[]},state_errors={'exhausted':False,'codes':[]},gl_version_hex=b'OpenGL ES 3.2 ControlledFake'.hex(),attachment=[0x8d41,44,0,2,3,0x8058],channels=[8,8,8,8],status=0x8cd5,read_buffer=0x8ce0,samples=0,native={'dimensions':[2,3],'fbo_rbo_tex':[11,44,22],'fx':'1','wlr':'2','image':'3'},dmabuf={'borrowed':True,'dimensions':[2,3],'format':int.from_bytes(b'AB24','little'),'modifier':'0000000000000000','planes':[{'stride':8,'offset':0,'fd':4,'fstat_ok':True,'dev':1,'ino':2}]},bytes=len(raw),file=filename,crc32=f'{zlib.crc32(raw):08x}',fresh_rectangles=[[0,0,2,3]]))
|
||||
rows.append(dict(kind='end',epoch=e,points=10,accepted=True,raw_total=total))
|
||||
protocol.exclusive(d/f'epoch-{e}.jsonl',b''.join((json.dumps(row,separators=(',',':'))+'\n').encode() for row in rows));(None if (d/f'epoch-{e}.consumed').exists() else protocol.exclusive(d/f'epoch-{e}.consumed',f'{NONCE} {e}\n'.encode()))
|
||||
return rows,total
|
||||
|
||||
def framing(d):
|
||||
rows,total=write_epoch(d,1,0);assert protocol.epoch(d,1,NONCE)['raw_total']==240
|
||||
tests=[('early-return',lambda x:x[1].update(read_attempted=False)),('fake-version',lambda x:x[1].update(gl_minor=1)),('unqualified-es',lambda x:x[1].update(gl_version_hex=b'OpenGL ES 3.3'.hex())),('capability-error',lambda x:x[1]['capability_errors']['codes'].append(0x500)),('read-error',lambda x:x[1]['read_errors']['codes'].append(0x502)),('restore',lambda x:x[1]['after'].update(pack=1)),('context',lambda x:x[1].update(context='0')),('path',lambda x:x[1].update(file='../escape')),('budget',lambda x:x[1].update(bytes=protocol.CAP+1)),('incomplete',lambda x:x.pop()),('stage',lambda x:x[1].update(stage='presented-buffer'))]
|
||||
p=d/'epoch-1.jsonl'
|
||||
for label,mutate in tests:
|
||||
changed=copy.deepcopy(rows);mutate(changed);p.write_text(''.join(json.dumps(x)+'\n' for x in changed));p.chmod(0o600)
|
||||
try:protocol.epoch(d,1,NONCE)
|
||||
except (Rejected,KeyError,AssertionError):print('PASS controlled framing rejection',label)
|
||||
else:raise AssertionError(label)
|
||||
p.write_text(''.join(json.dumps(x)+'\n' for x in rows));p.chmod(0o600)
|
||||
q=d/'epoch-1-point-0.rgba';q.write_bytes(b'bad')
|
||||
try:protocol.epoch(d,1,NONCE)
|
||||
except Rejected:print('PASS raw truncation/CRC rejection')
|
||||
else:raise AssertionError('truncation')
|
||||
q.unlink();q.symlink_to('/dev/null')
|
||||
try:protocol.bounded(q,10)
|
||||
except Rejected:print('PASS symlink refusal')
|
||||
else:raise AssertionError('symlink')
|
||||
try:protocol.exclusive(p,b'overwrite')
|
||||
except FileExistsError:print('PASS exclusive publication refusal')
|
||||
else:raise AssertionError('exclusive')
|
||||
assert scenario.exact_rgb(Image.new('RGB',(2,3)),Image.new('RGB',(2,3)))['equal']
|
||||
a=Image.new('RGB',(2,3));b=a.copy();b.putpixel((1,2),(1,0,0));assert not scenario.exact_rgb(a,b)['equal']
|
||||
print('PASS whole H1/H2 all-pixel zero RGB tolerance')
|
||||
|
||||
def token_test(d):
|
||||
with patch.object(protocol,'gate',lambda *args:None):
|
||||
protocol.arm(d,1,NONCE,d/'gate',d/'binary')
|
||||
assert protocol.bounded(d/'epoch-1.arm',80)==f'{NONCE} 1\n'.encode()
|
||||
for e in (1,2):
|
||||
try:protocol.arm(d,e,NONCE,d/'gate',d/'binary')
|
||||
except (Rejected,FileNotFoundError):pass
|
||||
else:raise AssertionError('retry or B before complete A')
|
||||
os.link(d/'epoch-1.arm',d/'epoch-1.consumed');(d/'epoch-1.arm').unlink()
|
||||
write_epoch(d,1,0)
|
||||
protocol.arm(d,2,NONCE,d/'gate',d/'binary')
|
||||
try:protocol.arm(d,3,NONCE,d/'gate',d/'binary')
|
||||
except (Rejected,FileNotFoundError):pass
|
||||
else:raise AssertionError('return before complete B')
|
||||
print('PASS exact arm token nonce/exclusive/replay and first A completion gate, controlled directory')
|
||||
|
||||
class Session:
|
||||
def __init__(self,d,fail=False):
|
||||
self.d=d;self.fail=fail;self.commands=[];self.captures=0;self.total=0;self.env={};self.spec={'id':'GPU-G3','binary':'gpu'};self.auth={'binary':str(d/'fake-binary'),'manifest_sha256':'controlled'};self.proc={};self.data={};self.saved={}
|
||||
def start(self):self.data['started']=True
|
||||
def cmd(self,*a,**kw):pass
|
||||
def spawn(self,name,argv):
|
||||
parent=self
|
||||
class Input:
|
||||
def write(self,text):
|
||||
parent.commands.append(text.strip());e=len(parent.commands)
|
||||
if parent.fail:protocol.exclusive(parent.d/f'epoch-{e}.jsonl',b'{"kind":"rejected"}\n')
|
||||
else:_,parent.total=write_epoch(parent.d,e,parent.total)
|
||||
def flush(self):pass
|
||||
self.proc[name]=type('Process',(),{'stdin':Input()})()
|
||||
def wait(self,fn):assert fn()
|
||||
def find(self,name):return {'id':1}
|
||||
def log(self,name):return 'BACKGROUND_CONFIGURED '+' '.join('G3_SOURCE_BYTES command='+x+' ' for x in self.commands)
|
||||
def debit(self,*a):pass
|
||||
def record(self,*a,**kw):pass
|
||||
def capture(self,*a):self.captures+=1;return Image.new('RGB',(2,3))
|
||||
def pair_capture(self,*a):self.captures+=2;return [Image.new('RGB',(2,3)),Image.new('RGB',(2,3))]
|
||||
def snapshot(self,*a):pass
|
||||
def save(self,name,result):self.saved[name]=result
|
||||
def close_client(self,*a):pass
|
||||
|
||||
def scenario_test(d,fail):
|
||||
s=Session(d,fail);binary=d/'fake-binary';binary.write_bytes(b'controlled')
|
||||
binding=dict(G3_ledger_reserved=True,binary=str(binary),source_manifest_sha256='controlled',gate_file=str(d/'gate'),client=str(binary),client_sha256=__import__('hashlib').sha256(binary.read_bytes()).hexdigest(),nonce=NONCE)
|
||||
armed=[]
|
||||
with patch.object(scenario,'ROOT',d),patch.object(scenario,'gate',lambda *args:None),patch.object(scenario,'arm',lambda root,e,*args:armed.append(e)),patch.object(scenario.time,'sleep',lambda seconds:None):
|
||||
if fail:
|
||||
try:scenario.run(s,binding)
|
||||
except RuntimeError:pass
|
||||
else:raise AssertionError('rejected A continued')
|
||||
assert s.commands==['G3-A'] and armed==[1] and s.captures==2
|
||||
print('PASS rejected first A stops B/A-return: exact executable adapter, fake Session only')
|
||||
else:
|
||||
r=scenario.run(s,binding);assert s.commands==['G3-A','G3-B','G3-A-return'] and armed==[1,2,3] and s.captures==8 and r['whole_H1_A_Areturn']['equal']
|
||||
print('PASS one-shot A/B/A adapter flow, eight captures with whole H1/H2 and H2 baseline, controlled framing')
|
||||
if __name__=='__main__':
|
||||
with tempfile.TemporaryDirectory(prefix='protocol-controlled-',dir=BASE) as temp:
|
||||
p=Path(temp);p.chmod(0o700)
|
||||
for name in ('framing','accepted','rejected','token'):(p/name).mkdir(mode=0o700)
|
||||
with patch.object(protocol,'FRAME',(2,3)):
|
||||
framing(p/'framing');token_test(p/'token');scenario_test(p/'accepted',False);scenario_test(p/'rejected',True)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
"""New G2 offline controls. Historical G1 stays HISTORICAL_REPORTED_ONLY."""
|
||||
import resource,ctypes
|
||||
resource.setrlimit(resource.RLIMIT_CORE,(0,0))
|
||||
assert ctypes.CDLL(None).prctl(4,0,0,0,0)==0
|
||||
|
||||
import hashlib,json,struct
|
||||
from pathlib import Path
|
||||
HEADER_SHA='992f33804acda60b0b46f1a272942c3592484ea3217d469239f1cb12249de2eb'
|
||||
BPP={int.from_bytes(s,'little'):4 for s in (b'AB24',b'XB24',b'AR24',b'XR24')}
|
||||
AMD='020000001046bb04'
|
||||
class Rejected(ValueError):pass
|
||||
def need(x,msg):
|
||||
if not x:raise Rejected(msg)
|
||||
def metadata(o):
|
||||
need(type(o['width']) is int and type(o['height']) is int and 0<o['width']<=2**31-1 and 0<o['height']<=2**31-1,'dimensions')
|
||||
need(o['format'] in BPP,'unknown FOURCC')
|
||||
need(o['modifier'] in ('0000000000000000',AMD),'unknown modifier')
|
||||
planes=o['planes'];need(len(planes)==(2 if o['modifier']==AMD else 1),'plane count')
|
||||
for p in planes:
|
||||
need(all(type(p[k]) is int and 0<=p[k]<=2**32-1 for k in ('stride','offset')) and p['stride']>0,'plane uint32')
|
||||
need(p['offset']+p['stride']<=2**64-1,'overflow')
|
||||
p=planes[0];need(p['stride']>=o['width']*BPP[o['format']],'image stride')
|
||||
end=p['offset']+p['stride']*(o['height']-1)+o['width']*BPP[o['format']]
|
||||
need(end<=2**64-1,'image overflow')
|
||||
# No fabricated DCC extent or same-allocation restriction. fstat sizes are
|
||||
# recorded as descriptor metadata, not interpreted as allocation length.
|
||||
return {'image_stride':'PASS','auxiliary_stride':'NOT_AN_IMAGE_STRIDE' if len(planes)>1 else 'N/A','allocation_bounds':'NOT_PROVEN','fd_ownership':'BORROWED' if o.get('borrowed') is True else 'NOT_PROVEN','decode_support':'NOT_PROVEN'}
|
||||
def f32(x):return struct.unpack('<f',struct.pack('<f',x))[0]
|
||||
def bits(x):return struct.unpack('<I',struct.pack('<f',x))[0]
|
||||
def halfpixel(width,height,down,observed):
|
||||
need(width>0 and height>0 and len(observed)==2 and all(type(x)is int and 0<=x<=2**32-1 for x in observed),'uniform framing')
|
||||
divisor=f32(2.0)
|
||||
ideal=[f32(f32(.5)/f32(f32(n)/divisor if down else f32(n)*divisor)) for n in (width,height)]
|
||||
return {'observed_bits':observed,'cpu_float32_ideal_bits':[bits(x) for x in ideal],'comparison_gate':'NONE','effective_arithmetic':'NOT_PROVEN','capability_is_per_operation_precision':False}
|
||||
def test():
|
||||
from copy import deepcopy
|
||||
need(hashlib.sha256(Path('/usr/include/libdrm/drm_fourcc.h').read_bytes()).hexdigest()==HEADER_SHA,'header pin')
|
||||
o={'width':800,'height':600,'format':int.from_bytes(b'AB24','little'),'modifier':AMD,'planes':[{'stride':3584,'offset':0},{'stride':1024,'offset':2293760}],'borrowed':True}
|
||||
r=metadata(o);need(r['auxiliary_stride']=='NOT_AN_IMAGE_STRIDE','AMD aux')
|
||||
# Controlled fixtures, never actual allocation evidence or historical PASS.
|
||||
for label,fn in [('unknown-format',lambda x:x.update(format=1)),('short-image',lambda x:x['planes'][0].update(stride=3199)),('plane-count',lambda x:x['planes'].pop()),('unknown-modifier',lambda x:x.update(modifier='ffffffffffffffff')),('negative-offset',lambda x:x['planes'][1].update(offset=-1)),('zero-aux',lambda x:x['planes'][1].update(stride=0))]:
|
||||
x=deepcopy(o);fn(x)
|
||||
try:metadata(x)
|
||||
except Rejected:print('PASS controlled rejection',label)
|
||||
else:raise AssertionError(label)
|
||||
u=halfpixel(800,600,True,[bits(1/800),bits(1/600)])
|
||||
v=halfpixel(800,600,True,[bits(.0012493133544921875),bits(.0016660690307617188)])
|
||||
need(u['cpu_float32_ideal_bits']==v['cpu_float32_ideal_bits'] and u['observed_bits']!=v['observed_bits'] and v['comparison_gate']=='NONE','no mediump equality or fit')
|
||||
try:halfpixel(800,600,False,[2**32,0])
|
||||
except Rejected:pass
|
||||
else:raise AssertionError('uniform framing')
|
||||
print('PASS controlled AMD/linear policy and separate halfpixel bits; HISTORICAL_REPORTED_ONLY')
|
||||
if __name__=='__main__':test()
|
||||
172
tooling/probes/step20c-corec-v2-followup/graphics/README.md
Normal file
172
tooling/probes/step20c-corec-v2-followup/graphics/README.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# Focused native closure graphics fixture
|
||||
|
||||
EXPERIMENTAL SOURCE ONLY. This is a new derivation of the source-only graphics
|
||||
fixture at Git `d46f854b3026e911659528fe94792aa13837a1d6`, not a modification or
|
||||
re-admission of P1/C7/C8/O1/G2. No production code, package or UX contract changes.
|
||||
The closed historical LAB is read-only. All native results remain NOT_RUN until
|
||||
independent final-byte review, source publication and individual admission.
|
||||
|
||||
The eight nonfungible IDs are FS-P(12 captures), FS-C(16), DY-C(16), OM-ROOT(10),
|
||||
OM-NESTED(10), GPU-G3(12), OM-EXTRA(12), GPU-CTRL(12): sum100, global110, at most
|
||||
8 runs /150s. No reset/refund/retry API. Graphics source leaves GPU-CTRL and OM-EXTRA NOT_ADMITTED. GPU-G3 is
|
||||
NOT_ADMITTED by default and has a distinct executable branch requiring B4 and
|
||||
independent exact-byte/source/build receipts; no gate is inferred from a plan.
|
||||
|
||||
WHY: the former fullscreen insertion placed peer outside owner group; migration
|
||||
ignored focus reveal; root OOM closure required nonexistent B. CONTRACT: exact
|
||||
owner+peer direct children and measured400/400, current-data clamp then focus
|
||||
reveal, scenario-created child-first closure. INVARIANT: whole-field expectations
|
||||
are calculated before acquisition, every action is debited first, raw failure
|
||||
and mechanical rescue remain visible and cannot establish scientific cleanup.
|
||||
|
||||
`fullscreen.prepare_group` uses an existing filler (container_split does not wrap
|
||||
a singleton H/V child), wraps owner, marks the real group, creates peer on ws3,
|
||||
then explicitly moves it to that mark. The source route is pinned
|
||||
`commands/split.c:do_split`, `tree/container.c:container_split`,
|
||||
`commands/move.c:container_move_to_container` (nonleaf destination adds child).
|
||||
No empirical runtime adjustment or retry exists. A failed topology/crossing is
|
||||
NOT_EXERCISED. Both binaries use the same fullscreen-client/protocol XML, explicit
|
||||
positioner adjustment0 and actual configured rectangles. A/B input, explicit
|
||||
close B with A surviving, fullscreen exit/splith return and final H2 are sampled.
|
||||
The complete H2 field composes A then B before each capture; A-only is predicted
|
||||
after B closes. FS-P uses12 captures; FS-C uses the same12 within16.
|
||||
|
||||
IPC `fullscreen_mode` is explicitly **pending** (`ipc-json.c` serializes
|
||||
`c->pending.fullscreen_mode`); workspace marker1 is not fullscreen proof. A
|
||||
post-command toplevel configure/ACK/MAP plus measured group geometry, paints and
|
||||
fresh input corroborates sampled applied fullscreen. Direct observation of the
|
||||
ancestor's `current.fullscreen_mode` is NOT_OBSERVABLE with these unchanged ELFs;
|
||||
atomic current-state generation is NOT_PROVEN. The validator rejects a
|
||||
pending-only or mismatched-owner/workspace certificate. Do not report a native
|
||||
current-bit measurement that these binaries do not expose.
|
||||
|
||||
`cases.dynamic_geometry` derives row extent and uniform gap from current actual
|
||||
child rectangles and pinned default640 sizing, validates row identity/order and
|
||||
focused owner, then clamps old scroll to current bounds and performs native
|
||||
left/right reveal for migration. The audited route is
|
||||
`commands/move.c:cmd_move_workspace` → arrange old/new output → seat_set_focus →
|
||||
`input/seat.c` row focus/reveal → `tree/arrange.c:apply_mosaic_layout` and
|
||||
`tree/mosaic.c:mosaic_row_clamp_scroll`. Neither800 nor1120 is a universal expected
|
||||
camera. Every resize/migration invalidates previous input/camera/origin; new
|
||||
painted interior and pointer-local delivery are required, or actual dismissal
|
||||
records input NOT_EXERCISED. Opening6 + four mutation pairs8 + final pair2=16.
|
||||
|
||||
OOM root closes A→TOP, nested failure closes B→A→TOP after mapped rootA; no_memory
|
||||
requires actual wl_display.error code1 and client ENOMEM/EPROTO, separately from
|
||||
requested error0 close. Every created object gets one wire destroy in reverse
|
||||
hierarchy; release accounting and requested close are required. Failed popup
|
||||
must never MAP. Native role/PID/nonce/counters and the same-XDG NULL return bind
|
||||
the failure. The diagnostic's `children` is **existing parent children**, not
|
||||
children owned by the failed tree (`wl_list_length(&parent->children)`); retain
|
||||
its raw value. Initialized/listeners/published0 plus the audited prepublication
|
||||
NULL path establish the scoped failed node, without inventing a new child-count
|
||||
counter or demanding nested parent_children0. RootA native success, real wire
|
||||
grab/configure/ACK/SHM/map and clipped pre-injection paint qualify nested start.
|
||||
|
||||
Availability identity (actual /proc exe hash, BuildID, mappings, exact wlroots,
|
||||
static SceneFX), tree/outputs and fresh exclusive neighbor press/release are
|
||||
saved **before cleanup**. A surviving painted rootA receives fresh right-button
|
||||
input before any neighbor click, checked against its actual configure, updated
|
||||
owner origin, exclusive wire pair and pointer-local coordinates. Actual root
|
||||
dismissal and ENOMEM disconnect remain distinct NOT_EXERCISED input outcomes.
|
||||
H2 pre/post/final must equal the whole reference. Failed-popup color is absent
|
||||
on both outputs before/post injection; final owner close rejects both A and B
|
||||
on both outputs. Surviving A is allowed after nested B failure.
|
||||
Fail-closed and availability are separate observations; full damage, GPU
|
||||
retirement, presented-buffer identity and atomic generation remain NOT_PROVEN.
|
||||
OM-NESTED needs qualified root outcome and scientific cleanup, or a new explicit
|
||||
independent dependency review with rationale and no common native fault.
|
||||
|
||||
Preparation never executes clients or compositors. All generated protocols,
|
||||
ELFs, pycache, caches, tests and receipts belong to a fresh canonical owned0700
|
||||
LAB directly below /var/tmp. Run every command using an allowlist environment
|
||||
with TMPDIR/TMP/TEMP=LAB/scratch, XDG_CACHE_HOME=LAB/cache, XDG_DATA_HOME=LAB/data,
|
||||
PYTHONPYCACHEPREFIX=LAB/pycache, CCACHE_DIR=LAB/cache/ccache, SMX_LAB=LAB. Source
|
||||
may reside in a fresh Git extraction; no historical absolute path is required.
|
||||
|
||||
```sh
|
||||
python graphics/prepare.py --lab "$LAB"
|
||||
python graphics/validate_offline.py --lab "$LAB"
|
||||
python graphics/freeze.py --lab "$LAB" --author author-identity
|
||||
```
|
||||
|
||||
The optional `validate_offline.py --historical-c7 <read-only-run-directory>`
|
||||
replays preserved resized-back and migrated-H2 snapshots through the same live
|
||||
validator and rejects a stale-camera mutation. It does not upgrade old C7.
|
||||
The manifest goes to LAB/followup-work/source-manifest.json and contains only
|
||||
relative hashes and modes for the **whole followup source kit**. Review/test/publication
|
||||
and runtime identities stay outside source. Review is never generated by these
|
||||
commands. `freeze` refuses replacement; changed source requires a new explicitly
|
||||
reviewed freeze path/version using `--output "$LAB/followup-work/source-v2.json"`,
|
||||
not an automatic rewrite at launch.
|
||||
|
||||
Independent review JSON binds ticket, manifest_sha256, independent:true,
|
||||
reviewer different from manifest.author, decision:ACCEPTED, runs[ID] with
|
||||
ACCEPTED/client_build_review and client_build_receipt path/SHA. `gates[NAME]`
|
||||
binds decision:ACCEPTED, manifest_sha256 and receipt path/SHA: the receipt requires
|
||||
status PASS, tests_run>0, failures0 and source_digest of the manifest files.
|
||||
Required names: FULLSCREEN_COMPARABILITY_OFFLINE, MIGRATION_REFOCUS_OFFLINE,
|
||||
OOM_ROOT_NESTED_ORACLE_OFFLINE, separately by scenario. Runtime identity file
|
||||
is external via review.runtime_manifest / runtime_manifest_sha256, with ticket
|
||||
and identities[pristine/core/oom]: binary path/SHA/BuildID, wlroots path/SHA,
|
||||
build_review and source_qualification ACCEPTED; pristine also binds its shared
|
||||
SceneFX path/SHA. OOM runs additionally require oom_source_review ACCEPTED,
|
||||
variant_identity COREC_V2_NATIVE_OOM_DIAGNOSTIC and source_manifest_sha256 in
|
||||
runs[ID]. Clients are newly built in LAB/followup-work/client-build. Source,
|
||||
protocol and ELF hashes are reconciled before admission and actions.
|
||||
|
||||
Publication JSON binds ticket, decision VERIFIED, source_commit, manifest SHA,
|
||||
source_digest, origin_sha/github_sha equal to that commit, offline_test_only:false
|
||||
and direct remote_observations origin/github {argv:[git,ls-remote,remote,
|
||||
refs/heads/main],exit:0,stdout:<commit + ref>}. The real preflight also hashes
|
||||
`git show <commit>:<source-path>` bytes for every source entry before reservation.
|
||||
The same gate reads `git ls-tree -rz --full-tree <commit> -- <kit-prefix>`
|
||||
without a shell, parses NUL-delimited paths and requires the exact inventory,
|
||||
blob type and matching 100644/100755 modes. Local freeze/refreeze rejects
|
||||
nonportable modes: source files use 0644 (or explicitly reviewed 0755); receipt
|
||||
0600 permissions are never represented as portable source permissions.
|
||||
Matching remote strings alone do not bind a source kit to Git. Ledger init is
|
||||
an explicit parent operation `Ledger(LAB).initialize()` after provenance gate;
|
||||
no child/test creates a real ledger. Example single-case CLI after publication:
|
||||
|
||||
```sh
|
||||
python graphics/run.py FS-P --lab "$LAB" \
|
||||
--manifest "$LAB/followup-work/source-manifest.json" \
|
||||
--review "$LAB/reviews/independent-graphics.json" \
|
||||
--publication "$LAB/reviews/pre-run-publication.json"
|
||||
```
|
||||
|
||||
Ledger state/lock enforce nofollow, regular file, owner, mode0600, single link,
|
||||
stable flock inode and atomic fsync replacement. Offline tests use only
|
||||
OFFLINE_TEST_ONLY ledgers and can never launch Session. Missing, stale or
|
||||
superficial input rejects before debit. Logs/actions are bounded; an independent
|
||||
watchdog TERM143/KILL≤146 seconds owns only registered process groups. Runtime
|
||||
creation probes quota with a write in a new /tmp/smx-*0700 directory and falls
|
||||
back on EDQUOT/ENOSPC to /var/tmp/smx-* if UNIX path fits. Runtime is socket-only.
|
||||
All IPC uses explicit private swaymsg -s. Daily display/socket/loader variables
|
||||
are excluded by allowlist. Prlimit sets core0 for every owned child; fixture
|
||||
clients and Python explicitly set PR_SET_DUMPABLE0. Linux may reset dumpable on
|
||||
exec of the unchanged compositor ELF: this is a stated limitation, never an
|
||||
asserted native dumpable0 measurement. No system coredump cleanup is attempted.
|
||||
|
||||
The GPU-G3 adapter imports only the frozen sibling gpu/ modules and validates
|
||||
every module origin and the exact acquisition ROOT. Its Session identity stays
|
||||
GPU-G3/gpu. The adapter redirects MOSAIC_G3_DIR/NONCE and WLR_RENDERER exclusively
|
||||
to compositor_environment; ordinary helpers and clients receive no hook vars.
|
||||
Each epoch debits mutation+hook before arm. Rejected/incomplete A stops before
|
||||
B and A-return. Native protocol validation and GL queries are owned by gpu/,
|
||||
not reimplemented here. GPU background closure checks its real layer-shell role;
|
||||
reference/glass check XDG, all with actual error0 exit/SHM release/wire destroys.
|
||||
|
||||
Additional GPU review fields: gates.B4 with the ordinary test semantic receipt;
|
||||
runs.GPU-G3 gpu_source_review/gl_observability_preflight/build_review ACCEPTED,
|
||||
each with *_receipt and *_receipt_sha256 whose content is independent:true,
|
||||
decision ACCEPTED and exact manifest_sha256; native_gl_status NOT_RUN;
|
||||
gpu_source_manifest(+SHA) schema G3-relative-source-freeze-v1; gpu_client_build_receipt
|
||||
(+SHA) {status:PASS,source_sha256:<gpu/clients-g2.c>,client_sha256:<new ELF>};
|
||||
gate_file(+SHA); reviewed_scene_sha256; binding {binary,client,client_sha256,
|
||||
source_manifest_sha256,gate_file,reviewed_scene_sha256}. The native gate separately
|
||||
binds source_review ACCEPTED, gl_observability PASS, runtime_authorized true,
|
||||
diagnostic GPU-G3, binary_sha256 and exact acquisition_root. Every receipt must
|
||||
be canonical owned0600 below the new LAB. Runtime identity gpu binary must differ
|
||||
from every other identity. These are input contracts, never auto-written accepts.
|
||||
175
tooling/probes/step20c-corec-v2-followup/graphics/cases.py
Normal file
175
tooling/probes/step20c-corec-v2-followup/graphics/cases.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
"""Small campaign scenarios over the same reviewed Session; no historical runner copy."""
|
||||
import re,time
|
||||
from PIL import ImageChops
|
||||
from engine import *
|
||||
from geometry import *
|
||||
|
||||
def init_reference(s,prefix_fillers=1):
|
||||
s.cmd('workspace 2');s.launch_client('reference','static-reference');rid=s.find('reference')['id'];s.cmd(f'[con_id={rid}] border none')
|
||||
s.cmd('workspace 1')
|
||||
for i in range(prefix_fillers):s.launch_client('filler'+str(i),'static-reference')
|
||||
s.cmd('layout mosaic' if s.spec['mode']=='mosaic' else 'layout splith')
|
||||
return rid
|
||||
|
||||
def stable_reference(s):
|
||||
s.inject('move',[s.outputs[0]['logical'][0]+10,s.outputs[0]['logical'][1]+10]);time.sleep(.15)
|
||||
a=s.capture('H2-reference1','HEADLESS-2');time.sleep(.6);b=s.capture('H2-reference2','HEADLESS-2');exact(a,b)
|
||||
need(all(v==(48,48,56) for v in a.get_flattened_data()),'static uniform H2');s.save('reference-stability',dict(status='PASS',exact=exact(a,b)));return a
|
||||
|
||||
def root_state(s,state,origin,window_start,expect_hidden=False,previous=None,expected_tree=None):
|
||||
# WHY/CONTRACT/INVARIANT: no PI/native scene identity exists in CORE.
|
||||
# Pixel/input evidence qualifies sampled clipping only, never generation.
|
||||
return dict(status='NOT OBSERVABLE',native_tree_identity='NOT OBSERVABLE',
|
||||
applied_generation='NOT OBSERVABLE',owner_id=state['owner']['id'],
|
||||
workspace_id=state['workspace']['id'],window=[window_start,time.monotonic_ns()])
|
||||
|
||||
def open_chain(s,tail_fillers=0):
|
||||
|
||||
s.cmd('workspace 1');s.cmd('layout mosaic' if s.spec['mode']=='mosaic' else 'layout splith');s.expected_layout='mosaic' if s.spec['mode']=='mosaic' else 'splith'
|
||||
pre=s.snapshot('before-chain-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)))
|
||||
validators.selected_workspace(pre,'1','HEADLESS-1',s.expected_layout)
|
||||
s.debit('chain');s.launch_client('owner',s.spec['client'],s.spec['placement'])
|
||||
s.owner('owner') # Reject wrong map before any click; never move mapped popup chains.
|
||||
for i in range(tail_fillers):s.launch_client('tail'+str(i),'static-reference')
|
||||
s.cmd(f'[con_id={s.find("owner")["id"]}] focus');state=s.calibrate('owner');baseline=s.reference if hasattr(s,'reference') else stable_reference(s);s.reference=baseline
|
||||
im=s.capture('owner-top-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)),'HEADLESS-1');rect=[*state['origin'],state['owner']['window_rect']['width'],state['owner']['window_rect']['height']]
|
||||
xy=solid_point(im,COLORS['TOPLEVEL'],s.outputs[0],s.frames['HEADLESS-1'],rect)
|
||||
need(contains([*state['origin'],420,575 if s.spec['axis']==1 else 320],xy),'opening native bounds')
|
||||
ta,ba=s.click('owner',xy,'left',True);s.wait(lambda:'POPUP_A MAP ' in s.log('owner'));time.sleep(.2)
|
||||
aim=s.capture('popup-A-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)),'HEADLESS-1');prov=Provenance(state['owner']['id'],state['workspace']['id'],'NOT OBSERVABLE',state['evidence'])
|
||||
xyA=solid_point(aim,COLORS['POPUP_A'],s.outputs[0],s.frames['HEADLESS-1'])
|
||||
tb,bb=s.click('owner',xyA,'left',True);s.wait(lambda:'POPUP_B MAP ' in s.log('owner'));time.sleep(.2)
|
||||
window=time.monotonic_ns();time.sleep(.15);p=validators.grab(s.log('owner'));g=geometry(s.log('owner'),state['origin'],prov,s.spec['axis'],s.spec['boundary'])
|
||||
s.delivery('owner',rect,xy,ta,ba,'TOPLEVEL',p['grabs'][0]['serial']);s.delivery('owner',g['A'],xyA,tb,bb,'POPUP_A',p['grabs'][1]['serial'])
|
||||
[validators.crosses(g[k],s.spec['axis'],s.spec['boundary']) for k in ('A','B')]
|
||||
left,right=s.pair_capture('mapped-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)))
|
||||
for name in ('POPUP_A','POPUP_B'):
|
||||
need(count(left,COLORS[name])>0,'permitted popup pixels missing')
|
||||
if s.spec['mode']=='pristine':need(count(right,COLORS[name])>0,'standard popup continuation missing')
|
||||
if s.spec['mode']=='mosaic':validators.rgb(baseline,right);root=root_state(s,state,state['origin'],window)
|
||||
else:root=dict(status='NOT OBSERVABLE',meaning='unmodified control has no private clip instrumentation')
|
||||
s.save('chain',dict(protocol=p,geometry=g,state=state,root=root,H2_exact=s.spec['mode']=='mosaic',color_counts={n:[count(left,c),count(right,c)] for n,c in COLORS.items()}))
|
||||
return state,baseline,p,g,left,right
|
||||
|
||||
def popup_campaign(s):
|
||||
init_reference(s);state,baseline,p,g,left,right=open_chain(s)
|
||||
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],s.frames['HEADLESS-1'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
forbidden=intersection(g['B'],s.outputs[1]['logical']);need(forbidden[2]>6 and forbidden[3]>6,'H2 crossing interior missing')
|
||||
xy=[forbidden[0]+forbidden[2]//2,forbidden[1]+forbidden[3]//2]
|
||||
if s.spec['mode']=='pristine':
|
||||
# The point must be real standard paint, not just a requested extent.
|
||||
xy=solid_point(right,COLORS['POPUP_B'],s.outputs[1],s.frames['HEADLESS-2'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
else:
|
||||
tails,buttons=s.click('owner',xy);ds=dismissal(tails['owner'],buttons['owner']);s.save('dismissal',dict(proof=ds,reference_buttons=buttons.get('reference'),interpretation='outside grab dismissal; no universal click-through claim'))
|
||||
need(not any('target=POPUP_A ' in l or 'target=POPUP_B ' in l for v in buttons.values() for l in v),'forbidden popup delivery')
|
||||
s.close_client('owner');time.sleep(.2);final=s.pair_capture('final');exact(baseline,final[1]);need(not any(count(im,COLORS[n]) for im in final for n in ('POPUP_A','POPUP_B')),'popup residual')
|
||||
return 'PARTIALLY_FEASIBLE'
|
||||
|
||||
def hidden_campaign(s):
|
||||
init_reference(s,2);state,baseline,p,g,left,right=open_chain(s,2)
|
||||
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],'logical',g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
delta=state['scene'][0]+state['scene'][2]-s.outputs[0]['logical'][0]+16;start=time.monotonic_ns();s.scroll('owner',delta);hidden=s.owner('owner')
|
||||
need(intersection(hidden['scene'],s.outputs[0]['logical'])[2]==0,'owner not completely hidden')
|
||||
images=s.pair_capture('fully-hidden');need(not any(count(im,COLORS[n]) for im in images for n in ('POPUP_A','POPUP_B')),'hidden popup residual');exact(baseline,images[1])
|
||||
root=root_state(s,hidden,hidden['origin'],start,True,previous=state);cut=len(s.log('owner'));s.inject('move',[-790,590]);need('target=POPUP_A ' not in s.log('owner')[cut:] and 'target=POPUP_B ' not in s.log('owner')[cut:],'hidden popup pointer enter')
|
||||
validators.hidden_survival(s.log('owner'),p,[])
|
||||
return_start=time.monotonic_ns();s.scroll('owner',-delta);restored=s.owner('owner');need(restored['origin']==state['origin'],'same owner origin restored')
|
||||
images=s.pair_capture('returned');validators.hidden_survival(s.log('owner'),p,[e for e in s.events if e['kind']=='injection' and start<=e['ns']<=return_start]);exact(baseline,images[1]);returned_root=root_state(s,restored,restored['origin'],return_start,previous=hidden,expected_tree=root.get('tree'));fresh=validators.grab(s.log('owner'));need(fresh['ids']==p['ids'] and fresh['grabs']==p['grabs'],'unchanged live chain')
|
||||
g=geometry(s.log('owner'),restored['origin'],Provenance(restored['owner']['id'],restored['workspace']['id'],'NOT OBSERVABLE','fresh return geometry'));xy=solid_point(images[0],COLORS['POPUP_B'],s.outputs[0],'logical',g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
s.save('hidden-return',dict(status='PASS',hidden=hidden,empty_root=root,returned=restored,returned_root=returned_root,ids=p['ids']))
|
||||
s.close_client('owner');final=s.pair_capture('final');exact(baseline,final[1]);return 'PARTIALLY_FEASIBLE'
|
||||
|
||||
def sample_surviving(s,images,tag):
|
||||
if 'POPUP_DONE ' in s.log('owner'):
|
||||
s.save(tag+'-input',dict(status='NOT_EXERCISED',reason='legitimate popup dismissal, no new chain reserved'));return
|
||||
state=s.owner('owner');prot=validators.grab(s.log('owner'));geo=geometry(s.log('owner'),state['origin'],Provenance(state['owner']['id'],state['workspace']['id'],'NOT OBSERVABLE','mutation paint+pointer'))
|
||||
index=next(i for i,o in enumerate(s.outputs) if o['name']==state['workspace']['output'])
|
||||
try:point=solid_point(images[index],COLORS['POPUP_B'],s.outputs[index],s.frames[s.outputs[index]['name']],geo['B'])
|
||||
except ValueError:
|
||||
s.save(tag+'-input',dict(status='NOT_EXERCISED',reason='B has no measured visible interior'));return
|
||||
tails,buttons=s.click('owner',point);s.delivery('owner',geo['B'],point,tails,buttons,'POPUP_B');s.save(tag+'-input',dict(status='PASS',point=point,protocol=prot))
|
||||
|
||||
def dynamic_geometry(snap,previous=None,mutation='resize',expected_focus=None):
|
||||
# WHY: migration move.c arranges both outputs, then seat_set_focus reveals
|
||||
# the current selected cell. CONTRACT: derive clamp/reveal from current row
|
||||
# extents, gaps, viewport and focus; no universal800/1120 expected value.
|
||||
# INVARIANT: IPC is a candidate; paint/input after each mutation qualify only
|
||||
# sampled applied behavior, never a native atomic generation.
|
||||
need(snap.get('state_source')!='pending-as-current','pending mixed with current')
|
||||
ws=workspace(snap['tree'],'1');need(ws.get('layout','mosaic')=='mosaic','dynamic layout')
|
||||
rows=ws['mosaic']['rows'];need(len(rows)==1,'runtime Row0 only');row=rows[0]
|
||||
children=ws['nodes'];ids=[c['id'] for c in children]
|
||||
need(len(children)>=2 and row['children']==ids and len(set(ids))==len(ids),'dynamic row order/identity')
|
||||
viewport=[ws['rect'][k] for k in ('x','y','width','height')];width=viewport[2]
|
||||
need(width>0,'dynamic viewport width')
|
||||
widths=[c['rect']['width'] for c in children]
|
||||
gaps=[b['rect']['x']-a['rect']['x']-a['rect']['width'] for a,b in zip(children,children[1:])]
|
||||
need(len(set(widths))==1 and widths[0]>=640 and len(set(gaps))==1 and gaps[0]>=0,'pinned equal cell / gap geometry')
|
||||
gap=gaps[0];fitting=(width-(len(children)-1)*gap)//len(children)
|
||||
need(widths[0]==max(640,fitting),'native default640 sizing mismatch')
|
||||
need(children[0]['rect']['x']==viewport[0],'row start differs from viewport')
|
||||
content=children[-1]['rect']['x']+widths[-1]-viewport[0];maximum=max(0,content-width)
|
||||
scroll=row['scroll_x'];need(0<=scroll<=maximum,'camera outside native clamp')
|
||||
focused=[c for c in children if c.get('focused')]
|
||||
need(len(focused)==1 and row.get('focused')==focused[0]['id'],'missing/ambiguous row refocus')
|
||||
selected=focused[0]
|
||||
if expected_focus is not None:need(selected['id']==expected_focus,'refocus selected another owner')
|
||||
result=dict(workspace_id=ws['id'],output=ws['output'],children=ids,viewport=viewport,minimum_width=640,cell_width=widths[0],gap=gap,content_width=content,min_scroll=0,max_scroll=maximum,scroll=scroll,focused=selected['id'],geometry_status='IPC_PENDING_CANDIDATE; native generation NOT_OBSERVABLE')
|
||||
if previous is not None:
|
||||
need(ws['id']==previous['workspace_id'] and ids==previous['children'],'workspace/order changed')
|
||||
need(mutation in ('resize','migration'),'unknown camera mutation')
|
||||
candidate=max(0,min(previous['scroll'],maximum));expected=candidate
|
||||
if mutation=='migration':
|
||||
lo=selected['rect']['x']-viewport[0];hi=lo+selected['rect']['width']
|
||||
if lo<expected:expected=max(0,lo)
|
||||
elif hi>expected+width:expected=hi-width
|
||||
expected=max(0,min(expected,maximum))
|
||||
need(scroll==expected,'native reconstructed '+mutation+' camera mismatch')
|
||||
result.update(expected_scroll=expected,resize_candidate=candidate,mutation=mutation,clamp='PASS')
|
||||
return result
|
||||
|
||||
|
||||
def dynamic_pixels(images,outputs,frames,viewport,reference=None):
|
||||
# WHY/CONTRACT: each mutation must reject old-output and outside-clip paint.
|
||||
# INVARIANT: sampled RGB exclusion cannot prove complete native damage.
|
||||
for image,out in zip(images,outputs):
|
||||
for y in range(image.height):
|
||||
for x in range(image.width):
|
||||
if image.getpixel((x,y)) in (COLORS['POPUP_A'],COLORS['POPUP_B']):
|
||||
need(contains(viewport,png_global((x,y),out,frames[out['name']])),'dynamic popup pixel outside current viewport')
|
||||
if reference is not None:exact(reference,images[1])
|
||||
return dict(exclusion='PASS',whole_H2='PASS' if reference is not None else 'NOT_APPLICABLE: current owner workspace on H2',complete_damage='NOT OBSERVABLE')
|
||||
|
||||
def dynamic_campaign(s):
|
||||
init_reference(s,2);s.cmd('gaps inner 0');state,baseline,p,g,left,right=open_chain(s)
|
||||
owner_id=state['owner']['id'];previous=dynamic_geometry(state['snapshot'],expected_focus=owner_id);stages=[]
|
||||
mutations=[('output HEADLESS-1 resolution 960x600 position -960 0','resized960','HEADLESS-1'),
|
||||
('output HEADLESS-1 resolution 800x600 position -800 0','resized-back','HEADLESS-1'),
|
||||
('move workspace to output HEADLESS-2','migrated-H2','HEADLESS-2'),
|
||||
('move workspace to output HEADLESS-1','migrated-back-H1','HEADLESS-1')]
|
||||
for command,tag,out in mutations:
|
||||
s.cmd('workspace 1');s.record('invalidate-mutation-coordinates',tag=tag,old_viewport=previous['viewport'],old_camera=previous['scroll'],old_focus=previous['focused']);s.cmd(command,True);s.expected_output=out
|
||||
if tag=='resized960':s.outputs[0]={**s.outputs[0],'logical':[-960,0,960,600],'mode':[960,600]}
|
||||
elif tag=='resized-back':s.outputs[0]=dict(s.spec['outputs'][0])
|
||||
time.sleep(.35);snap=s.snapshot(tag);current=dynamic_geometry(snap,previous,"migration" if tag.startswith("migrated") else "resize",owner_id)
|
||||
need(current['output']==out and s.find('owner')['id']==owner_id,'dynamic exact owner/output')
|
||||
expected_viewport=next(o['logical'] for o in s.outputs if o['name']==out)
|
||||
need(current['viewport']==expected_viewport,'dynamic viewport/output reconstruction')
|
||||
pictures=s.pair_capture(tag)
|
||||
pixels=dynamic_pixels(pictures,s.outputs,s.frames,current['viewport'],baseline if out=='HEADLESS-1' else None)
|
||||
# An alive chain must supply fresh paint and local pointer input; an
|
||||
# actual XDG dismissal is recorded without injecting on destroyed popups.
|
||||
if 'POPUP_DONE ' not in s.log('owner'):
|
||||
fresh=s.owner('owner');geo=geometry(s.log('owner'),fresh['origin'],Provenance(owner_id,current['workspace_id'],'NOT OBSERVABLE','fresh mutation IPC + paint + pointer'))
|
||||
index=next(i for i,o in enumerate(s.outputs) if o['name']==out)
|
||||
point=solid_point(pictures[index],COLORS['POPUP_B'],s.outputs[index],s.frames[out],geo['B'])
|
||||
tails,buttons=s.click('owner',point);s.delivery('owner',geo['B'],point,tails,buttons,'POPUP_B')
|
||||
need(validators.grab(s.log('owner'))['ids']==p['ids'],'dynamic popup IDs changed')
|
||||
input_proof=dict(status='PASS',point=point)
|
||||
else:input_proof=dict(status='NOT_EXERCISED',reason='XDG dismissal; no injection on destroyed popup')
|
||||
stages.append(dict(tag=tag,geometry=current,pixels=pixels,input=input_proof));previous=current
|
||||
s.save('dynamic-ownership',dict(status='PASS',stages=stages,ids_before=p['ids'],atomic_generation='NOT PROVEN',complete_damage='NOT OBSERVABLE'))
|
||||
s.close_client('owner')
|
||||
for name in list(s.proc):
|
||||
if name.startswith('filler'):s.close_client(name,'close')
|
||||
final=s.pair_capture('dynamic-final');exact(baseline,final[1]);need(not any(count(im,COLORS[n]) for im in final for n in ('POPUP_A','POPUP_B')),'dynamic final popup residual');s.save('dynamic-final-restoration',dict(status='PARTIALLY_FEASIBLE',H2='EXACT',captured_after_close=True));return 'PARTIALLY_FEASIBLE'
|
||||
|
|
@ -0,0 +1,310 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
|
||||
* parent is A's xdg_surface; logs name object IDs as well as colors. */
|
||||
struct surface {
|
||||
const char *name;
|
||||
struct wl_surface *wl;
|
||||
struct xdg_surface *xdg;
|
||||
struct xdg_popup *popup;
|
||||
bool configured, mapped, done;
|
||||
int width, height, anchor_x, anchor_y;
|
||||
uint32_t color;
|
||||
};
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static struct xdg_toplevel *top_role;
|
||||
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
|
||||
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
|
||||
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static struct surface *entered;
|
||||
|
||||
/* Observe client-side SHM release separately from native GPU retirement.
|
||||
* Keep protocol buffer alive until release; no backing memory is reused. */
|
||||
static unsigned shm_created, shm_released;
|
||||
static void buffer_release(void *data, struct wl_buffer *buffer) {
|
||||
(void)data;
|
||||
++shm_released;
|
||||
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
|
||||
wl_buffer_destroy(buffer);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
|
||||
static void draw(struct surface *s) {
|
||||
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
|
||||
char path[4096];
|
||||
const char *scratch = getenv("TMPDIR");
|
||||
const char *lab = getenv("SMX_LAB");
|
||||
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
|
||||
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
|
||||
char expected[4096];
|
||||
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
|
||||
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
|
||||
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
|
||||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
|
||||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
|
||||
path_len >= (int)sizeof(path)) {
|
||||
fprintf(stderr, "invalid LAB scratch for SHM\n");
|
||||
exit(1);
|
||||
}
|
||||
int fd = mkstemp(path);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
|
||||
unlink(path);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
|
||||
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
|
||||
s->width*4, WL_SHM_FORMAT_XRGB8888);
|
||||
++shm_created;
|
||||
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
|
||||
wl_surface_attach(s->wl, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
|
||||
wl_surface_commit(s->wl);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
s->mapped = true;
|
||||
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
|
||||
}
|
||||
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
|
||||
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
|
||||
xdg_surface_ack_configure(xdg, serial);
|
||||
s->configured=true;
|
||||
if (!s->done) draw(s); /* fresh native commit after every configure */
|
||||
}
|
||||
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
|
||||
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
|
||||
int32_t h, struct wl_array *states) {
|
||||
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
|
||||
}
|
||||
static void on_top_close(void *data, struct xdg_toplevel *role) {
|
||||
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
|
||||
}
|
||||
static const struct xdg_toplevel_listener top_listener = {
|
||||
.configure=on_top_configure, .close=on_top_close,
|
||||
};
|
||||
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
|
||||
int32_t y, int32_t w, int32_t h) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
|
||||
}
|
||||
static void on_popup_done(void *data, struct xdg_popup *role) {
|
||||
struct surface *s=data;
|
||||
s->done=true;
|
||||
s->mapped=false;
|
||||
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
|
||||
}
|
||||
static const struct xdg_popup_listener popup_listener = {
|
||||
.configure=on_popup_configure, .popup_done=on_popup_done,
|
||||
};
|
||||
/* WHY: XDG requires child-first destruction even if the compositor sends
|
||||
* dismissals for multiple levels in one dispatch batch. */
|
||||
static void destroy_popup(struct surface *s) {
|
||||
if (!s->popup) return;
|
||||
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl));
|
||||
xdg_popup_destroy(s->popup);
|
||||
xdg_surface_destroy(s->xdg);
|
||||
wl_surface_destroy(s->wl);
|
||||
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void collect_dismissals(void) {
|
||||
if (b.done) destroy_popup(&b);
|
||||
if (a.done) {
|
||||
destroy_popup(&b);
|
||||
destroy_popup(&a);
|
||||
}
|
||||
}
|
||||
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
|
||||
if (s->popup || !parent->mapped || !serial) return;
|
||||
s->wl=wl_compositor_create_surface(compositor);
|
||||
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
|
||||
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
|
||||
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
|
||||
/* WHY/CONTRACT/INVARIANT: explicit identical adjustment in both controls. */
|
||||
xdg_positioner_set_constraint_adjustment(pos, 0);
|
||||
xdg_positioner_set_size(pos,s->width,s->height);
|
||||
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
|
||||
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
|
||||
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
|
||||
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
|
||||
xdg_popup_add_listener(s->popup,&popup_listener,s);
|
||||
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
|
||||
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
|
||||
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
|
||||
s->name,parent->name,serial,serial);
|
||||
xdg_popup_grab(s->popup,seat,serial);
|
||||
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
|
||||
xdg_positioner_destroy(pos);
|
||||
}
|
||||
static struct surface *which(struct wl_surface *wl) {
|
||||
if (wl==top.wl) return ⊤
|
||||
if (wl==a.wl && wl) return &a;
|
||||
if (wl==b.wl && wl) return &b;
|
||||
return NULL;
|
||||
}
|
||||
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
|
||||
entered=which(wl);
|
||||
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
|
||||
wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl) {
|
||||
struct surface *s=which(wl);
|
||||
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
|
||||
wl_fixed_t x,wl_fixed_t y) {
|
||||
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
uint32_t time,uint32_t button,uint32_t state) {
|
||||
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
|
||||
entered?entered->name:"OTHER",serial,button,state);
|
||||
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
|
||||
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
|
||||
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
|
||||
}
|
||||
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
|
||||
static void on_frame(void *data,struct wl_pointer *ptr) {}
|
||||
static const struct wl_pointer_listener pointer_listener={
|
||||
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
|
||||
.axis=on_axis,.frame=on_frame,
|
||||
};
|
||||
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer=wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
|
||||
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
|
||||
const char *interface,uint32_t version) {
|
||||
if (!strcmp(interface,wl_compositor_interface.name))
|
||||
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
|
||||
else if (!strcmp(interface,wl_shm_interface.name))
|
||||
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
|
||||
else if (!strcmp(interface,xdg_wm_base_interface.name))
|
||||
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
|
||||
else if (!strcmp(interface,wl_seat_interface.name)) {
|
||||
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
|
||||
wl_seat_add_listener(seat,&seat_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
|
||||
static void stop(int sig) { running=0; }
|
||||
int main(int argc,char **argv) {
|
||||
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
|
||||
struct rlimit no_core = {0, 0};
|
||||
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
/* EXPERIMENTAL: only placement changes; serial/grab/lifecycle stay historical.
|
||||
* Both 1x1 anchors lie inside their parent SHM window geometry. */
|
||||
/* WHY/CONTRACT/INVARIANT: fullscreen group geometry is measured; legal
|
||||
* anchors and wide buffers request crossing without forcing native placement. */
|
||||
if (!strcmp(placement,"fullscreen_cross")) {
|
||||
a.anchor_x=350; a.anchor_y=80; a.width=900;
|
||||
b.anchor_x=100; b.anchor_y=60; b.width=900;
|
||||
} else if (!strcmp(placement,"static_cross")) {
|
||||
a.anchor_x=100; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=100; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"visible")) {
|
||||
a.anchor_x=50; a.anchor_y=80; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=60; b.width=170;
|
||||
} else if (!strcmp(placement,"both_cross")) {
|
||||
a.anchor_x=350; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=160; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"b_cross")) {
|
||||
a.anchor_x=200; a.anchor_y=80; a.width=300;
|
||||
b.anchor_x=220; b.anchor_y=60; b.width=300;
|
||||
} else { fprintf(stderr,"invalid placement\n"); return 2; }
|
||||
display=wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry,®istry_listener,NULL);
|
||||
wl_display_roundtrip(display); wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm || !seat || !pointer) {
|
||||
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
|
||||
}
|
||||
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
|
||||
top.wl=wl_compositor_create_surface(compositor);
|
||||
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
|
||||
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
|
||||
top_role=xdg_surface_get_toplevel(top.xdg);
|
||||
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
|
||||
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
|
||||
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
|
||||
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)top.wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)top.xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
|
||||
wl_surface_commit(top.wl);
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
|
||||
{.fd=STDIN_FILENO,.events=POLLIN}};
|
||||
int ready=poll(fds,2,500);
|
||||
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
|
||||
if (wl_display_dispatch(display)<0) break;
|
||||
collect_dismissals();
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (fgets(cmd,sizeof(cmd),stdin)) {
|
||||
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
|
||||
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"CLOSE_RECEIVED\nCOMMAND close_owner\n"); running=0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy_popup(&b); destroy_popup(&a);
|
||||
fprintf(stderr,"TOPLEVEL DESTROY\n");
|
||||
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
|
||||
wl_display_flush(display);
|
||||
/* WHY/CONTRACT/INVARIANT: exit0 does not prove protocol error0. */
|
||||
fprintf(stderr,"EXIT error=%d\n",wl_display_get_error(display));
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
|
||||
* parent is A's xdg_surface; logs name object IDs as well as colors. */
|
||||
struct surface {
|
||||
const char *name;
|
||||
struct wl_surface *wl;
|
||||
struct xdg_surface *xdg;
|
||||
struct xdg_popup *popup;
|
||||
bool configured, mapped, done;
|
||||
int width, height, anchor_x, anchor_y;
|
||||
uint32_t color;
|
||||
};
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static struct xdg_toplevel *top_role;
|
||||
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
|
||||
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
|
||||
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static struct surface *entered;
|
||||
|
||||
/* Observe client-side SHM release separately from native GPU retirement.
|
||||
* Keep protocol buffer alive until release; no backing memory is reused. */
|
||||
static unsigned shm_created, shm_released;
|
||||
static void buffer_release(void *data, struct wl_buffer *buffer) {
|
||||
(void)data;
|
||||
++shm_released;
|
||||
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
|
||||
wl_buffer_destroy(buffer);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
|
||||
static void draw(struct surface *s) {
|
||||
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
|
||||
char path[4096];
|
||||
const char *scratch = getenv("TMPDIR");
|
||||
const char *lab = getenv("SMX_LAB");
|
||||
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
|
||||
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
|
||||
char expected[4096];
|
||||
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
|
||||
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
|
||||
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
|
||||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
|
||||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
|
||||
path_len >= (int)sizeof(path)) {
|
||||
fprintf(stderr, "invalid LAB scratch for SHM\n");
|
||||
exit(1);
|
||||
}
|
||||
int fd = mkstemp(path);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
|
||||
unlink(path);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
|
||||
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
|
||||
s->width*4, WL_SHM_FORMAT_XRGB8888);
|
||||
++shm_created;
|
||||
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
|
||||
wl_surface_attach(s->wl, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
|
||||
wl_surface_commit(s->wl);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
s->mapped = true;
|
||||
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
|
||||
}
|
||||
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
|
||||
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
|
||||
xdg_surface_ack_configure(xdg, serial);
|
||||
s->configured=true;
|
||||
if (!s->mapped && !s->done) draw(s);
|
||||
}
|
||||
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
|
||||
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
|
||||
int32_t h, struct wl_array *states) {
|
||||
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
|
||||
}
|
||||
static void on_top_close(void *data, struct xdg_toplevel *role) {
|
||||
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
|
||||
}
|
||||
static const struct xdg_toplevel_listener top_listener = {
|
||||
.configure=on_top_configure, .close=on_top_close,
|
||||
};
|
||||
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
|
||||
int32_t y, int32_t w, int32_t h) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
|
||||
}
|
||||
static void on_popup_done(void *data, struct xdg_popup *role) {
|
||||
struct surface *s=data;
|
||||
s->done=true;
|
||||
s->mapped=false;
|
||||
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
|
||||
}
|
||||
static const struct xdg_popup_listener popup_listener = {
|
||||
.configure=on_popup_configure, .popup_done=on_popup_done,
|
||||
};
|
||||
/* WHY: XDG requires child-first destruction even if the compositor sends
|
||||
* dismissals for multiple levels in one dispatch batch. */
|
||||
static void destroy_popup(struct surface *s) {
|
||||
if (!s->popup) return;
|
||||
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl));
|
||||
xdg_popup_destroy(s->popup);
|
||||
xdg_surface_destroy(s->xdg);
|
||||
wl_surface_destroy(s->wl);
|
||||
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void collect_dismissals(void) {
|
||||
if (b.done) destroy_popup(&b);
|
||||
if (a.done) {
|
||||
destroy_popup(&b);
|
||||
destroy_popup(&a);
|
||||
}
|
||||
}
|
||||
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
|
||||
if (s->popup || !parent->mapped || !serial) return;
|
||||
s->wl=wl_compositor_create_surface(compositor);
|
||||
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
|
||||
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
|
||||
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
|
||||
xdg_positioner_set_size(pos,s->width,s->height);
|
||||
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
|
||||
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
|
||||
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
|
||||
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
|
||||
xdg_popup_add_listener(s->popup,&popup_listener,s);
|
||||
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
|
||||
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
|
||||
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
|
||||
s->name,parent->name,serial,serial);
|
||||
xdg_popup_grab(s->popup,seat,serial);
|
||||
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
|
||||
xdg_positioner_destroy(pos);
|
||||
}
|
||||
static struct surface *which(struct wl_surface *wl) {
|
||||
if (wl==top.wl) return ⊤
|
||||
if (wl==a.wl && wl) return &a;
|
||||
if (wl==b.wl && wl) return &b;
|
||||
return NULL;
|
||||
}
|
||||
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
|
||||
entered=which(wl);
|
||||
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
|
||||
wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl) {
|
||||
struct surface *s=which(wl);
|
||||
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
|
||||
wl_fixed_t x,wl_fixed_t y) {
|
||||
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
uint32_t time,uint32_t button,uint32_t state) {
|
||||
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
|
||||
entered?entered->name:"OTHER",serial,button,state);
|
||||
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
|
||||
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
|
||||
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
|
||||
}
|
||||
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
|
||||
static void on_frame(void *data,struct wl_pointer *ptr) {}
|
||||
static const struct wl_pointer_listener pointer_listener={
|
||||
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
|
||||
.axis=on_axis,.frame=on_frame,
|
||||
};
|
||||
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer=wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
|
||||
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
|
||||
const char *interface,uint32_t version) {
|
||||
if (!strcmp(interface,wl_compositor_interface.name))
|
||||
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
|
||||
else if (!strcmp(interface,wl_shm_interface.name))
|
||||
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
|
||||
else if (!strcmp(interface,xdg_wm_base_interface.name))
|
||||
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
|
||||
else if (!strcmp(interface,wl_seat_interface.name)) {
|
||||
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
|
||||
wl_seat_add_listener(seat,&seat_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
|
||||
static void stop(int sig) { running=0; }
|
||||
int main(int argc,char **argv) {
|
||||
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
|
||||
struct rlimit no_core = {0, 0};
|
||||
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
/* EXPERIMENTAL: only placement changes; serial/grab/lifecycle stay historical.
|
||||
* Both 1x1 anchors lie inside their parent SHM window geometry. */
|
||||
if (!strcmp(placement,"static_cross")) {
|
||||
a.anchor_x=100; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=100; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"visible")) {
|
||||
a.anchor_x=50; a.anchor_y=80; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=60; b.width=170;
|
||||
} else if (!strcmp(placement,"both_cross")) {
|
||||
a.anchor_x=350; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=160; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"b_cross")) {
|
||||
a.anchor_x=200; a.anchor_y=80; a.width=300;
|
||||
b.anchor_x=220; b.anchor_y=60; b.width=300;
|
||||
} else { fprintf(stderr,"invalid placement\n"); return 2; }
|
||||
display=wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry,®istry_listener,NULL);
|
||||
wl_display_roundtrip(display); wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm || !seat || !pointer) {
|
||||
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
|
||||
}
|
||||
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
|
||||
top.wl=wl_compositor_create_surface(compositor);
|
||||
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
|
||||
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
|
||||
top_role=xdg_surface_get_toplevel(top.xdg);
|
||||
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
|
||||
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
|
||||
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
|
||||
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)top.wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)top.xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
|
||||
wl_surface_commit(top.wl);
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
|
||||
{.fd=STDIN_FILENO,.events=POLLIN}};
|
||||
int ready=poll(fds,2,500);
|
||||
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
|
||||
if (wl_display_dispatch(display)<0) break;
|
||||
collect_dismissals();
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (fgets(cmd,sizeof(cmd),stdin)) {
|
||||
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
|
||||
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"CLOSE_RECEIVED\nCOMMAND close_owner\n"); running=0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy_popup(&b); destroy_popup(&a);
|
||||
fprintf(stderr,"TOPLEVEL DESTROY\n");
|
||||
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
|
||||
wl_display_flush(display);
|
||||
/* WHY/CONTRACT/INVARIANT: exit0 does not prove protocol error0. */
|
||||
fprintf(stderr,"EXIT error=%d\n",wl_display_get_error(display));
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#define _DEFAULT_SOURCE
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <wayland-client.h>
|
||||
#include "virtual-pointer-client.h"
|
||||
static struct zwlr_virtual_pointer_manager_v1 *manager;
|
||||
static void global(void *data, struct wl_registry *registry, uint32_t name,
|
||||
const char *interface, uint32_t version) {
|
||||
if (!strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name))
|
||||
manager=wl_registry_bind(registry,name,&zwlr_virtual_pointer_manager_v1_interface,
|
||||
version < 2 ? version : 2);
|
||||
}
|
||||
static void removed(void *data, struct wl_registry *registry, uint32_t name) {}
|
||||
int main(void) {
|
||||
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
|
||||
struct rlimit no_core = {0, 0};
|
||||
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
|
||||
struct wl_display *display=wl_display_connect(NULL);
|
||||
if (!display) return 1;
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
struct wl_registry_listener listener={global,removed};
|
||||
wl_registry_add_listener(registry,&listener,NULL);
|
||||
wl_display_roundtrip(display);
|
||||
if (!manager) return 2;
|
||||
struct zwlr_virtual_pointer_v1 *pointer=
|
||||
zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager,NULL);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"POINTER_READY\n");fflush(stderr);
|
||||
char line[128];
|
||||
uint32_t stamp=1000;
|
||||
while (fgets(line,sizeof(line),stdin)) {
|
||||
char action[16];int x,y;
|
||||
unsigned width,height;
|
||||
if (sscanf(line,"%15s %d %d %u %u",action,&x,&y,&width,&height)!=5 || !width || !height || x<0 || y<0 || (unsigned)x>=width || (unsigned)y>=height || (strcmp(action,"move") && strcmp(action,"left") && strcmp(action,"right"))) return 3;
|
||||
fprintf(stderr,"INJECT %s %d,%d\n",action,x,y);fflush(stderr);
|
||||
zwlr_virtual_pointer_v1_motion_absolute(pointer,stamp++,x,y,width,height);
|
||||
zwlr_virtual_pointer_v1_frame(pointer);
|
||||
wl_display_flush(display);usleep(250000);
|
||||
if (!strcmp(action,"left") || !strcmp(action,"right")) {
|
||||
uint32_t button=!strcmp(action,"left") ? BTN_LEFT : BTN_RIGHT;
|
||||
zwlr_virtual_pointer_v1_button(pointer,stamp++,button,WL_POINTER_BUTTON_STATE_PRESSED);
|
||||
zwlr_virtual_pointer_v1_frame(pointer);
|
||||
wl_display_flush(display);usleep(80000);
|
||||
zwlr_virtual_pointer_v1_button(pointer,stamp++,button,WL_POINTER_BUTTON_STATE_RELEASED);
|
||||
zwlr_virtual_pointer_v1_frame(pointer);
|
||||
wl_display_flush(display);
|
||||
}
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"INJECT_DONE %s %d,%d\n",action,x,y);fflush(stderr);
|
||||
}
|
||||
zwlr_virtual_pointer_v1_destroy(pointer);
|
||||
zwlr_virtual_pointer_manager_v1_destroy(manager);
|
||||
wl_registry_destroy(registry);
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* WHY: the compositor must wait on a real, responsive XDG client.
|
||||
* CONTRACT: hold only a received configure; ACK the latest received serial.
|
||||
* INVARIANT: Wayland dispatch and ping replies continue while ACK is held. */
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_surface *surface;
|
||||
static struct xdg_surface *xdg;
|
||||
static struct xdg_toplevel *toplevel;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static bool mapped, hold_next, held;
|
||||
static uint32_t latest_serial;
|
||||
static int32_t width = 420, height = 320, configured_width = 420, configured_height = 320;
|
||||
static unsigned generation;
|
||||
|
||||
static long long stamp(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
}
|
||||
|
||||
/* Observe client-side SHM release separately from native GPU retirement.
|
||||
* Keep protocol buffer alive until release; no backing memory is reused. */
|
||||
static unsigned shm_created, shm_released;
|
||||
static void buffer_release(void *data, struct wl_buffer *buffer) {
|
||||
(void)data;
|
||||
++shm_released;
|
||||
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
|
||||
wl_buffer_destroy(buffer);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
|
||||
static void draw(void) {
|
||||
if (configured_width > 0) width = configured_width;
|
||||
if (configured_height > 0) height = configured_height;
|
||||
if (width <= 0 || height <= 0 || width > 4096 || height > 4096) {
|
||||
fprintf(stderr, "INVALID_SIZE %d %d\n", width, height);
|
||||
exit(2);
|
||||
}
|
||||
size_t bytes = (size_t)width * (size_t)height * 4;
|
||||
char name[4096];
|
||||
const char *scratch = getenv("TMPDIR");
|
||||
const char *lab = getenv("SMX_LAB");
|
||||
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
|
||||
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
|
||||
char expected[4096];
|
||||
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
|
||||
int path_len = scratch ? snprintf(name, sizeof(name), "%s/smx-shm-XXXXXX", scratch) : -1;
|
||||
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
|
||||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
|
||||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
|
||||
path_len >= (int)sizeof(name)) {
|
||||
fprintf(stderr, "invalid LAB scratch for SHM\n");
|
||||
exit(1);
|
||||
}
|
||||
int fd = mkstemp(name);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("shm"); exit(2); }
|
||||
unlink(name);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(2); }
|
||||
uint32_t color = (generation++ % 2) ? 0x0020d0a0 : 0x002344cc;
|
||||
#ifdef STATIC_REFERENCE
|
||||
/* CONTRACT: H2 uses one opaque SHM color across every real configure. */
|
||||
color = 0x00303038;
|
||||
#endif
|
||||
for (size_t i = 0; i < bytes / 4; ++i) pixels[i] = color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
|
||||
width * 4, WL_SHM_FORMAT_XRGB8888);
|
||||
++shm_created;
|
||||
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(surface, 0, 0, width, height);
|
||||
wl_surface_commit(surface);
|
||||
fprintf(stderr, "COMMIT ns=%lld serial=%u size=%dx%d color=%08x\n",
|
||||
stamp(), latest_serial, width, height, color);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
mapped = true;
|
||||
}
|
||||
|
||||
static void ack_and_commit(void) {
|
||||
if (!latest_serial) return;
|
||||
xdg_surface_ack_configure(xdg, latest_serial);
|
||||
fprintf(stderr, "ACK ns=%lld serial=%u size=%dx%d\n",
|
||||
stamp(), latest_serial, configured_width, configured_height);
|
||||
draw();
|
||||
held = false;
|
||||
}
|
||||
|
||||
static void ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
fprintf(stderr, "PONG ns=%lld serial=%u\n", stamp(), serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
|
||||
|
||||
static void role_configure(void *data, struct xdg_toplevel *role,
|
||||
int32_t w, int32_t h, struct wl_array *states) {
|
||||
if (w > 0) configured_width = w;
|
||||
if (h > 0) configured_height = h;
|
||||
fprintf(stderr, "ROLE_CONFIGURE ns=%lld size=%dx%d\n", stamp(), w, h);
|
||||
}
|
||||
static void role_close(void *data, struct xdg_toplevel *role) { running = 0; }
|
||||
static const struct xdg_toplevel_listener role_listener = {
|
||||
.configure = role_configure, .close = role_close,
|
||||
};
|
||||
static void configure(void *data, struct xdg_surface *s, uint32_t serial) {
|
||||
latest_serial = serial;
|
||||
fprintf(stderr, "XDG_CONFIGURE ns=%lld serial=%u size=%dx%d hold=%d previous=%d\n",
|
||||
stamp(), serial, configured_width, configured_height, hold_next || held, held);
|
||||
if (hold_next || held) {
|
||||
held = true;
|
||||
hold_next = false;
|
||||
fprintf(stderr, "HELD ns=%lld serial=%u\n", stamp(), serial);
|
||||
} else {
|
||||
ack_and_commit();
|
||||
}
|
||||
}
|
||||
static const struct xdg_surface_listener surface_listener = {.configure = configure};
|
||||
static void pointer_enter(void *data, struct wl_pointer *p, uint32_t serial,
|
||||
struct wl_surface *s, wl_fixed_t x, wl_fixed_t y) {
|
||||
fprintf(stderr, "ENTER ns=%lld x=%.2f y=%.2f\n", stamp(),
|
||||
wl_fixed_to_double(x), wl_fixed_to_double(y));
|
||||
}
|
||||
static void pointer_leave(void *data, struct wl_pointer *p, uint32_t serial,
|
||||
struct wl_surface *s) { fprintf(stderr, "LEAVE ns=%lld\n", stamp()); }
|
||||
static void pointer_motion(void *data, struct wl_pointer *p, uint32_t time,
|
||||
wl_fixed_t x, wl_fixed_t y) {
|
||||
fprintf(stderr, "MOTION ns=%lld x=%.2f y=%.2f\n", stamp(),
|
||||
wl_fixed_to_double(x), wl_fixed_to_double(y));
|
||||
}
|
||||
static void pointer_button(void *data, struct wl_pointer *p, uint32_t serial,
|
||||
uint32_t time, uint32_t button, uint32_t state) {
|
||||
fprintf(stderr, "BUTTON ns=%lld serial=%u button=%u state=%u\n",
|
||||
stamp(), serial, button, state);
|
||||
}
|
||||
static void pointer_axis(void *data, struct wl_pointer *p, uint32_t t,
|
||||
uint32_t axis, wl_fixed_t value) {}
|
||||
static void pointer_frame(void *data, struct wl_pointer *p) {}
|
||||
static const struct wl_pointer_listener pointer_listener = {
|
||||
.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion,
|
||||
.button = pointer_button, .axis = pointer_axis, .frame = pointer_frame,
|
||||
};
|
||||
static void capabilities(void *data, struct wl_seat *s, uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer = wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer, &pointer_listener, NULL);
|
||||
}
|
||||
}
|
||||
static void seat_name(void *data, struct wl_seat *s, const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener = {
|
||||
.capabilities = capabilities, .name = seat_name,
|
||||
};
|
||||
static void global(void *data, struct wl_registry *registry, uint32_t name,
|
||||
const char *interface, uint32_t version) {
|
||||
if (!strcmp(interface, wl_compositor_interface.name))
|
||||
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, version < 4 ? version : 4);
|
||||
else if (!strcmp(interface, wl_shm_interface.name))
|
||||
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
||||
else if (!strcmp(interface, xdg_wm_base_interface.name))
|
||||
wm = wl_registry_bind(registry, name, &xdg_wm_base_interface, version < 3 ? version : 3);
|
||||
else if (!strcmp(interface, wl_seat_interface.name)) {
|
||||
seat = wl_registry_bind(registry, name, &wl_seat_interface, version < 5 ? version : 5);
|
||||
wl_seat_add_listener(seat, &seat_listener, NULL);
|
||||
}
|
||||
}
|
||||
static void global_remove(void *data, struct wl_registry *registry, uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = global, .global_remove = global_remove,
|
||||
};
|
||||
static void stop(int signum) { running = 0; }
|
||||
|
||||
int main(void) {
|
||||
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
|
||||
struct rlimit no_core = {0, 0};
|
||||
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
|
||||
signal(SIGTERM, stop);
|
||||
display = wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr, "CONNECT_FAILED\n"); return 1; }
|
||||
struct wl_registry *registry = wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry, ®istry_listener, NULL);
|
||||
wl_display_roundtrip(display);
|
||||
wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm) { fprintf(stderr, "MISSING_GLOBAL\n"); return 2; }
|
||||
xdg_wm_base_add_listener(wm, &wm_listener, NULL);
|
||||
surface = wl_compositor_create_surface(compositor);
|
||||
xdg = xdg_wm_base_get_xdg_surface(wm, surface);
|
||||
xdg_surface_add_listener(xdg, &surface_listener, NULL);
|
||||
toplevel = xdg_surface_get_toplevel(xdg);
|
||||
xdg_toplevel_add_listener(toplevel, &role_listener, NULL);
|
||||
#ifdef STATIC_REFERENCE
|
||||
xdg_toplevel_set_title(toplevel, "Mosaic Static Reference");
|
||||
xdg_toplevel_set_app_id(toplevel, "org.mosaic.StaticReference");
|
||||
#else
|
||||
xdg_toplevel_set_title(toplevel, "Mosaic Saved Buffer Probe");
|
||||
xdg_toplevel_set_app_id(toplevel, "org.mosaic.SavedBufferProbe");
|
||||
#endif
|
||||
wl_surface_commit(surface);
|
||||
fprintf(stderr, "READY ns=%lld\n", stamp());
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2] = {
|
||||
{.fd = wl_display_get_fd(display), .events = POLLIN},
|
||||
{.fd = STDIN_FILENO, .events = POLLIN},
|
||||
};
|
||||
int ready = poll(fds, 2, 100);
|
||||
if (ready < 0) { if (errno == EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) {
|
||||
if (wl_display_dispatch(display) < 0) break;
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (!fgets(cmd, sizeof(cmd), stdin)) break;
|
||||
if (!strncmp(cmd, "hold_next_configure", 19)) {
|
||||
hold_next = true;
|
||||
fprintf(stderr, "COMMAND hold_next_configure DONE ns=%lld\n", stamp());
|
||||
} else if (!strncmp(cmd, "release_configure", 17)) {
|
||||
if (held) ack_and_commit();
|
||||
fprintf(stderr, "COMMAND release_configure DONE ns=%lld\n", stamp());
|
||||
} else if (!strncmp(cmd, "close", 5)) {
|
||||
fprintf(stderr,"CLOSE_RECEIVED\n");
|
||||
running = 0;
|
||||
fprintf(stderr, "COMMAND close DONE ns=%lld\n", stamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "EXIT ns=%lld mapped=%d held=%d error=%d\n",
|
||||
stamp(), mapped, held, wl_display_get_error(display));
|
||||
xdg_toplevel_destroy(toplevel);
|
||||
xdg_surface_destroy(xdg);
|
||||
wl_surface_destroy(surface);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
|
||||
wl_display_flush(display);
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/* WHY: a deterministic H2 reference cannot contain Kitty's blinking cursor.
|
||||
* CONTRACT: use the same real XDG configure/ACK loop with fixed opaque SHM.
|
||||
* INVARIANT: once configured and stable, this client changes no pixels. */
|
||||
#define STATIC_REFERENCE 1
|
||||
#include "saved-client.c"
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
|
||||
* parent is A's xdg_surface; logs name object IDs as well as colors. */
|
||||
struct surface {
|
||||
const char *name;
|
||||
struct wl_surface *wl;
|
||||
struct xdg_surface *xdg;
|
||||
struct xdg_popup *popup;
|
||||
bool configured, mapped, done;
|
||||
int width, height, anchor_x, anchor_y;
|
||||
uint32_t color;
|
||||
};
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static struct xdg_toplevel *top_role;
|
||||
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
|
||||
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
|
||||
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static struct surface *entered;
|
||||
|
||||
/* Observe client-side SHM release separately from native GPU retirement.
|
||||
* Keep protocol buffer alive until release; no backing memory is reused. */
|
||||
static unsigned shm_created, shm_released;
|
||||
static void buffer_release(void *data, struct wl_buffer *buffer) {
|
||||
(void)data;
|
||||
++shm_released;
|
||||
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
|
||||
wl_buffer_destroy(buffer);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
|
||||
static void draw(struct surface *s) {
|
||||
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
|
||||
char path[4096];
|
||||
const char *scratch = getenv("TMPDIR");
|
||||
const char *lab = getenv("SMX_LAB");
|
||||
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
|
||||
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
|
||||
char expected[4096];
|
||||
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
|
||||
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
|
||||
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
|
||||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
|
||||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
|
||||
path_len >= (int)sizeof(path)) {
|
||||
fprintf(stderr, "invalid LAB scratch for SHM\n");
|
||||
exit(1);
|
||||
}
|
||||
int fd = mkstemp(path);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
|
||||
unlink(path);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
|
||||
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
|
||||
s->width*4, WL_SHM_FORMAT_XRGB8888);
|
||||
++shm_created;
|
||||
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
|
||||
wl_surface_attach(s->wl, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
|
||||
wl_surface_commit(s->wl);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
s->mapped = true;
|
||||
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
|
||||
}
|
||||
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
|
||||
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
|
||||
xdg_surface_ack_configure(xdg, serial);
|
||||
s->configured=true;
|
||||
if (!s->mapped && !s->done) draw(s);
|
||||
}
|
||||
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
|
||||
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
|
||||
int32_t h, struct wl_array *states) {
|
||||
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
|
||||
}
|
||||
static void on_top_close(void *data, struct xdg_toplevel *role) {
|
||||
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
|
||||
}
|
||||
static const struct xdg_toplevel_listener top_listener = {
|
||||
.configure=on_top_configure, .close=on_top_close,
|
||||
};
|
||||
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
|
||||
int32_t y, int32_t w, int32_t h) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
|
||||
}
|
||||
static void on_popup_done(void *data, struct xdg_popup *role) {
|
||||
struct surface *s=data;
|
||||
s->done=true;
|
||||
s->mapped=false;
|
||||
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
|
||||
}
|
||||
static const struct xdg_popup_listener popup_listener = {
|
||||
.configure=on_popup_configure, .popup_done=on_popup_done,
|
||||
};
|
||||
/* WHY: XDG requires child-first destruction even if the compositor sends
|
||||
* dismissals for multiple levels in one dispatch batch. */
|
||||
static void destroy_popup(struct surface *s) {
|
||||
if (!s->popup) return;
|
||||
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl));
|
||||
xdg_popup_destroy(s->popup);
|
||||
xdg_surface_destroy(s->xdg);
|
||||
wl_surface_destroy(s->wl);
|
||||
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void collect_dismissals(void) {
|
||||
if (b.done) destroy_popup(&b);
|
||||
if (a.done) {
|
||||
destroy_popup(&b);
|
||||
destroy_popup(&a);
|
||||
}
|
||||
}
|
||||
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
|
||||
if (s->popup || !parent->mapped || !serial) return;
|
||||
s->wl=wl_compositor_create_surface(compositor);
|
||||
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
|
||||
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
|
||||
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
|
||||
xdg_positioner_set_size(pos,s->width,s->height);
|
||||
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
|
||||
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
|
||||
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
|
||||
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
|
||||
xdg_popup_add_listener(s->popup,&popup_listener,s);
|
||||
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
|
||||
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
|
||||
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
|
||||
s->name,parent->name,serial,serial);
|
||||
xdg_popup_grab(s->popup,seat,serial);
|
||||
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
|
||||
xdg_positioner_destroy(pos);
|
||||
}
|
||||
static struct surface *which(struct wl_surface *wl) {
|
||||
if (wl==top.wl) return ⊤
|
||||
if (wl==a.wl && wl) return &a;
|
||||
if (wl==b.wl && wl) return &b;
|
||||
return NULL;
|
||||
}
|
||||
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
|
||||
entered=which(wl);
|
||||
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
|
||||
wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl) {
|
||||
struct surface *s=which(wl);
|
||||
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
|
||||
wl_fixed_t x,wl_fixed_t y) {
|
||||
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
uint32_t time,uint32_t button,uint32_t state) {
|
||||
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
|
||||
entered?entered->name:"OTHER",serial,button,state);
|
||||
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
|
||||
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
|
||||
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
|
||||
}
|
||||
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
|
||||
static void on_frame(void *data,struct wl_pointer *ptr) {}
|
||||
static const struct wl_pointer_listener pointer_listener={
|
||||
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
|
||||
.axis=on_axis,.frame=on_frame,
|
||||
};
|
||||
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer=wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
|
||||
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
|
||||
const char *interface,uint32_t version) {
|
||||
if (!strcmp(interface,wl_compositor_interface.name))
|
||||
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
|
||||
else if (!strcmp(interface,wl_shm_interface.name))
|
||||
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
|
||||
else if (!strcmp(interface,xdg_wm_base_interface.name))
|
||||
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
|
||||
else if (!strcmp(interface,wl_seat_interface.name)) {
|
||||
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
|
||||
wl_seat_add_listener(seat,&seat_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
|
||||
static void stop(int sig) { running=0; }
|
||||
int main(int argc,char **argv) {
|
||||
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
|
||||
struct rlimit no_core = {0, 0};
|
||||
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
/* CONTRACT: vertical positioner anchors remain inside the toplevel's
|
||||
* effective window geometry, which defaults to its buffer bounds. */
|
||||
if (!strncmp(placement,"v_",2)) top.height=575;
|
||||
if (!strcmp(placement,"visible")) {
|
||||
a.anchor_x=50; a.anchor_y=80; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=60; b.width=170;
|
||||
} else if (!strcmp(placement,"v_visible")) {
|
||||
a.anchor_x=50; a.anchor_y=80; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=60; b.width=170;
|
||||
} else if (!strcmp(placement,"v_b_cross")) {
|
||||
/* WHY: only B's requested rectangle reaches below y=600 when the
|
||||
* tiled toplevel content begins near y=25. Configure and pixels
|
||||
* remain the authority for the actual crossing. */
|
||||
a.anchor_x=50; a.anchor_y=350; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=155; b.width=170;
|
||||
} else if (!strcmp(placement,"v_both_cross")) {
|
||||
a.anchor_x=50; a.anchor_y=480; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=80; b.width=170;
|
||||
} else if (!strcmp(placement,"both_cross")) {
|
||||
a.anchor_x=350; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=160; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"b_cross")) {
|
||||
a.anchor_x=200; a.anchor_y=80; a.width=300;
|
||||
b.anchor_x=220; b.anchor_y=60; b.width=300;
|
||||
} else { fprintf(stderr,"invalid placement\n"); return 2; }
|
||||
display=wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry,®istry_listener,NULL);
|
||||
wl_display_roundtrip(display); wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm || !seat || !pointer) {
|
||||
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
|
||||
}
|
||||
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
|
||||
top.wl=wl_compositor_create_surface(compositor);
|
||||
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
|
||||
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
|
||||
top_role=xdg_surface_get_toplevel(top.xdg);
|
||||
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
|
||||
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
|
||||
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
|
||||
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)top.wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)top.xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
|
||||
wl_surface_commit(top.wl);
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
|
||||
{.fd=STDIN_FILENO,.events=POLLIN}};
|
||||
int ready=poll(fds,2,500);
|
||||
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
|
||||
if (wl_display_dispatch(display)<0) break;
|
||||
collect_dismissals();
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (fgets(cmd,sizeof(cmd),stdin)) {
|
||||
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
|
||||
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"CLOSE_RECEIVED\nCOMMAND close_owner\n"); running=0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy_popup(&b); destroy_popup(&a);
|
||||
fprintf(stderr,"TOPLEVEL DESTROY\n");
|
||||
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
|
||||
wl_display_flush(display);
|
||||
/* WHY/CONTRACT/INVARIANT: exit0 does not prove protocol error0. */
|
||||
fprintf(stderr,"EXIT error=%d\n",wl_display_get_error(display));
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
345
tooling/probes/step20c-corec-v2-followup/graphics/engine.py
Normal file
345
tooling/probes/step20c-corec-v2-followup/graphics/engine.py
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
"""Bounded owned headless session, usable only after the separate matrix review gate."""
|
||||
import errno,json,os,re,shutil,signal,subprocess,tempfile,threading,time
|
||||
from pathlib import Path
|
||||
from PIL import Image,ImageDraw
|
||||
from geometry import *
|
||||
from gate import sha,clean_environment,Ledger,canonical_lab,disable_dump,durable
|
||||
|
||||
import sys
|
||||
|
||||
from ipc_predicates import nodes,workspace
|
||||
from predicates import protocol,geometry,pair,dismissal
|
||||
import validators
|
||||
from lifecycle import opening_pair
|
||||
from model import Provenance
|
||||
COLORS={'TOPLEVEL':(35,68,204),'POPUP_A':(224,192,32),'POPUP_B':(32,208,160)}
|
||||
|
||||
def storage_guard(lab):
|
||||
# WHY/CONTRACT/INVARIANT: stop before new action at 9GiB or <20GiB free;
|
||||
# never purge old evidence to manufacture space.
|
||||
v=os.statvfs(lab);need(v.f_bavail*v.f_frsize>=20*1024**3,'SSD free space below20GiB')
|
||||
total=sum(p.lstat().st_size for p in lab.rglob('*') if p.is_file() and not p.is_symlink())
|
||||
need(total<=9*1024**3,'new LAB exceeds9GiB prudent stop')
|
||||
|
||||
def private_runtime():
|
||||
# WHY: tmpfs may have user quota exhaustion despite statvfs free space.
|
||||
# CONTRACT: probe an owned 0700 directory by writing, then fall back only
|
||||
# on EDQUOT/ENOSPC. INVARIANT: socket-only path stays UNIX-length-safe.
|
||||
for base in ('/tmp','/var/tmp'):
|
||||
runtime=None
|
||||
try:
|
||||
v=os.statvfs(base)
|
||||
if v.f_bavail*v.f_frsize<1024*1024:raise OSError(errno.ENOSPC,'runtime free space')
|
||||
runtime=Path(tempfile.mkdtemp(prefix='smx-',dir=base));runtime.chmod(0o700)
|
||||
need(len(os.fsencode(runtime/'sway-ipc.4294967295.4294967295.sock'))<108,'UNIX socket path too long')
|
||||
fd=os.open(runtime/'quota-probe',os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
|
||||
try:os.write(fd,b'X'*4096);os.fsync(fd)
|
||||
finally:os.close(fd)
|
||||
(runtime/'quota-probe').unlink();return runtime
|
||||
except OSError as exc:
|
||||
if runtime is not None:
|
||||
p=runtime/'quota-probe'
|
||||
if p.exists():p.unlink()
|
||||
runtime.rmdir()
|
||||
if exc.errno not in (errno.EDQUOT,errno.ENOSPC):raise
|
||||
if base=='/var/tmp':raise
|
||||
raise ValueError('no safe private runtime')
|
||||
|
||||
class Session:
|
||||
def __init__(self,spec,auth):
|
||||
need(not auth.get('offline'), 'OFFLINE_TEST_ONLY cannot launch');self.spec=spec;self.auth=auth;self.lab=canonical_lab(auth['lab']);self.ledger=Ledger(self.lab);self.folder=self.lab/'graphics-runs'/spec['id'];self.run=self.folder/'run';self.run.mkdir(parents=True)
|
||||
disable_dump();self.runtime=private_runtime()
|
||||
self.env=clean_environment(self.lab);self.env.update(XDG_RUNTIME_DIR=str(self.runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2')
|
||||
self.outputs=[dict(o) for o in spec['outputs']];self.frames={o['name']:'logical' for o in self.outputs}
|
||||
self.proc_lock=threading.RLock();self.proc={};self.transient={};self.tool_serial=0;self.closed=[];self.helper_exits=[];self.handles=[];self.started=time.monotonic();self.finished=threading.Event();self.expired=False;self.events=[];self.pointer=None
|
||||
def start(self):
|
||||
spec=self.spec;auth=self.auth
|
||||
cfg='\n'.join(f"output {o['name']} resolution {o['mode'][0]}x{o['mode'][1]} scale {o['scale']} transform {o['transform']} position {o['logical'][0]} {o['logical'][1]}" for o in self.outputs)
|
||||
cfg+='\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\nshadows disable\nblur disable\n'
|
||||
if spec['id'] in ('C3','C4'):
|
||||
marker=Image.new('RGB',(800,600),(30,30,46));draw=ImageDraw.Draw(marker)
|
||||
for k,box in [('TL',(15,15,44,44)),('TR',(755,15,784,44)),('BL',(15,555,44,584)),('BR',(755,555,784,584)),('C',(385,285,414,314))]:draw.rectangle(box,fill=MARKERS[k])
|
||||
marker.save(self.run/'marker-source.png');self.marker_source=self.run/'marker-source.png';self.record('generated-content-PNG',path=str(self.marker_source),counts_as_capture=False)
|
||||
need(cfg==self.spec['config'],'sealed config bytes');(self.run/'config').write_text(cfg);self.watch=threading.Thread(target=self.watchdog,daemon=True);self.watch.start()
|
||||
if spec['id'] in ('OM-ROOT','OM-NESTED'):
|
||||
from oom import process_environment
|
||||
self.oom_env=process_environment(self.lab,spec['id'],auth['token'])
|
||||
self.comp=self.spawn('compositor',[auth['binary'],'-d',*(['-Dtxn-timeout=5000'] if spec['id']=='C6' else []),'-c',self.run/'config'])
|
||||
self.sock=self.wait(lambda:next(iter(self.runtime.glob('sway-ipc.*.sock')),None));display=self.wait(lambda:next((p for p in self.runtime.glob('wayland-*') if p.is_socket()),None))
|
||||
self.env.update(WAYLAND_DISPLAY=display.name,SWAYSOCK=str(self.sock));need(Path(f'/proc/{self.comp.pid}/exe').resolve()==Path(auth['binary']).resolve() and sha(Path(f'/proc/{self.comp.pid}/exe'))==auth['binary_sha256'],'actual executable')
|
||||
maps=Path(f'/proc/{self.comp.pid}/maps').read_text();(self.run/'proc-maps.txt').write_text(maps)
|
||||
paths={s.split()[-1] for s in maps.splitlines() if len(s.split())>=6 and s.split()[-1].startswith('/')}
|
||||
wl=[Path(p).resolve() for p in paths if 'libwlroots' in p];need(wl==[Path(auth['wlroots'])] and sha(wl[0])==auth['wlroots_sha256'],'mapped wlroots')
|
||||
scene=[Path(p).resolve() for p in paths if 'libscenefx' in p]
|
||||
if spec['binary'] in ('core','oom','gpu'):need(not scene,'static SceneFX')
|
||||
else:need(scene==[Path(auth['pristine_scene'])] and sha(scene[0])==auth['pristine_scene_sha256'],'mapped pristine SceneFX')
|
||||
render=[s for s in self.log('compositor').splitlines() if any(x in s for x in ('Creating scenefx FX renderer','GL renderer:','GL vendor:','Using OpenGL ES'))]
|
||||
need(any('Creating scenefx FX renderer' in s for s in render) and any('GL renderer:' in s for s in render),'actual renderer')
|
||||
self.save('runtime-identity',dict(pid=self.comp.pid,executable=str(Path(f'/proc/{self.comp.pid}/exe').resolve()),binary_sha256=sha(Path(f'/proc/{self.comp.pid}/exe')),wlroots=[dict(path=str(p),sha256=sha(p)) for p in wl],renderer=render,egl=[line for line in self.log('compositor').splitlines() if 'EGL' in line],egl_source='compositor startup log; NOT_OBSERVABLE if empty',source_manifest=self.auth['manifest_sha256']));self.record('actual-renderer',lines=render);self.snapshot('startup')
|
||||
if spec['id'] in ('C3','C4'):
|
||||
self.spawn('marker-background',['swaybg','-o','HEADLESS-1','-i',self.marker_source,'-m','fill']);time.sleep(.35)
|
||||
im=self.capture('marker-H1','HEADLESS-1',orientation_pending=True);m=marker_frame(im,self.outputs[0]['scale'],self.outputs[0]['transform']=='90');self.frames['HEADLESS-1']=m['frame'];self.record('marker-frame',**m);self.close_helper('marker-background')
|
||||
self.pointer=self.spawn('pointer',[self.lab/'followup-work/client-build/pointer-daemon']);self.wait(lambda:'POINTER_READY' in self.log('pointer'))
|
||||
def close_helper(self,name):
|
||||
# WHY: swaybg is a calibration helper, not an XDG SHM test client.
|
||||
# CONTRACT: retire its owned group after the marker screenshot; retain
|
||||
# raw status separately. Never reinterpret SIGTERM as scientific close.
|
||||
need(name=='marker-background','unknown helper role');p=self.proc[name]
|
||||
if p.poll() is None:
|
||||
try:os.killpg(p.pid,signal.SIGTERM)
|
||||
except ProcessLookupError:pass
|
||||
code=p.wait(timeout=2)
|
||||
proof=dict(name=name,pid=p.pid,exit=code,expected_termination='owned helper SIGTERM after calibration',scientific_client=False)
|
||||
self.helper_exits.append(proof);self.record('helper-exit',**proof)
|
||||
need(code in (0,-signal.SIGTERM),'unexpected calibration helper exit')
|
||||
need(not Path('/proc',str(p.pid)).exists(),'calibration helper residual')
|
||||
if p.stdin and not p.stdin.closed:p.stdin.close()
|
||||
del self.proc[name]
|
||||
|
||||
def record(self,kind,**fields):
|
||||
e=dict(kind=kind,ns=time.monotonic_ns(),**fields);self.events.append(e)
|
||||
with (self.run/'events.jsonl').open('a') as f:f.write(json.dumps(e)+'\n')
|
||||
def save(self,name,value):
|
||||
durable(self.run/(name+'.json'),value)
|
||||
def debit(self,*kinds):
|
||||
storage_guard(self.lab);self.ledger.action_debit(self.auth,*kinds)
|
||||
def spawn(self,name,argv,debug=False):
|
||||
self.debit('spawn');need(not self.expired,'expired before spawn');f=(self.run/(name+'.log')).open('w');self.handles.append(f)
|
||||
requested_argv=list(map(str,argv));prlimit=shutil.which('prlimit')
|
||||
need(prlimit is not None,'util-linux prlimit unavailable; refusing unbounded child')
|
||||
# WHY: a child core dump can exhaust the bounded graphics LAB.
|
||||
# CONTRACT: every owned child starts with a zero soft and hard core limit.
|
||||
# INVARIANT: exec keeps the child PID stable for /proc identity checks.
|
||||
actual_argv=[prlimit,'--core=0:0','--',*requested_argv]
|
||||
# WHY/CONTRACT/INVARIANT: registration and expiry are atomic: no child
|
||||
# can escape the watchdog's owned-group snapshot by racing its deadline.
|
||||
with getattr(self,'proc_lock',threading.RLock()):
|
||||
need(not self.expired,'expired before process creation')
|
||||
# WHY/CONTRACT/INVARIANT: diagnostics are compositor-private and
|
||||
# never enter the environment shared with capture, IPC or clients.
|
||||
child_env={**self.env,**({'WAYLAND_DEBUG':'1'} if debug else {})}
|
||||
need(not any(k.startswith(('SMX_OOM_','MOSAIC_G2_','MOSAIC_G3_')) for k in child_env),'diagnostic environment leaked into common environment')
|
||||
if name=='compositor':
|
||||
child_env.update(getattr(self,'oom_env',{}));child_env.update(getattr(self,'compositor_environment',{}))
|
||||
p=subprocess.Popen(actual_argv,stdin=subprocess.PIPE,stdout=f,stderr=subprocess.STDOUT,text=True,start_new_session=True,env=child_env)
|
||||
self.proc[name]=p
|
||||
self.record('spawn',name=name,pid=p.pid,requested_argv=requested_argv,actual_argv=actual_argv);return p
|
||||
def log(self,name):return (self.run/(name+'.log')).read_text(errors='replace')
|
||||
def wait(self,fn,seconds=5):
|
||||
end=min(self.started+143,time.monotonic()+seconds)
|
||||
while time.monotonic()<end:
|
||||
need(not self.expired,'watchdog')
|
||||
if hasattr(self,'comp'):need(self.comp.poll() is None,'unexpected compositor exit')
|
||||
v=fn()
|
||||
if v:return v
|
||||
time.sleep(.02)
|
||||
raise Ambiguous('bounded wait timeout')
|
||||
def remaining(self,deadline=146):
|
||||
return max(.001,self.started+deadline-time.monotonic())
|
||||
def signal_owned(self,sig):
|
||||
with getattr(self,'proc_lock',threading.RLock()):
|
||||
for p in [*self.proc.values(),*self.transient.values()]:
|
||||
try:os.killpg(p.pid,sig)
|
||||
except ProcessLookupError:pass
|
||||
def watchdog(self):
|
||||
# WHY: main-thread protocol closure may block or fail.
|
||||
# CONTRACT: autonomous TERM at143, KILL by146, below150s/run.
|
||||
# INVARIANT: only registered private session groups are signalled;
|
||||
# the main cleanup shares this absolute deadline, never N grace periods.
|
||||
while not self.finished.wait(.02):
|
||||
if time.monotonic()>=self.started+143 or any(p.stat().st_size>32*1024*1024 for p in self.run.glob('*.log')):
|
||||
with getattr(self,'proc_lock',threading.RLock()):self.expired=True
|
||||
self.signal_owned(signal.SIGTERM)
|
||||
grace_end=min(self.started+146,time.monotonic()+2)
|
||||
if not self.finished.wait(max(0,grace_end-time.monotonic())):
|
||||
self.signal_owned(signal.SIGKILL)
|
||||
return
|
||||
def command_process(self,argv,timeout=4):
|
||||
# WHY: capture/IPC helpers also belong to the autonomous watchdog.
|
||||
# CONTRACT: bounded file output and owned session groups for every tool.
|
||||
# INVARIANT: debit precedes spawn; raw tool status remains independent.
|
||||
self.debit('tool');self.tool_serial+=1;tag='tool-'+str(self.tool_serial)
|
||||
outpath=self.run/(tag+'.stdout.log');errpath=self.run/(tag+'.stderr.log')
|
||||
with outpath.open('w') as stdout,errpath.open('w') as stderr:
|
||||
with self.proc_lock:
|
||||
need(not self.expired,'expired before helper spawn')
|
||||
argv=list(map(str,argv));actual=['prlimit','--core=0:0','--',*argv]
|
||||
p=subprocess.Popen(actual,env=self.env,stdout=stdout,stderr=stderr,start_new_session=True)
|
||||
self.transient[p.pid]=p
|
||||
try:
|
||||
try:p.wait(timeout=min(timeout,self.remaining()))
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(p.pid,signal.SIGKILL);p.wait(timeout=1);raise
|
||||
finally:
|
||||
with self.proc_lock:self.transient.pop(p.pid,None)
|
||||
need(outpath.stat().st_size<=32*1024*1024 and errpath.stat().st_size<=32*1024*1024,'helper log bound')
|
||||
result=subprocess.CompletedProcess(argv,p.returncode,outpath.read_text(errors='replace'),errpath.read_text(errors='replace'))
|
||||
self.record('tool-exit',argv=argv,pid=p.pid,exit=p.returncode,stdout_path=str(outpath),stderr_path=str(errpath))
|
||||
return result
|
||||
|
||||
def ipc(self,*args,strict=True):
|
||||
need(self.sock.is_relative_to(self.runtime) and self.sock.is_socket(),'private socket')
|
||||
self.record('ipc-request',args=args)
|
||||
p=self.command_process(['swaymsg','-s',str(self.sock),*args])
|
||||
self.record('ipc-reply',args=args,exit=p.returncode,stdout=p.stdout,stderr=p.stderr)
|
||||
if strict:need(p.returncode==0,'IPC command failure')
|
||||
return json.loads(p.stdout) if p.returncode==0 else None
|
||||
def cmd(self,command,business=False):
|
||||
self.debit('mutation' if business else 'configuration');reply=self.ipc(command);need(all(r['success'] for r in reply),'command rejected')
|
||||
def snapshot(self,tag,validate=True):
|
||||
snap=dict(tree=self.ipc('-t','get_tree'),outputs=self.ipc('-t','get_outputs'),workspaces=self.ipc('-t','get_workspaces'),ns=time.monotonic_ns())
|
||||
if validate:expected_outputs(snap['outputs'],self.outputs)
|
||||
self.save(tag+'-snapshot',snap);return snap
|
||||
def find(self,name):
|
||||
pid=self.proc[name].pid;rows=[n for n in nodes(self.ipc('-t','get_tree')) if n.get('pid')==pid];need(len(rows)<=1,'client owner ambiguity');return rows[0] if rows else None
|
||||
def launch_client(self,name,client,*args):
|
||||
build=self.lab/'followup-work/client-build'
|
||||
p=self.spawn(name,[build/client,*args],True);self.wait(lambda:self.find(name));time.sleep(.25)
|
||||
if client=='fullscreen-client':
|
||||
exe=Path(f'/proc/{p.pid}/exe');need(exe.resolve()==build/client and sha(exe)==self.auth['client_binary_sha256'],'actual fullscreen client executable')
|
||||
self.record('fullscreen-client-identity',pid=p.pid,binary=str(exe.resolve()),sha256=sha(exe))
|
||||
return p
|
||||
def capture(self,tag,out,orientation_pending=False):
|
||||
self.debit('capture');spec=next(o for o in self.outputs if o['name']==out);p=self.run/(tag+'.png');need(not p.exists(),'capture unique')
|
||||
t=time.monotonic_ns();r=self.command_process(['grim','-s',str(spec['scale']),'-o',out,str(p)])
|
||||
self.record('capture',tag=tag,output=out,start_ns=t,end_ns=time.monotonic_ns(),exit=r.returncode,path=str(p),sha256=sha(p) if p.exists() else None)
|
||||
need(r.returncode==0,'capture failure');im=Image.open(p).convert('RGB')
|
||||
logical=tuple(round(v*spec['scale']) for v in spec['logical'][2:]);physical=tuple(spec['mode'])
|
||||
need(im.size==logical or (orientation_pending and im.size==physical) or (self.frames[out]=='physical90' and im.size==physical),'PNG size')
|
||||
return im
|
||||
def pair_capture(self,tag):return [self.capture(tag+'-H1','HEADLESS-1'),self.capture(tag+'-H2','HEADLESS-2')]
|
||||
def inject(self,action,point,grab=False):
|
||||
need(action in ('move','left','right'),'injection action');outs=self.ipc('-t','get_outputs');active=[o for o in outs if o['active']]
|
||||
rects=[[o['rect'][k] for k in ('x','y','width','height')] for o in active];need(any(contains(r,point) for r in rects),'logical point outside output')
|
||||
minx=min(r[0] for r in rects);miny=min(r[1] for r in rects);w=max(r[0]+r[2] for r in rects)-minx;h=max(r[1]+r[3] for r in rects)-miny
|
||||
self.debit('move',*(['click'] if action!='move' else []),*(['grab'] if grab else []))
|
||||
sent=[point[0]-minx,point[1]-miny];marker=f'INJECT_DONE {action} {sent[0]},{sent[1]}';before=self.log('pointer').count(marker)
|
||||
self.pointer.stdin.write(f'{action} {sent[0]} {sent[1]} {w} {h}\n');self.pointer.stdin.flush();self.wait(lambda:self.log('pointer').count(marker)>before)
|
||||
self.record('injection',action=action,global_logical=point,sent=sent,logical_extent=[w,h],grab=grab);time.sleep(.12)
|
||||
def click(self,name,point,action='right',grab=False):
|
||||
before={n:len(self.log(n)) for n in self.proc if n not in ('compositor','pointer','background')};t=time.monotonic_ns();self.inject(action,point,grab)
|
||||
validators.window(t,time.monotonic_ns(),[e['ns'] for e in self.events if e['kind']=='injection' and e['ns']>=t]);tails={n:self.log(n)[i:] for n,i in before.items()};lines={n:[s for s in v.splitlines() if s.startswith(('POINTER_BUTTON ','BUTTON '))] for n,v in tails.items()}
|
||||
self.record('click-observation',name=name,point=point,start_ns=t,tails=tails,buttons=lines)
|
||||
return tails,lines
|
||||
def delivery(self,name,rect,point,tails,lines,target,opening_serial=None):
|
||||
need(not any(v for n,v in lines.items() if n!=name),'wrong client received popup button')
|
||||
proof=opening_pair(lines[name],target,opening_serial) if opening_serial else pair(lines[name],target)
|
||||
rows=re.findall(rf'POINTER_(?:ENTER|MOTION) target={target} .*?x=([\d.-]+) y=([\d.-]+)',tails[name]);need(rows,'fresh pointer local coordinates')
|
||||
local=list(map(float,rows[-1]));validators.applied(rect[:2],rect[:2],[point[0]-local[0],point[1]-local[1]],'applied')
|
||||
for e in proof:need(re.search(rf'wl_pointer#\d+\.button\({e[1]}, \d+, {e[2]}, {e[3]}\)',tails[name]) is not None,'button actual wire')
|
||||
return proof
|
||||
def owner(self,name):
|
||||
snap=self.snapshot(name+'-owner')
|
||||
rows=[n for n in nodes(snap['tree']) if n.get('pid')==self.proc[name].pid]
|
||||
need(len(rows)==1,'owner identity/ambiguity');owner=rows[0]
|
||||
ws=workspace(snap['tree'],'1')
|
||||
path=__import__('ipc_predicates').ancestry(snap['tree'],owner['id'])
|
||||
validators.owner_context(owner,path,self.proc[name].pid,'1',getattr(self,'expected_output','HEADLESS-1'),getattr(self,'expected_layout','mosaic' if self.spec['mode']=='mosaic' else 'splith'))
|
||||
# WHY: standard_return changes layout after the initial campaign mode.
|
||||
# CONTRACT: camera applies only to current Mosaic tiled descendants;
|
||||
# floating branches and fullscreen group ancestry remain exempt.
|
||||
# WHY/CONTRACT: root/output/workspace IPC descriptors may have floating=null.
|
||||
# INVARIANT: null means no floating flag; branch membership still counts,
|
||||
# and this geometry remains a pending candidate, never native-applied.
|
||||
eligible=validators.camera_eligible(path,ws)
|
||||
origin=assigned_origin(owner);scene=exterior_ipc(owner)
|
||||
if eligible:
|
||||
scroll=int(ws['mosaic']['rows'][0]['scroll_x']);origin[0]-=scroll;scene[0]-=scroll
|
||||
# INVARIANT: IPC never becomes native-applied without fresh paint and pointer corroboration.
|
||||
raw='current-layout IPC candidate; native paint and fresh pointer-local corroboration required, generation NOT OBSERVABLE'
|
||||
data=dict(geometry_status='IPC_PENDING_CANDIDATE; requires fresh paint/pointer after every mutation',owner=owner,workspace=ws,origin=origin,scene=scene,evidence=raw,snapshot=snap);self.save(name+'-geometry',data);return data
|
||||
def calibrate(self,name):
|
||||
state=self.owner(name)
|
||||
if self.spec['mode']=='mosaic' and self.spec['axis']==0:
|
||||
delta=int(state['origin'][0]+state['owner']['window_rect']['width']//2)
|
||||
if delta:self.scroll(name,delta)
|
||||
state=self.owner(name);need(crossing([*state['origin'],state['owner']['window_rect']['width'],state['owner']['window_rect']['height']],0,0),'owner real x0 crossing')
|
||||
return state
|
||||
def scroll(self,name,delta):
|
||||
self.debit('camera');self.cmd(f'[con_id={self.find(name)["id"]}] layout mosaic scroll {int(delta)}');time.sleep(.35)
|
||||
self.record('camera',owner=self.find(name)['id'],delta=int(delta))
|
||||
def client_cmd(self,name,cmd,marker=None):
|
||||
self.debit('configuration' if cmd.startswith('close') else 'mutation');before=len(self.log(name));self.record('client-request',name=name,command=cmd)
|
||||
self.proc[name].stdin.write(cmd+'\n');self.proc[name].stdin.flush()
|
||||
if marker:self.wait(lambda:marker in self.log(name)[before:])
|
||||
def close_client(self,name,command='close_owner',scenario=None):
|
||||
# WHY: exit0 alone cannot prove SHM release or an explicit normal close.
|
||||
# CONTRACT: retain each PID's validation, including retired same-name owners.
|
||||
# INVARIANT: a failed closure is durable evidence, never laundered by cleanup.
|
||||
p=self.proc[name];proof=dict(name=name,pid=p.pid,status='INCONCLUSIVE',failure=None)
|
||||
try:
|
||||
if p.poll() is None:self.client_cmd(name,command)
|
||||
code=p.wait(timeout=min(3,self.remaining()));raw=self.log(name)
|
||||
archive=self.run/f'{name}-closed-{p.pid}.log';archive.write_text(raw)
|
||||
proof.update(exit=code,log=str(archive),sha256=sha(archive),close_received='CLOSE_RECEIVED' in raw,shm_closure=re.findall(r'SHM_CLOSURE created=(\d+) released=(\d+)',raw),wayland_errors=re.findall(r'EXIT[^\n]*error=(\d+)',raw))
|
||||
validators.client_exit(raw,code,scenario or getattr(self,'closure_scenarios',{}).get(name,'observed'));self.wait(lambda:self.find(name) is None)
|
||||
proof['status']='PASS'
|
||||
except Exception as error:
|
||||
proof['failure']=f'{type(error).__name__}: {error}'
|
||||
proof.setdefault('exit',p.poll())
|
||||
raise
|
||||
finally:
|
||||
self.closed.append(proof);self.record('client-closed',**proof)
|
||||
|
||||
def cleanup(self,measurement_status,failure=None):
|
||||
terminal=None;rescue=False;closures=[]
|
||||
# Close test clients while their Wayland server still exists. Mechanical
|
||||
# rescue and terminal IPC can never substitute for scientific closure.
|
||||
for name in reversed(list(self.proc)):
|
||||
if name in ('compositor','pointer') or any(c['pid']==self.proc[name].pid for c in self.closed):continue
|
||||
if self.expired or time.monotonic()>=self.started+143:break
|
||||
try:
|
||||
self.close_client(name,'close_owner' if name=='owner' and self.spec['client']!='saved-client' and self.spec['id']!='C6' else 'close')
|
||||
except Exception:pass # close_client retains the explicit validation failure.
|
||||
# WHY/CONTRACT/INVARIANT: archived and already-exited clients count equally;
|
||||
# an empty list of newly closed processes must not erase prior failures.
|
||||
closures=[dict(c) for c in self.closed]
|
||||
tracked=[p.pid for n,p in self.proc.items() if n not in ('compositor','pointer')]
|
||||
closure_coverage=all(any(c['pid']==pid for c in closures) for pid in tracked)
|
||||
|
||||
if hasattr(self,'comp') and self.comp.poll() is None and hasattr(self,'sock') and self.sock.is_socket() and not self.expired and time.monotonic()<self.started+145:
|
||||
try:
|
||||
self.debit('configuration')
|
||||
p=self.command_process(['swaymsg','-s',str(self.sock),'exit'],timeout=2)
|
||||
terminal=dict(ipc_exit=p.returncode,stdout=p.stdout,stderr=p.stderr)
|
||||
self.save('terminal-ipc',terminal)
|
||||
except Exception as e:
|
||||
terminal=dict(ipc_error=str(e));self.save('terminal-ipc',terminal)
|
||||
if terminal is not None and 'ipc_exit' in terminal:
|
||||
try:self.comp.wait(timeout=min(1,self.remaining()))
|
||||
except Exception as e:
|
||||
terminal['wait_error']=str(e);self.save('terminal-ipc',terminal)
|
||||
codes=[dict(name=c['name'],pid=c['pid'],exit=c['exit']) for c in self.closed if not any(p.pid==c['pid'] for p in self.proc.values())]
|
||||
# One shared cleanup deadline prevents N owned clients multiplying waits.
|
||||
cleanup_end=min(self.started+146,time.monotonic()+2)
|
||||
if self.pointer and self.pointer.poll() is None and self.pointer.stdin and not self.pointer.stdin.closed:self.pointer.stdin.close()
|
||||
for name,p in reversed(list(self.proc.items())):
|
||||
if p.poll() is None:
|
||||
try:p.wait(timeout=max(.01,cleanup_end-time.monotonic()))
|
||||
except subprocess.TimeoutExpired:pass
|
||||
residual=[p for p in self.proc.values() if p.poll() is None]
|
||||
if residual:
|
||||
rescue=True
|
||||
for p in residual:
|
||||
try:os.killpg(p.pid,signal.SIGKILL)
|
||||
except ProcessLookupError:pass
|
||||
kill_end=min(self.started+147,time.monotonic()+1)
|
||||
for p in residual:p.wait(timeout=max(.01,kill_end-time.monotonic()))
|
||||
for name,p in self.proc.items():
|
||||
codes.append(dict(name=name,pid=p.pid,exit=p.returncode))
|
||||
if p.stdin and not p.stdin.closed:p.stdin.close()
|
||||
self.finished.set();
|
||||
if hasattr(self,'watch'):self.watch.join(timeout=1)
|
||||
for h in self.handles:h.close()
|
||||
for p in self.proc.values():
|
||||
try:os.killpg(p.pid,0)
|
||||
except ProcessLookupError:pass
|
||||
else:rescue=True;os.killpg(p.pid,signal.SIGKILL)
|
||||
reaped=all(not Path('/proc',str(p.pid)).exists() for p in self.proc.values());need(reaped,'owned process residual');shutil.rmtree(self.runtime)
|
||||
seconds=time.monotonic()-self.started
|
||||
protocol_errors=[n for n in self.proc if n not in ('compositor','pointer') and re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',self.log(n),re.I)]
|
||||
cleanup_pass=bool(closure_coverage and all(c['status']=='PASS' for c in closures) and not rescue and reaped and seconds<=150 and not self.expired and all(any(c['name']==n and c.get('expected_enomem') for c in closures) for n in protocol_errors) and all(p['exit']==0 or any(c['pid']==p['pid'] and c.get('expected_enomem') for c in closures) for p in codes))
|
||||
transport_pass=validators.terminal_transport(terminal,self.comp.returncode if hasattr(self,'comp') else None)
|
||||
final_status=measurement_status if failure is None and cleanup_pass and transport_pass else 'INCONCLUSIVE'
|
||||
self.save('outcome',dict(status=final_status,measurements_pass=measurement_status,architecture_verdict='INCONCLUSIVE',evidence_provenance=self.auth,untested_matrix_cells=['ATOMIC_GENERATION','COMPLETE_DAMAGE','NATIVE_BUFFER_IDENTITY','GPU_LIFETIME'],scientific_cleanup='PASS' if cleanup_pass else 'INCONCLUSIVE',runtime_exit=codes,measurements_status=measurement_status,failure=failure,terminal=terminal,helper_exits=getattr(self,'helper_exits',[]),explicit_client_closures=self.closed,mechanical_cleanup='PASS' if reaped and not rescue and not self.runtime.exists() else 'INCONCLUSIVE',client_closures=closures,processes=codes,rescue=rescue,reaped=reaped,runtime_removed=not self.runtime.exists(),seconds=seconds,watchdog=self.expired,protocol_errors=protocol_errors,cleanup_pass=cleanup_pass,transport_status='PASS' if transport_pass else 'INCONCLUSIVE'))
|
||||
self.ledger.complete(self.auth,dict(status=final_status,runner_failure=failure,terminal=terminal,processes=codes,scientific_cleanup=cleanup_pass,mechanical_rescue=rescue));return final_status
|
||||
15
tooling/probes/step20c-corec-v2-followup/graphics/freeze.py
Normal file
15
tooling/probes/step20c-corec-v2-followup/graphics/freeze.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Portable source-only freeze, external to immutable source; never admits runs."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from gate import canonical_lab,source_files,source_modes,durable,TICKET
|
||||
from plans import RUNS,LIMITS
|
||||
|
||||
def freeze(lab,author,output=None):
|
||||
lab=canonical_lab(lab);work=lab/'followup-work';work.mkdir(mode=0o700,exist_ok=True)
|
||||
result=dict(schema='native-closure-source-v1',ticket=TICKET,author=author,files=source_files(),modes=source_modes(),plans=RUNS,limits=LIMITS,runtime='NOT_RUN',independent_review='NOT_PROVEN')
|
||||
destination=Path(output) if output else work/'source-manifest.json'
|
||||
if destination.resolve()!=destination or destination.parent!=work:raise ValueError('freeze output must be canonical followup-work path')
|
||||
durable(destination,result,exclusive=True);return result
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('--lab',required=True);p.add_argument('--author',required=True);p.add_argument('--output');a=p.parse_args();freeze(a.lab,a.author,a.output)
|
||||
146
tooling/probes/step20c-corec-v2-followup/graphics/fullscreen.py
Normal file
146
tooling/probes/step20c-corec-v2-followup/graphics/fullscreen.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
"""EXPERIMENTAL comparable nonleaf fullscreen, whole-field predictions first."""
|
||||
import re,time
|
||||
from PIL import ImageDraw
|
||||
import cases,validators
|
||||
from engine import COLORS
|
||||
from geometry import need,crossing,solid_point,exact,count
|
||||
from ipc_predicates import ancestry
|
||||
from model import Provenance
|
||||
from predicates import geometry
|
||||
|
||||
|
||||
def group_candidate(state,peer_id,group_id,fullscreen=True,applied=None):
|
||||
# WHY: IPC serializes pending.fullscreen_mode, workspace marker is always1.
|
||||
# CONTRACT: never turn that field into a direct current-state observation.
|
||||
# INVARIANT: sampled applied qualification requires post-command configure /
|
||||
# ACK / MAP plus measured geometry; native current bit remains NOT_OBSERVABLE.
|
||||
path=ancestry(state['snapshot']['tree'],state['owner']['id'])
|
||||
rows=[n for n in path[:-1] if n.get('type')=='con' and n.get('id')==group_id]
|
||||
need(len(rows)==1,'actual nonleaf group absent');group=rows[0]
|
||||
need(group.get('fullscreen_mode',0)==int(fullscreen),'group fullscreen IPC candidate mismatch')
|
||||
need(group.get('layout')=='splith' and len(group.get('nodes',[]))==2,'exact horizontal two-child group')
|
||||
need({n['id'] for n in group['nodes']}=={state['owner']['id'],peer_id},'owner/peer must be direct children')
|
||||
need(not group.get('floating_nodes') and not any((n.get('floating') or '').endswith('on') for n in group['nodes']),'floating group child')
|
||||
if fullscreen:
|
||||
out=next(o for o in state['snapshot']['outputs'] if o['name']==state['workspace']['output'])['rect']
|
||||
need(group['rect']==out,'fullscreen group does not cover output')
|
||||
need(out['width']==800 and [n['rect']['width'] for n in group['nodes']]==[400,400],'fullscreen measured400/400 required')
|
||||
need(applied and applied.get('source')=='post-command-xdg-configure-ack-map' and applied.get('group_id')==group_id and applied.get('owner_id')==state['owner']['id'] and applied.get('workspace_id')==state['workspace']['id'],'pending-only ancestor is not sampled applied evidence')
|
||||
return group
|
||||
|
||||
|
||||
def applied_configure(raw,cut,owner_id,workspace_id,group_id):
|
||||
tail=raw[cut:]
|
||||
configs=re.findall(r'TOPLEVEL XDG_CONFIGURE serial=(\d+) ack',tail)
|
||||
need(configs and 'TOPLEVEL MAP ' in tail,'post-fullscreen configure/ACK/map absent')
|
||||
# Actual wire association and SHM mapping are independently parsed by grab().
|
||||
need(re.search(r'xdg_surface#\d+\.ack_configure\('+configs[-1]+r'\)',tail),'post-fullscreen actual ACK wire absent')
|
||||
return dict(source='post-command-xdg-configure-ack-map',group_id=group_id,owner_id=owner_id,workspace_id=workspace_id,serial=int(configs[-1]),native_current_bit='NOT_OBSERVABLE')
|
||||
|
||||
|
||||
def positioner_adjust(raw):
|
||||
values=[]
|
||||
for popup in ('POPUP_A','POPUP_B'):
|
||||
ids=re.search(rf'{popup} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',raw);need(ids is not None,'popup role absent')
|
||||
create=re.search(rf'xdg_surface#{ids[2]}\.get_popup\(new id xdg_popup#{ids[3]}, xdg_surface#\d+, xdg_positioner#(\d+)\)',raw);need(create is not None,'actual positioner absent')
|
||||
# IDs may be reused after destroy: bind the latest positioner declaration
|
||||
# and all setters before this exact get_popup request.
|
||||
before=raw[:create.start()];decl=list(re.finditer(rf'create_positioner\(new id xdg_positioner#{create[1]}\)',before));need(decl,'positioner creation wire absent')
|
||||
segment=before[decl[-1].start():]
|
||||
adjust=re.findall(rf'xdg_positioner#{create[1]}\.set_constraint_adjustment\((\d+)\)',segment)
|
||||
need(adjust==['0'],'different/unobserved positioner adjustment');values.append(int(adjust[0]))
|
||||
return values
|
||||
|
||||
|
||||
def comparable(pristine,core):
|
||||
for r in (pristine,core):
|
||||
need(r.get('fresh') and crossing(r['popup_A'],0,0) and crossing(r['popup_B'],0,0),'actual fresh crossing not exercised')
|
||||
need(r['child_widths']==[400,400] and r['applied']['source']=='post-command-xdg-configure-ack-map','pending/noncomparable group')
|
||||
keys=('client_sha256','client_source_sha256','protocol_sha256','positioner_adjust','roles','top_configure','child_rects','child_widths','group_rect','popup_A','popup_B','configured_A','configured_B','origin','outputs')
|
||||
need(all(pristine[k]==core[k] for k in keys),'pristine/core source/role/actual geometry differs')
|
||||
return dict(status='PARTIALLY_FEASIBLE',native_current_bit='NOT_OBSERVABLE',atomic_current_state='NOT_PROVEN')
|
||||
|
||||
|
||||
def expected_h2(baseline,g,output,frame,colors,active_roles=('POPUP_A','POPUP_B'),require_crossing=False):
|
||||
# WHY/CONTRACT/INVARIANT: predict whole RGB before acquisition. Standard /
|
||||
# fullscreen popup overflow is composed A then B, without a learned mask.
|
||||
need(frame=='logical' and output['scale']==1 and output['transform']=='normal','unsupported H2 frame')
|
||||
ox,oy,ow,oh=output['logical'];need(baseline.size==(ow,oh),'H2 baseline size')
|
||||
expected=baseline.copy();draw=ImageDraw.Draw(expected);parent=g['origin']
|
||||
for key,name in (('A','POPUP_A'),('B','POPUP_B')):
|
||||
cfg=g['configured_'+key];rect=g[key]
|
||||
need(len(cfg)==4 and cfg[2]>0 and cfg[3]>0 and rect==[parent[0]+cfg[0],parent[1]+cfg[1],cfg[2],cfg[3]],'actual configure/global rectangle differs')
|
||||
if require_crossing:need(crossing(rect,0,0),'actual popup noncrossing')
|
||||
l,t,r,b=max(rect[0],ox),max(rect[1],oy),min(rect[0]+rect[2],ox+ow),min(rect[1]+rect[3],oy+oh)
|
||||
if name in active_roles and l<r and t<b:draw.rectangle((l-ox,t-oy,r-ox-1,b-oy-1),fill=colors[name])
|
||||
parent=rect
|
||||
return expected
|
||||
|
||||
|
||||
def measured_pair(s,tag,baseline,g,active=('POPUP_A','POPUP_B'),cross=False):
|
||||
predicted=expected_h2(baseline,g,s.outputs[1],s.frames['HEADLESS-2'],COLORS,active,cross)
|
||||
s.record('whole-H2-prediction',tag=tag,rgb_sha256=__import__('hashlib').sha256(predicted.tobytes()).hexdigest(),geometry=g,roles=active)
|
||||
images=s.pair_capture(tag);exact(predicted,images[1]);return images
|
||||
|
||||
|
||||
def fresh_geometry(s,state):
|
||||
return geometry(s.log('owner'),state['origin'],Provenance(state['owner']['id'],state['workspace']['id'],'NOT_OBSERVABLE','fresh paint + pointer-local corroboration'))
|
||||
|
||||
|
||||
def input_target(s,images,g,target,key):
|
||||
for index,output in enumerate(s.outputs):
|
||||
try:point=solid_point(images[index],COLORS[target],output,s.frames[output['name']],g[key]);break
|
||||
except ValueError:continue
|
||||
else:raise ValueError('no freshly painted input interior')
|
||||
tails,buttons=s.click('owner',point);s.delivery('owner',g[key],point,tails,buttons,target)
|
||||
|
||||
|
||||
def prepare_group(s):
|
||||
# Native container_split ignores singleton H/V parents. Existing filler
|
||||
# guarantees siblings>1; peer is created on ws3, then explicitly inserted
|
||||
# in the marked nonleaf group. No post-failure empirical repair is allowed.
|
||||
s.debit('chain');s.launch_client('owner','fullscreen-client','fullscreen_cross')
|
||||
oid=s.find('owner')['id'];s.cmd(f'[con_id={oid}] focus');s.cmd(f'[con_id={oid}] split h')
|
||||
path=ancestry(s.snapshot('group-wrapper')['tree'],oid);group=path[-2]
|
||||
need(group.get('type')=='con' and [n['id'] for n in group.get('nodes',[])]==[oid],'split did not create owner wrapper')
|
||||
gid=group['id'];s.cmd(f'[con_id={gid}] mark --add smx-followup-group')
|
||||
s.cmd('workspace 3');s.launch_client('fullscreen-peer','static-reference');peer=s.find('fullscreen-peer')['id']
|
||||
s.cmd(f'[con_id={peer}] move container to mark smx-followup-group');s.cmd('workspace 1');s.cmd(f'[con_id={oid}] focus')
|
||||
snap=s.snapshot('fullscreen-before');path=ancestry(snap['tree'],oid)
|
||||
group=next(n for n in path if n.get('id')==gid)
|
||||
need({n['id'] for n in group.get('nodes',[])}=={oid,peer},'peer outside exact owner group')
|
||||
return oid,peer,gid
|
||||
|
||||
|
||||
def run(s):
|
||||
cases.init_reference(s,1)
|
||||
try:oid,peer,gid=prepare_group(s)
|
||||
except ValueError as exc:
|
||||
s.save('fullscreen-not-exercised',dict(status='NOT_EXERCISED',reason=str(exc)));return 'NOT_EXERCISED'
|
||||
baseline=cases.stable_reference(s);cut=len(s.log('owner'))
|
||||
s.cmd(f'[con_id={gid}] fullscreen enable');s.cmd(f'[con_id={oid}] focus')
|
||||
s.wait(lambda:'TOPLEVEL XDG_CONFIGURE ' in s.log('owner')[cut:] and 'TOPLEVEL MAP ' in s.log('owner')[cut:]);time.sleep(.2)
|
||||
state=s.owner('owner');applied=applied_configure(s.log('owner'),cut,oid,state['workspace']['id'],gid)
|
||||
group=group_candidate(state,peer,gid,applied=applied)
|
||||
top=s.capture('fullscreen-owner','HEADLESS-1');xy=solid_point(top,COLORS['TOPLEVEL'],s.outputs[0],'logical',[*state['origin'],420,320])
|
||||
ta,ba=s.click('owner',xy,'left',True);s.wait(lambda:'POPUP_A MAP ' in s.log('owner'))
|
||||
root=s.capture('fullscreen-A','HEADLESS-1');xyA=solid_point(root,COLORS['POPUP_A'],s.outputs[0],'logical')
|
||||
tb,bb=s.click('owner',xyA,'left',True);s.wait(lambda:'POPUP_B MAP ' in s.log('owner'));time.sleep(.2)
|
||||
p=validators.grab(s.log('owner'));adjust=positioner_adjust(s.log('owner'));g=fresh_geometry(s,state)
|
||||
s.delivery('owner',[*state['origin'],420,320],xy,ta,ba,'TOPLEVEL',p['grabs'][0]['serial']);s.delivery('owner',g['A'],xyA,tb,bb,'POPUP_A',p['grabs'][1]['serial'])
|
||||
if not all(crossing(g[k],0,0) for k in ('A','B')):
|
||||
s.save('fullscreen-not-exercised',dict(status='NOT_EXERCISED',geometry=g));return 'NOT_EXERCISED'
|
||||
images=measured_pair(s,'fullscreen-mapped',baseline,g,cross=True);input_target(s,images,g,'POPUP_B','B')
|
||||
m=__import__('json').loads(__import__('pathlib').Path(s.auth['manifest']).read_text())
|
||||
record=dict(client_sha256=s.auth['client_binary_sha256'],client_source_sha256=m['files']['graphics/clients/fullscreen-client.c'],protocol_sha256=m['files']['graphics/protocols/xdg-shell.xml'],positioner_adjust=adjust,fresh=True,applied=applied,roles=p['hierarchy'],top_configure=re.findall(r'TOPLEVEL ROLE_CONFIGURE w=(\d+) h=(\d+)',s.log('owner'))[-1],child_rects=[c['rect'] for c in group['nodes']],child_widths=[c['rect']['width'] for c in group['nodes']],group_rect=group['rect'],popup_A=g['A'],popup_B=g['B'],configured_A=g['configured_A'],configured_B=g['configured_B'],origin=g['origin'],outputs=s.outputs,owner_id=oid,group_id=gid,peer_id=peer,protocol=p)
|
||||
s.save('fullscreen-comparability',record)
|
||||
before=s.log('owner');start=time.monotonic_ns();s.client_cmd('owner','close_b','COMMAND close_b DONE')
|
||||
from lifecycle import explicit_child_close
|
||||
explicit_child_close(before,s.log('owner'),p['ids'],start,time.monotonic_ns());time.sleep(.15)
|
||||
state=s.owner('owner');group_candidate(state,peer,gid,applied=applied);g=fresh_geometry(s,state)
|
||||
images=measured_pair(s,'fullscreen-B-closed',baseline,g,('POPUP_A',));need(not any(count(im,COLORS['POPUP_B']) for im in images),'B residual');input_target(s,images,g,'POPUP_A','A')
|
||||
s.cmd(f'[con_id={gid}] fullscreen disable',True);s.cmd(f'[con_id={state["workspace"]["id"]}] layout splith',True);s.expected_layout='splith';time.sleep(.35)
|
||||
state=s.owner('owner');need(state['owner']['id']==oid,'owner changed on standard return');group_candidate(state,peer,gid,fullscreen=False)
|
||||
g=fresh_geometry(s,state);images=measured_pair(s,'fullscreen-standard-A',baseline,g,('POPUP_A',));input_target(s,images,g,'POPUP_A','A')
|
||||
s.close_client('owner',scenario='nested');final=s.pair_capture('fullscreen-final');exact(baseline,final[1])
|
||||
return 'PARTIALLY_FEASIBLE'
|
||||
212
tooling/probes/step20c-corec-v2-followup/graphics/gate.py
Normal file
212
tooling/probes/step20c-corec-v2-followup/graphics/gate.py
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
"""WHY: a new ticket cannot borrow historical reservations or source reviews.
|
||||
CONTRACT: freeze + independent review + publication precede every reservation.
|
||||
INVARIANT: no reset/refund; failed actions remain debited, bindings never repaired.
|
||||
"""
|
||||
import ctypes,fcntl,hashlib,json,os,re,resource,stat,subprocess,time,uuid
|
||||
from pathlib import Path
|
||||
from plans import RUNS,LIMITS
|
||||
TICKET='SWAYFX_MOSAIC_STEP20C_COREC_V2_FULLSCREEN_OOM_GLES_NATIVE_CLOSURE_BATCH_V1'
|
||||
WL_SHA='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
|
||||
SOURCE_ROOT=Path(__file__).resolve().parent.parent
|
||||
|
||||
def need(v,m):
|
||||
if not v:raise ValueError(m)
|
||||
def sha(p):return hashlib.sha256(Path(p).read_bytes()).hexdigest()
|
||||
def encoded(v):return (json.dumps(v,sort_keys=True,indent=2)+'\n').encode()
|
||||
def digest(v):return hashlib.sha256(encoded(v)).hexdigest()
|
||||
def canonical_lab(lab):
|
||||
p=Path(lab);need(p.is_absolute() and p.resolve()==p and p.parent==Path('/var/tmp'),'canonical direct /var/tmp LAB required')
|
||||
s=p.lstat();need(stat.S_ISDIR(s.st_mode) and s.st_uid==os.getuid() and stat.S_IMODE(s.st_mode)==0o700,'LAB mode/owner/type');return p
|
||||
|
||||
def clean_environment(lab):
|
||||
lab=canonical_lab(lab)
|
||||
for name in ('scratch','cache','data','pycache'):
|
||||
p=lab/name;need(p.resolve()==p and p.is_dir() and p.stat().st_uid==os.getuid(),'owned scratch/cache/data required')
|
||||
e={k:os.environ[k] for k in ('PATH','LANG','LC_ALL','TZ') if k in os.environ}
|
||||
e.update(TMPDIR=str(lab/'scratch'),TMP=str(lab/'scratch'),TEMP=str(lab/'scratch'),SMX_LAB=str(lab),XDG_CACHE_HOME=str(lab/'cache'),XDG_DATA_HOME=str(lab/'data'),PYTHONPYCACHEPREFIX=str(lab/'pycache'),CCACHE_DIR=str(lab/'cache/ccache'))
|
||||
return e
|
||||
|
||||
def disable_dump():
|
||||
resource.setrlimit(resource.RLIMIT_CORE,(0,0))
|
||||
need(ctypes.CDLL(None).prctl(4,0,0,0,0)==0,'PR_SET_DUMPABLE failed')
|
||||
|
||||
def secure_read(path):
|
||||
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
|
||||
try:
|
||||
s=os.fstat(fd);need(stat.S_ISREG(s.st_mode) and s.st_uid==os.getuid() and s.st_nlink==1 and not s.st_mode&0o077,'receipt/ledger owner/type/link/mode')
|
||||
need(s.st_size<=16*1024*1024,'receipt size bound')
|
||||
with os.fdopen(fd,'r',closefd=False) as f:return json.load(f)
|
||||
finally:os.close(fd)
|
||||
|
||||
def durable(path,value,exclusive=False):
|
||||
p=Path(path);need(p.parent.resolve()==p.parent and p.parent.is_dir(),'canonical output parent')
|
||||
if p.exists() or p.is_symlink():
|
||||
s=p.lstat();need(stat.S_ISREG(s.st_mode) and s.st_uid==os.getuid() and s.st_nlink==1 and not s.st_mode&0o077,'unsafe existing output')
|
||||
tmp=p.with_name(p.name+'.tmp-'+uuid.uuid4().hex)
|
||||
fd=os.open(tmp,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
try:
|
||||
with os.fdopen(fd,'wb') as f:f.write(encoded(value));f.flush();os.fsync(f.fileno())
|
||||
if exclusive:os.link(tmp,p);tmp.unlink()
|
||||
else:os.replace(tmp,p)
|
||||
d=os.open(p.parent,os.O_RDONLY|os.O_DIRECTORY)
|
||||
try:os.fsync(d)
|
||||
finally:os.close(d)
|
||||
finally:
|
||||
if tmp.exists():tmp.unlink()
|
||||
|
||||
def source_files(root=None):
|
||||
root=root or SOURCE_ROOT;result={}
|
||||
for p in sorted(root.rglob('*')):
|
||||
need(not p.is_symlink(),'source symlink forbidden')
|
||||
if p.is_file():
|
||||
rel=p.relative_to(root).as_posix();need('__pycache__' not in p.parts and p.suffix not in ('.pyc','.png','.o','.so','.a'),'generated source artifact')
|
||||
result[rel]=sha(p)
|
||||
need(result,'empty source kit');return result
|
||||
|
||||
def source_modes(root=None):
|
||||
root=root or SOURCE_ROOT
|
||||
files=source_files(root);result={}
|
||||
for rel in files:
|
||||
mode=stat.S_IMODE((root/rel).stat().st_mode)
|
||||
need(mode in (0o644,0o755),'source mode not portable Git 0644/0755')
|
||||
result[rel]=oct(mode)
|
||||
return result
|
||||
|
||||
def git_inventory(raw,prefix,files,modes):
|
||||
# WHY: identical blob bytes do not prove file type, mode, or full inventory.
|
||||
# CONTRACT/INVARIANT: exact Git subtree, canonical portable modes, blobs only.
|
||||
need(set(files)==set(modes),'manifest mode inventory')
|
||||
need(all(v in ('0o644','0o755') for v in modes.values()),'invalid manifest modes')
|
||||
prefix=prefix.encode()+b'/';seen={}
|
||||
need(not raw or raw.endswith(b'\0'),'truncated Git tree')
|
||||
for entry in raw.split(b'\0')[:-1]:
|
||||
meta,path=entry.split(b'\t',1);mode,kind,oid=meta.split(b' ')
|
||||
need(path.startswith(prefix),'Git tree wrong prefix')
|
||||
rel=path[len(prefix):].decode('utf-8',errors='strict')
|
||||
need(rel in files and rel not in seen,'Git tree added/duplicate path')
|
||||
need(kind==b'blob' and mode in (b'100644',b'100755'),'Git tree type/mode')
|
||||
need(mode=={'0o644':b'100644','0o755':b'100755'}[modes[rel]],'Git mode differs')
|
||||
need(re.fullmatch(b'[0-9a-f]{40}',oid),'Git object identity')
|
||||
seen[rel]=oid.decode()
|
||||
need(set(seen)==set(files),'Git tree missing path')
|
||||
return seen
|
||||
|
||||
def published_sources(top,commit,prefix,files,modes,env):
|
||||
tree=subprocess.run(['git','-C',str(top),'ls-tree','-rz','--full-tree',commit,'--',str(prefix)],capture_output=True,env=env,timeout=4)
|
||||
need(tree.returncode==0,'published Git tree unavailable')
|
||||
git_inventory(tree.stdout,str(prefix),files,modes)
|
||||
for rel,h in files.items():
|
||||
blob=subprocess.run(['git','-C',str(top),'show',f'{commit}:{prefix/rel}'],capture_output=True,env=env,timeout=4)
|
||||
need(blob.returncode==0 and hashlib.sha256(blob.stdout).hexdigest()==h,'commit does not contain frozen bytes')
|
||||
|
||||
|
||||
def _receipt(lab,path,expected):
|
||||
p=Path(path);need(p.resolve()==p and p.is_relative_to(lab),'runtime receipt outside new LAB')
|
||||
value=secure_read(p);need(sha(p)==expected,'receipt SHA mismatch');return value
|
||||
|
||||
def validate_review(lab,manifest,review,publication,rid,offline=False,check_git=True):
|
||||
lab=canonical_lab(lab);need(rid in RUNS,'historical/unknown run ID');need(RUNS[rid]['admission']!='NOT_ADMITTED' or rid=='GPU-G3','scenario NOT_ADMITTED')
|
||||
manifest,review,publication=map(Path,(manifest,review,publication))
|
||||
need(all(p.resolve()==p and p.is_relative_to(lab) for p in (manifest,review,publication)),'external admission receipt outside new LAB')
|
||||
m=secure_read(manifest);r=secure_read(review);p=secure_read(publication)
|
||||
need(m.get('ticket')==TICKET and m.get('schema')=='native-closure-source-v1' and m.get('plans')==RUNS and m.get('limits')==LIMITS,'manifest ticket/plans/schema/limits')
|
||||
need(m.get('files')==source_files() and m.get('modes')==source_modes(),'source changed/added/removed/mode after freeze')
|
||||
need(r.get('decision')=='ACCEPTED' and r.get('independent') is True and r.get('reviewer') and r['reviewer']!=m.get('author'),'independent review absent')
|
||||
need(r.get('manifest_sha256')==sha(manifest) and r.get('ticket')==TICKET,'review stale/ticket')
|
||||
case=r.get('runs',{}).get(rid,{})
|
||||
need(case.get('decision')=='ACCEPTED' and case.get('client_build_review')=='ACCEPTED','per-run client/source review absent')
|
||||
for gate in {'FS-P':('FULLSCREEN_COMPARABILITY_OFFLINE',),'FS-C':('FULLSCREEN_COMPARABILITY_OFFLINE',),'DY-C':('MIGRATION_REFOCUS_OFFLINE',),'OM-ROOT':('OOM_ROOT_NESTED_ORACLE_OFFLINE',),'OM-NESTED':('OOM_ROOT_NESTED_ORACLE_OFFLINE',),'GPU-G3':('B4',)}[rid]:
|
||||
proof=r.get('gates',{}).get(gate,{})
|
||||
need(proof.get('decision')=='ACCEPTED' and proof.get('manifest_sha256')==sha(manifest),'required gate not independently accepted')
|
||||
receipt=_receipt(lab,proof.get('receipt',''),proof.get('receipt_sha256'))
|
||||
need(receipt.get('status')=='PASS' and receipt.get('tests_run',0)>0 and receipt.get('failures')==0 and receipt.get('source_digest')==digest(m['files']),'superficial/stale offline gate')
|
||||
need(p.get('ticket')==TICKET and p.get('decision')=='VERIFIED' and p.get('offline_test_only') is offline,'publication namespace/ticket')
|
||||
commit=p.get('source_commit','');need(re.fullmatch('[0-9a-f]{40}',commit),'publication commit')
|
||||
need(p.get('manifest_sha256')==sha(manifest) and p.get('source_digest')==digest(m['files']),'publication manifest binding')
|
||||
need(p.get('origin_sha')==p.get('github_sha')==commit,'source kit unpublished')
|
||||
for name in ('origin','github'):
|
||||
obs=p.get('remote_observations',{}).get(name,{})
|
||||
need(obs.get('exit')==0 and obs.get('argv')==['git','ls-remote',name,'refs/heads/main'] and obs.get('stdout','').split()==[commit,'refs/heads/main'],'direct remote observation absent')
|
||||
if not offline and check_git:
|
||||
# Git bytes independently bind the remote commit to this portable kit.
|
||||
repo=SOURCE_ROOT
|
||||
root=subprocess.run(['git','-C',str(repo),'rev-parse','--show-toplevel'],capture_output=True,text=True,env=clean_environment(lab),timeout=4)
|
||||
need(root.returncode==0,'published Git repository unavailable');top=Path(root.stdout.strip());prefix=SOURCE_ROOT.relative_to(top)
|
||||
published_sources(top,commit,prefix,m['files'],m['modes'],clean_environment(lab))
|
||||
runtime=_receipt(lab,r.get('runtime_manifest',''),r.get('runtime_manifest_sha256'))
|
||||
need(runtime.get('ticket')==TICKET,'runtime identity ticket')
|
||||
identity=runtime['identities'][RUNS[rid]['binary']]
|
||||
need(sha(identity['binary'])==identity['binary_sha256'],'binary changed')
|
||||
if not offline and check_git:
|
||||
elf=subprocess.run(['readelf','-n',identity['binary']],capture_output=True,text=True,env=clean_environment(lab),timeout=4)
|
||||
need(elf.returncode==0 and re.findall(r'Build ID: ([0-9a-f]+)',elf.stdout)==[identity.get('build_id')],'binary BuildID mismatch')
|
||||
need(sha(identity['wlroots'])==identity['wlroots_sha256']==WL_SHA,'wlroots changed')
|
||||
need(identity.get('build_review')=='ACCEPTED' and identity.get('source_qualification')=='ACCEPTED','identity/source build unreviewed')
|
||||
build=_receipt(lab,case.get('client_build_receipt',''),case.get('client_build_receipt_sha256'))
|
||||
need(build.get('status')=='PASS' and build.get('runtime')=='NOT_RUN','client compile receipt')
|
||||
for name in ('pointer-daemon','static-reference',RUNS[rid]['client']):
|
||||
binary=lab/'followup-work/client-build'/name
|
||||
need(sha(binary)==build['generated'][name] and build['inputs']['clients/'+name+'.c']==m['files']['graphics/clients/'+name+'.c'],'client generated/source mismatch')
|
||||
need(build['inputs']['protocols/xdg-shell.xml']==m['files']['graphics/protocols/xdg-shell.xml'] and build['inputs']['protocols/wlr-virtual-pointer-unstable-v1.xml']==m['files']['graphics/protocols/wlr-virtual-pointer-unstable-v1.xml'],'protocol source binding')
|
||||
if rid=='GPU-G3':
|
||||
need(all(identity['binary_sha256']!=other.get('binary_sha256') for name,other in runtime['identities'].items() if name!='gpu'),'GPU new distinct ELF required')
|
||||
from gpu_handler import validate_binding
|
||||
validate_binding(lab,case,identity,sha(manifest))
|
||||
if rid in ('OM-ROOT','OM-NESTED'):
|
||||
need(case.get('oom_source_review')=='ACCEPTED' and case.get('variant_identity')=='COREC_V2_NATIVE_OOM_DIAGNOSTIC' and case.get('source_manifest_sha256')==sha(manifest),'OOM native/source variant review absent')
|
||||
if rid=='OM-NESTED' and case.get('root_dependency_override'):
|
||||
v=case['root_dependency_override'];need(v.get('independent') is True and v.get('reviewer')!=m['author'] and v.get('decision')=='ACCEPTED' and v.get('manifest_sha256')==sha(manifest) and v.get('rationale') and v.get('no_common_native_fault') is True,'invalid independent nested dependency review')
|
||||
return dict(identity,prospective=case,manifest=str(manifest),review=str(review),publication=str(publication),manifest_sha256=sha(manifest),review_sha256=sha(review),publication_sha256=sha(publication),runtime_manifest_sha256=r['runtime_manifest_sha256'],source_digest=digest(m['files']),client_binary_sha256=build['generated'][RUNS[rid]['client']])
|
||||
|
||||
class Ledger:
|
||||
def __init__(self,lab,path=None,offline=False):
|
||||
self.lab=canonical_lab(lab);self.offline=offline;self.path=Path(path) if path else self.lab/'native-closure-budget.json'
|
||||
need(self.path.parent.resolve()==self.path.parent and self.path.parent.is_relative_to(self.lab),'ledger outside new LAB/canonical parent')
|
||||
need(not self.path.is_symlink(),'ledger symlink')
|
||||
if offline:need('OFFLINE_TEST_ONLY' in self.path.parts,'offline namespace')
|
||||
else:need(self.path==self.lab/'native-closure-budget.json','real ledger fixed new path')
|
||||
self.lock=self.path.with_name(self.path.name+'.lock')
|
||||
def transaction(self,fn,initialize=False):
|
||||
fd=os.open(self.lock,os.O_RDWR|os.O_CREAT|os.O_NOFOLLOW,0o600)
|
||||
try:
|
||||
s=os.fstat(fd);need(stat.S_ISREG(s.st_mode) and s.st_uid==os.getuid() and s.st_nlink==1 and not s.st_mode&0o077,'lock owner/type/link/mode')
|
||||
fcntl.flock(fd,fcntl.LOCK_EX);need(self.lock.lstat().st_ino==s.st_ino,'lock inode changed')
|
||||
if initialize:need(not self.path.exists(),'ledger already initialized');d=None
|
||||
else:
|
||||
d=secure_read(self.path);need(d.get('ticket')==TICKET and d.get('lab')==str(self.lab) and d.get('uid')==os.getuid(),'old ticket/LAB/UID')
|
||||
need(d.get('namespace')==('OFFLINE_TEST_ONLY' if self.offline else 'REAL') and d.get('limits')==LIMITS,'namespace/limits')
|
||||
result,new=fn(d);durable(self.path,new,exclusive=initialize);return result
|
||||
finally:os.close(fd)
|
||||
def initialize(self):
|
||||
return self.transaction(lambda _:(None,dict(ticket=TICKET,lab=str(self.lab),uid=os.getuid(),namespace='OFFLINE_TEST_ONLY' if self.offline else 'REAL',limits=LIMITS,runs=[])),True)
|
||||
def admit(self,rid,manifest,review,publication):
|
||||
auth=validate_review(self.lab,manifest,review,publication,rid,offline=self.offline)
|
||||
def reserve(d):
|
||||
spec=RUNS[rid];need(not any(x['id']==rid for x in d['runs']),'consumed run; no retry')
|
||||
if rid=='OM-NESTED':
|
||||
roots=[x for x in d['runs'] if x['id']=='OM-ROOT'];good=len(roots)==1 and roots[0]['status']=='COMPLETED' and roots[0].get('outcome',{}).get('status')=='PARTIALLY_FEASIBLE' and roots[0].get('outcome',{}).get('scientific_cleanup') is True
|
||||
need(good or auth['prospective'].get('root_dependency_override'),'root unresolved; independent dependency review required')
|
||||
if rid=='FS-C':
|
||||
controls=[x for x in d['runs'] if x['id']=='FS-P'];need(len(controls)==1 and controls[0]['status']=='COMPLETED' and controls[0].get('outcome',{}).get('status')=='PARTIALLY_FEASIBLE','fullscreen control incomplete')
|
||||
need(len(d['runs'])<LIMITS['runs'] and sum(x['capture_limit'] for x in d['runs'])+spec['capture_limit']<=LIMITS['captures'],'global cap')
|
||||
token=uuid.uuid4().hex;r=dict(id=rid,token=token,started_ns=time.monotonic_ns(),capture_limit=spec['capture_limit'],action_limits=spec['action_limits'],actions={},status='RESERVED',bindings=auth)
|
||||
d['runs'].append(r);return dict(auth,token=token,run_id=rid,lab=str(self.lab),offline=self.offline),d
|
||||
return self.transaction(reserve)
|
||||
def action_debit(self,auth,*kinds):
|
||||
fresh=validate_review(self.lab,auth['manifest'],auth['review'],auth['publication'],auth['run_id'],offline=self.offline,check_git=False)
|
||||
def update(d):
|
||||
rows=[x for x in d['runs'] if x['id']==auth['run_id']];need(len(rows)==1,'missing reservation');r=rows[0]
|
||||
need(r['token']==auth['token'] and r['bindings']==fresh and r['status']=='RESERVED','historical token/stale binding/completed')
|
||||
need(0<=time.monotonic_ns()-r['started_ns']<150_000_000_000,'duration exhausted')
|
||||
actions=dict(r['actions'])
|
||||
for kind in kinds:
|
||||
limit=r['capture_limit'] if kind=='capture' else r['action_limits'].get(kind,0)
|
||||
need(actions.get(kind,0)<limit,'local action exhausted: '+kind);actions[kind]=actions.get(kind,0)+1
|
||||
total=sum(x['actions'].get('capture',0) for x in d['runs'])+actions.get('capture',0)-r['actions'].get('capture',0)
|
||||
need(total<=LIMITS['captures'],'global capture cap');r['actions']=actions;return None,d
|
||||
return self.transaction(update)
|
||||
def complete(self,auth,outcome):
|
||||
def finish(d):
|
||||
rows=[x for x in d['runs'] if x['id']==auth['run_id']];need(len(rows)==1,'missing reservation');r=rows[0]
|
||||
need(r['token']==auth['token'] and r['status']=='RESERVED','historical token/completed');r.update(status='COMPLETED',finished_ns=time.monotonic_ns(),outcome=outcome);return None,d
|
||||
self.transaction(finish)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
"""Explicit logical global, IPC assignment, physical mode and PNG-frame adapters."""
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
from PIL import Image
|
||||
|
||||
class Ambiguous(ValueError):pass
|
||||
def need(ok,msg):
|
||||
if not ok:raise Ambiguous(msg)
|
||||
def box_end(r):return [r[0]+r[2],r[1]+r[3]]
|
||||
def contains(r,p):return r[0]<=p[0]<r[0]+r[2] and r[1]<=p[1]<r[1]+r[3]
|
||||
def crossing(r,axis,boundary):return r[axis]<boundary<r[axis]+r[axis+2]
|
||||
def intersection(a,b):
|
||||
x=max(a[0],b[0]);y=max(a[1],b[1]);return [x,y,max(0,min(a[0]+a[2],b[0]+b[2])-x),max(0,min(a[1]+a[3],b[1]+b[3])-y)]
|
||||
def assigned_origin(owner):
|
||||
# IPC rect already excludes top decoration (ipc-json.c899–906).
|
||||
return [owner['rect']['x']+owner['window_rect']['x'],owner['rect']['y']+owner['window_rect']['y']]
|
||||
def exterior_ipc(owner):
|
||||
r=owner['rect'];d=owner['deco_rect']['height'];return [r['x'],r['y']-d,r['width'],r['height']+d]
|
||||
MARKERS={'TL':(255,32,32),'TR':(32,255,32),'BL':(32,32,255),'BR':(255,255,32),'C':(192,32,255)}
|
||||
def marker_frame(image,scale,rotated):
|
||||
positions={k:[] for k in MARKERS};lookup={v:k for k,v in MARKERS.items()}
|
||||
for y in range(image.height):
|
||||
for x in range(image.width):
|
||||
k=lookup.get(image.getpixel((x,y)))
|
||||
if k:positions[k].append((x,y))
|
||||
need(all(len(v)>=100 for v in positions.values()),'marker color missing')
|
||||
c={k:[sum(p[i] for p in v)/len(v) for i in (0,1)] for k,v in positions.items()}
|
||||
logical=image.size==(round(800*scale),round(600*scale)) and c['TL'][0]<c['TR'][0] and c['TL'][1]<c['BL'][1]
|
||||
physical=rotated and image.size==(round(600*scale),round(800*scale)) and c['TL'][0]>c['BL'][0] and c['TL'][1]<c['TR'][1]
|
||||
need(logical or physical,'PNG orientation unestablished')
|
||||
return dict(frame='logical' if logical else 'physical90',centers=c,counts={k:len(v) for k,v in positions.items()},size=list(image.size))
|
||||
def png_global(px,output,frame):
|
||||
scale=output['scale'];x,y=px;ox,oy,w,h=output['logical']
|
||||
if frame=='physical90':x,y=y,round(h*scale)-1-x
|
||||
need(frame in ('logical','physical90'),'unknown PNG frame')
|
||||
# Sample integer logical centers only; resample at the chosen center later.
|
||||
return [ox+int(x/scale),oy+int(y/scale)]
|
||||
def global_png(p,output,frame):
|
||||
x=(p[0]-output['logical'][0])*output['scale'];y=(p[1]-output['logical'][1])*output['scale']
|
||||
if frame=='physical90':x,y=output['logical'][3]*output['scale']-1-y,x
|
||||
return [round(x),round(y)]
|
||||
def solid_point(image,color,output,frame,rectangle=None,margin=3):
|
||||
for y in range(margin,image.height-margin,3):
|
||||
for x in range(margin,image.width-margin,3):
|
||||
if image.getpixel((x,y))!=color:continue
|
||||
p=png_global((x,y),output,frame)
|
||||
if rectangle is not None and not contains(rectangle,p):continue
|
||||
pp=global_png(p,output,frame)
|
||||
if not (margin<=pp[0]<image.width-margin and margin<=pp[1]<image.height-margin):continue
|
||||
if all(image.getpixel((xx,yy))==color for yy in range(pp[1]-margin,pp[1]+margin+1) for xx in range(pp[0]-margin,pp[0]+margin+1)):
|
||||
return p
|
||||
raise Ambiguous('no measured solid logical interior')
|
||||
def count(image,color):return sum(v==color for v in image.get_flattened_data())
|
||||
def exact(a,b):need(a.size==b.size and a.tobytes()==b.tobytes(),'whole RGB differs');return dict(equal=True,pixels=a.width*a.height)
|
||||
def expected_outputs(outputs,spec):
|
||||
active=[o for o in outputs if o['active']];need(len(active)==2,'active output count')
|
||||
for want in spec:
|
||||
rows=[o for o in active if o['name']==want['name']];need(len(rows)==1,'output identity')
|
||||
o=rows[0];need([o['rect'][k] for k in ('x','y','width','height')]==want['logical'],'logical output geometry')
|
||||
need(o['scale']==want['scale'] and o['transform']==want['transform'],'scale/IPC transform string')
|
||||
need([o['current_mode'][k] for k in ('width','height')]==want['mode'],'physical mode')
|
||||
return active
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
"""New G3 wiring only. Import never arms a hook, reserves or launches a client."""
|
||||
import importlib,json,os,re,stat,sys
|
||||
from pathlib import Path
|
||||
import gate
|
||||
from engine import Session
|
||||
from gate import need,sha
|
||||
|
||||
|
||||
def validate_binding(lab,case,identity,manifest_sha):
|
||||
# WHY: G2 qualification cannot authorize a new native G3 ELF or source.
|
||||
# CONTRACT: independent source/capability/build receipts bind final bytes.
|
||||
# INVARIANT: no ACCEPTED status is manufactured by this adapter.
|
||||
need(case.get('gpu_source_review')=='ACCEPTED' and case.get('gl_observability_preflight')=='ACCEPTED' and case.get('build_review')=='ACCEPTED' and case.get('native_gl_status')=='NOT_RUN','GPU source/capability/build review absent')
|
||||
for name in ('gpu_source_review','gl_observability_preflight','build_review'):
|
||||
receipt=gate._receipt(lab,case.get(name+'_receipt',''),case.get(name+'_receipt_sha256'))
|
||||
need(receipt.get('decision')=='ACCEPTED' and receipt.get('independent') is True and receipt.get('manifest_sha256')==manifest_sha,'GPU independent exact-byte receipt missing')
|
||||
source=gate._receipt(lab,case.get('gpu_source_manifest',''),case.get('gpu_source_manifest_sha256'))
|
||||
need(source.get('schema')=='G3-relative-source-freeze-v1' and source.get('files'),'GPU new relative freeze')
|
||||
root=gate.SOURCE_ROOT/'gpu'
|
||||
actual={str(p.relative_to(root)) for p in root.rglob('*') if p.is_file()}
|
||||
need(actual==set(source['files']),'GPU source added/removed')
|
||||
for rel,record in source['files'].items():
|
||||
p=root/rel;need(not Path(rel).is_absolute() and p.resolve()==p and p.is_relative_to(root),'GPU source path')
|
||||
need(sha(p)==record['sha256'] and oct(stat.S_IMODE(p.stat().st_mode))==record['mode'],'GPU frozen source changed')
|
||||
binding=case.get('binding',{});client=Path(binding.get('client',''))
|
||||
need(client.resolve()==client and client.is_relative_to(lab) and sha(client)==binding.get('client_sha256'),'GPU newly built client bytes')
|
||||
need(binding.get('binary')==identity['binary'] and binding.get('source_manifest_sha256')==manifest_sha,'GPU binary/source binding')
|
||||
build=gate._receipt(lab,case.get('gpu_client_build_receipt',''),case.get('gpu_client_build_receipt_sha256'))
|
||||
need(build.get('status')=='PASS' and build.get('source_sha256')==sha(root/'clients-g2.c') and build.get('client_sha256')==sha(client),'GPU generated client/source receipt')
|
||||
gatefile=Path(case.get('gate_file',''));native=gate._receipt(lab,gatefile,case.get('gate_file_sha256'))
|
||||
acquisition=lab/'gpu-work/acquisition'
|
||||
need(acquisition.resolve()==acquisition and acquisition.is_dir() and acquisition.stat().st_uid==os.getuid() and acquisition.stat().st_mode&0o777==0o700,'GPU acquisition root/mode')
|
||||
need(native.get('source_review')=='ACCEPTED' and native.get('gl_observability')=='PASS' and native.get('runtime_authorized') is True and native.get('diagnostic')=='GPU-G3' and native.get('acquisition_root')==str(acquisition) and native.get('binary_sha256')==identity['binary_sha256'],'GPU native external authorization')
|
||||
need(binding.get('gate_file')==str(gatefile) and binding.get('reviewed_scene_sha256')==case.get('reviewed_scene_sha256') and re.fullmatch('[0-9a-f]{64}',case.get('reviewed_scene_sha256','')),'GPU final scene/binding missing')
|
||||
return binding
|
||||
|
||||
|
||||
def modules(lab):
|
||||
path=gate.SOURCE_ROOT/'gpu';previous=os.environ.get('MOSAIC_G3_DIR');os.environ['MOSAIC_G3_DIR']=str(lab/'gpu-work/acquisition');sys.path.insert(0,str(path))
|
||||
try:
|
||||
protocol=importlib.import_module('protocol');scenario=importlib.import_module('scenario');validator=importlib.import_module('validate_source')
|
||||
need(all(Path(m.__file__).resolve()==path/(name+'.py') for m,name in ((protocol,'protocol'),(scenario,'scenario'),(validator,'validate_source'))),'GPU module origin mismatch')
|
||||
need(protocol.ROOT==lab/'gpu-work/acquisition','GPU imported wrong acquisition root')
|
||||
return scenario
|
||||
finally:
|
||||
sys.path.remove(str(path))
|
||||
if previous is None:os.environ.pop('MOSAIC_G3_DIR',None)
|
||||
else:os.environ['MOSAIC_G3_DIR']=previous
|
||||
|
||||
class CompositorEnvironment:
|
||||
def __init__(self,session):self.session=session
|
||||
def update(self,**fields):
|
||||
need(set(fields)=={'MOSAIC_G3_DIR','MOSAIC_G3_NONCE','WLR_RENDERER'},'GPU compositor-only environment scope')
|
||||
need(fields['MOSAIC_G3_DIR']==str(self.session.lab/'gpu-work/acquisition') and re.fullmatch('[0-9a-f]{64}',fields['MOSAIC_G3_NONCE']) and fields['WLR_RENDERER']=='gles2','GPU private acquisition environment')
|
||||
self.session.compositor_environment=fields
|
||||
|
||||
class AdapterSession:
|
||||
def __init__(self,session):
|
||||
need(session.spec['id']=='GPU-G3' and session.spec['binary']=='gpu','real GPU-G3 Session identity')
|
||||
self.session=session;self.spec=session.spec;self.env=CompositorEnvironment(session)
|
||||
def __getattr__(self,name):return getattr(self.session,name)
|
||||
def debit(self,*kinds):
|
||||
need(kinds==('mutation',),'GPU adapter unexpected direct debit');self.session.debit('mutation','hook')
|
||||
def spawn(self,name,argv,debug=False):
|
||||
need(name in ('g2-reference','g2-background','g2-glass'),'GPU client role');return self.session.spawn(name,argv,True)
|
||||
|
||||
class GPUSceneSession(Session):
|
||||
def close_client(self,name,command='close',scenario=None):
|
||||
need(name in ('g2-reference','g2-background','g2-glass') and command=='close','GPU scientific close role')
|
||||
try:
|
||||
super().close_client(name,command)
|
||||
raw=self.log(name);proof=self.closed[-1]
|
||||
mode={'g2-reference':'reference','g2-background':'background','g2-glass':'glass'}[name]
|
||||
role='zwlr_layer_surface_v1' if mode=='background' else 'xdg_toplevel'
|
||||
need(re.search(rf'EXIT ns=\d+ mode={mode} error=0 commits=\d+',raw),'GPU exact client exit role')
|
||||
need(re.search(rf'{role}#\d+\.destroy\(\)',raw) and re.search(r'wl_surface#\d+\.destroy\(\)',raw),'GPU native role wire destruction')
|
||||
proof.update(role=role,scientific_client=True,meaning='actual layer closure' if mode=='background' else 'actual GPU XDG closure')
|
||||
except Exception as exc:
|
||||
if self.closed and self.closed[-1]['pid']==self.proc[name].pid:self.closed[-1].update(status='INCONCLUSIVE',failure=str(exc))
|
||||
raise
|
||||
|
||||
def run(s):
|
||||
case=s.auth['prospective'];binding=validate_binding(s.lab,case,s.auth,s.auth['manifest_sha256'])
|
||||
result=modules(s.lab).run(AdapterSession(s),dict(binding,nonce=s.auth['token']*2,G3_ledger_reserved=True))
|
||||
need(result['native_source_A_Areturn_exact'] and result['whole_H1_A_Areturn']['equal'] and all(x['equal'] for x in result['whole_H2_A_B_Areturn']),'GPU measured return mismatch')
|
||||
return 'PARTIALLY_FEASIBLE'
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
"""Pure tree identity helpers only; IPC geometry is pending candidate."""
|
||||
def nodes(node):
|
||||
yield node
|
||||
for child in node.get('nodes', []) + node.get('floating_nodes', []):
|
||||
yield from nodes(child)
|
||||
|
||||
|
||||
def workspace(tree, name):
|
||||
rows = [n for n in nodes(tree) if n.get('type') == 'workspace' and n.get('name') == name]
|
||||
assert len(rows) == 1, 'workspace identity'
|
||||
return rows[0]
|
||||
|
||||
|
||||
def ancestry(tree, con_id, path=()):
|
||||
if tree.get('id') == con_id:
|
||||
return path + (tree,)
|
||||
for child in tree.get('nodes', []) + tree.get('floating_nodes', []):
|
||||
found = ancestry(child, con_id, path + (tree,))
|
||||
if found:
|
||||
return found
|
||||
return ()
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
"""Pure E2 input/lifecycle predicates shared with offline negative tests."""
|
||||
import re
|
||||
from model import require
|
||||
from predicates import COLORS, paint, equal, pair, point
|
||||
|
||||
|
||||
def opening_pair(lines,target,serial):
|
||||
actual=[]
|
||||
for line in lines:
|
||||
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line)
|
||||
require(m is not None,'opening-button-format');actual.append([m[1],*map(int,m.groups()[1:])])
|
||||
require(len(actual)==2 and all(e[0]==target for e in actual) and
|
||||
[e[2:] for e in actual]==[[272,1],[272,0]] and actual[0][1]==serial and
|
||||
0<actual[0][1]<actual[1][1]<=0xffffffff,'real-left-opening-pair')
|
||||
return actual
|
||||
|
||||
|
||||
def explicit_child_close(before,after,ids,request_ns,done_ns):
|
||||
"""Client destroy must follow a recorded explicit request; no popup_done."""
|
||||
require(type(request_ns) is int and request_ns<done_ns,'explicit-close-request-order')
|
||||
require(after.startswith(before),'append-only-client-protocol')
|
||||
tail=after[len(before):]
|
||||
require('POPUP_DONE ' not in after,'close-must-not-be-dismissal')
|
||||
b=ids['POPUP_B'];a=ids['POPUP_A']
|
||||
destroy=f'POPUP_B DESTROY role={b[2]} xdg={b[1]} wl={b[0]}'
|
||||
require(tail.count(destroy)==1 and tail.count('COMMAND close_b DONE')==1 and
|
||||
tail.index(destroy)<tail.index('COMMAND close_b DONE'),'explicit-child-destroy-DONE')
|
||||
for obj,stem in [(b[2],'xdg_popup'),(b[1],'xdg_surface'),(b[0],'wl_surface')]:
|
||||
require(tail.count(f'{stem}#{obj}.destroy()')==1,'child-protocol-destroy')
|
||||
require(after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1 and
|
||||
'POPUP_A DESTROY ' not in after and 'TOPLEVEL DESTROY' not in after,'same-A-remains-mapped')
|
||||
return dict(status='PASS',request_ns=request_ns,done_ns=done_ns,B=b,A=a,
|
||||
meaning='explicit client close, no compositor popup_done')
|
||||
|
||||
|
||||
def a_survival(image,before,after,ids,buttons,global_point,rect):
|
||||
a=ids['POPUP_A']
|
||||
require(paint(image,'POPUP_B')['count']==0 and paint(image,'POPUP_A')['count']>0,'child-zero-residual-A-painted')
|
||||
require(before.startswith('') and after.startswith(before),'A-input-protocol-appended')
|
||||
require('POPUP_A DESTROY ' not in after and 'POPUP_A POPUP_DONE ' not in after and
|
||||
after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1,'same-A-live-after-input')
|
||||
require(rect[0]<=global_point[0]<rect[0]+rect[2] and rect[1]<=global_point[1]<rect[1]+rect[3], 'A-recomputed-point-bounds')
|
||||
return dict(status='PASS',pair=pair(buttons,'POPUP_A'),ids=a,point=global_point)
|
||||
|
||||
|
||||
def normal_owner_close(before,after,ids,code):
|
||||
require(code==0 and after.startswith(before),'normal-client-exit')
|
||||
tail=after[len(before):]
|
||||
require(tail.count('COMMAND close_owner')==1 and tail.count('TOPLEVEL DESTROY')==1 and
|
||||
'POPUP_DONE ' not in tail,'normal-owner-close-command')
|
||||
a=ids['POPUP_A'];top=ids['TOPLEVEL']
|
||||
require(tail.count(f'POPUP_A DESTROY role={a[2]} xdg={a[1]} wl={a[0]}')==1 and
|
||||
tail.index('COMMAND close_owner')<tail.index('POPUP_A DESTROY ')<tail.index('TOPLEVEL DESTROY'),
|
||||
'normal-owner-child-first-destruction')
|
||||
for obj,stem in [(a[2],'xdg_popup'),(a[1],'xdg_surface'),(a[0],'wl_surface'),
|
||||
(top[2],'xdg_toplevel'),(top[1],'xdg_surface'),(top[0],'wl_surface')]:
|
||||
require(tail.count(f'{stem}#{obj}.destroy()')==1,'owner-protocol-destroy')
|
||||
return dict(status='PASS',client_exit=code,ids=ids)
|
||||
|
||||
|
||||
def final_restoration(baseline,final):
|
||||
cmp=equal(baseline,final)
|
||||
require(cmp['equal'] and all(paint(final,n)['count']==0 for n in ('POPUP_A','POPUP_B')),'whole-H2-final-exact')
|
||||
return dict(status='PASS',comparison=cmp)
|
||||
194
tooling/probes/step20c-corec-v2-followup/graphics/model.py
Normal file
194
tooling/probes/step20c-corec-v2-followup/graphics/model.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
"""EXPERIMENTAL typed half-open geometry. Pure: no processes or sockets.
|
||||
|
||||
Source contracts are recorded in the offline gate, not inferred from RGB.
|
||||
Every conversion is explicit and keeps assignment/owner/generation provenance.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
class Invalid(ValueError):
|
||||
pass
|
||||
|
||||
class Space(str, Enum):
|
||||
NATIVE = 'NativeSHM'
|
||||
CONFIGURE = 'XDGConfigure'
|
||||
CONTENT = 'AppliedContent'
|
||||
GLOBAL = 'SceneGlobal'
|
||||
POPUP = 'PopupLocal'
|
||||
VIEWPORT = 'GlobalViewport'
|
||||
PNG = 'OutputPNG'
|
||||
|
||||
|
||||
def require(value, contract):
|
||||
if not value:
|
||||
raise Invalid(contract)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Provenance:
|
||||
owner: int
|
||||
workspace: int
|
||||
generation: str
|
||||
evidence: str
|
||||
state: str = 'applied'
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Rect:
|
||||
space: Space
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
provenance: Provenance
|
||||
|
||||
def __post_init__(self):
|
||||
require(isinstance(self.space, Space), 'tag-required')
|
||||
require(all(type(v) is int for v in (self.x, self.y, self.width, self.height)), 'integer-geometry')
|
||||
require(self.width >= 0 and self.height >= 0, 'nonnegative-size')
|
||||
require(bool(self.provenance.evidence), 'evidence-required')
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
return (self.x + self.width, self.y + self.height)
|
||||
|
||||
def contains(self, xy):
|
||||
return self.x <= xy[0] < self.end[0] and self.y <= xy[1] < self.end[1]
|
||||
|
||||
def values(self):
|
||||
return [self.x, self.y, self.width, self.height]
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Native:
|
||||
rectangle: Rect
|
||||
buffer_id: int
|
||||
stride: int
|
||||
format: int
|
||||
commit_serial: int
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Applied:
|
||||
scene: Rect
|
||||
window: Rect
|
||||
configured: Rect
|
||||
native: Native
|
||||
border_left: int
|
||||
border_top: int
|
||||
titlebar: int
|
||||
configure_ack: int
|
||||
view_offset: tuple = (0, 0)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Output:
|
||||
name: str
|
||||
rectangle: Rect
|
||||
scale: float
|
||||
transform: str
|
||||
|
||||
|
||||
def same(a, b):
|
||||
require(a.owner == b.owner, 'owner-identity')
|
||||
require(a.workspace == b.workspace, 'workspace-identity')
|
||||
require(a.generation == b.generation, 'applied-generation')
|
||||
require(a.state == b.state == 'applied', 'pending-not-applied')
|
||||
|
||||
|
||||
def content(applied):
|
||||
s, w, c, n = applied.scene, applied.window, applied.configured, applied.native
|
||||
require(s.space is Space.GLOBAL and w.space is Space.CONTENT and c.space is Space.CONFIGURE,
|
||||
'assignment-spaces')
|
||||
require(n.rectangle.space is Space.NATIVE, 'native-space')
|
||||
for r in (w, c, n.rectangle):
|
||||
same(s.provenance, r.provenance)
|
||||
require(n.buffer_id > 0 and n.stride == n.rectangle.width * 4 and n.format in (0, 1), 'native-buffer-identity')
|
||||
require(0 < n.commit_serial <= 0xffffffff, 'commit-serial')
|
||||
require(0 < applied.configure_ack <= 0xffffffff, 'assigned-configure-ack')
|
||||
require((w.width, w.height) == (c.width, c.height), 'configure-assignment')
|
||||
require(applied.border_left == w.x and applied.border_top == applied.titlebar + w.y,
|
||||
'titlebar-content-offset')
|
||||
require(w.width + 2 * applied.border_left == s.width and
|
||||
w.height + applied.border_top + applied.border_left == s.height, 'assigned-destination')
|
||||
return Rect(Space.GLOBAL, s.x + applied.border_left + applied.view_offset[0],
|
||||
s.y + applied.border_top + applied.view_offset[1], w.width, w.height, s.provenance)
|
||||
|
||||
|
||||
def intersect(rect, boundary):
|
||||
require(rect.space is Space.GLOBAL and boundary.space in (Space.GLOBAL, Space.VIEWPORT), 'global-intersection')
|
||||
same(rect.provenance, boundary.provenance)
|
||||
x, y = max(rect.x, boundary.x), max(rect.y, boundary.y)
|
||||
return Rect(Space.GLOBAL, x, y, max(0, min(rect.end[0], boundary.end[0]) - x),
|
||||
max(0, min(rect.end[1], boundary.end[1]) - y), rect.provenance)
|
||||
|
||||
|
||||
def to_png(rect, output):
|
||||
require(rect.space is Space.GLOBAL and output.rectangle.space is Space.GLOBAL, 'global-to-png-spaces')
|
||||
require(output.scale == 1.0, 'unmeasured-output-scale')
|
||||
require(output.transform == 'normal', 'unmeasured-output-transform')
|
||||
same(rect.provenance, output.rectangle.provenance)
|
||||
visible = intersect(rect, output.rectangle)
|
||||
return Rect(Space.PNG, min(max(visible.x - output.rectangle.x,0),output.rectangle.width), min(max(visible.y - output.rectangle.y,0),output.rectangle.height),
|
||||
visible.width, visible.height, rect.provenance)
|
||||
|
||||
|
||||
def from_png(rect, output):
|
||||
require(rect.space is Space.PNG and output.rectangle.space is Space.GLOBAL, 'png-to-global-spaces')
|
||||
require(output.scale == 1.0 and output.transform == 'normal', 'unmeasured-png-transform')
|
||||
same(rect.provenance, output.rectangle.provenance)
|
||||
require(0 <= rect.x <= rect.end[0] <= output.rectangle.width and
|
||||
0 <= rect.y <= rect.end[1] <= output.rectangle.height, 'png-bounds')
|
||||
return Rect(Space.GLOBAL, rect.x + output.rectangle.x, rect.y + output.rectangle.y,
|
||||
rect.width, rect.height, rect.provenance)
|
||||
|
||||
|
||||
def verify_paint(rect, output, bbox, count, clip=None):
|
||||
expected = to_png(intersect(rect, clip) if clip is not None else rect, output)
|
||||
actual = [] if bbox is None else list(bbox)
|
||||
box=[expected.x, expected.y, *expected.end] if expected.width and expected.height else []
|
||||
require(actual == box, 'paint-origin-intersection')
|
||||
require(count == expected.width * expected.height, 'paint-area')
|
||||
return expected
|
||||
|
||||
|
||||
def popup(parent, configured, parent_xdg, expected_parent_xdg, ack, mapped):
|
||||
require(parent.space is Space.GLOBAL and configured.space is Space.POPUP, 'popup-spaces')
|
||||
same(parent.provenance, configured.provenance)
|
||||
require(parent_xdg == expected_parent_xdg and expected_parent_xdg > 0, 'xdg-parent')
|
||||
require(0 < ack <= 0xffffffff, 'popup-ack')
|
||||
require(mapped is True, 'popup-map')
|
||||
return Rect(Space.GLOBAL, parent.x + configured.x, parent.y + configured.y,
|
||||
configured.width, configured.height, parent.provenance)
|
||||
|
||||
|
||||
def crossing(rect, boundary_x):
|
||||
require(rect.space is Space.GLOBAL, 'crossing-space')
|
||||
return 'EXERCISED' if rect.x < boundary_x < rect.end[0] else 'NOT EXERCISED'
|
||||
|
||||
|
||||
def local_clip_to_global(local, origin, provenance):
|
||||
require(local.space is Space.CONTENT, 'clip-local-space')
|
||||
same(local.provenance, provenance)
|
||||
return Rect(Space.VIEWPORT, local.x + origin[0], local.y + origin[1],
|
||||
local.width, local.height, provenance)
|
||||
|
||||
|
||||
def pointer_local(xy, rect, actual):
|
||||
require(rect.space is Space.GLOBAL and rect.contains(xy), 'pointer-global-bounds')
|
||||
require(list(actual) == [xy[0] - rect.x, xy[1] - rect.y], 'pointer-local-correlation')
|
||||
return list(actual)
|
||||
|
||||
|
||||
def fresh_chain(old, new):
|
||||
require(old['owner'] != new['owner'] and old['pid'] != new['pid'], 'fresh-E2-identity')
|
||||
require(len(set(old['serials'] + new['serials'])) == 4 and
|
||||
max(old['serials']) < min(new['serials']), 'fresh-E2-serials')
|
||||
|
||||
|
||||
def fresh_standard(mosaic, standard):
|
||||
require(mosaic.provenance.owner == standard.provenance.owner and
|
||||
mosaic.provenance.workspace == standard.provenance.workspace, 'standard-owner')
|
||||
require(mosaic.provenance.generation != standard.provenance.generation, 'fresh-standard-generation')
|
||||
return standard
|
||||
|
||||
|
||||
def constraints(flags, requested, configured):
|
||||
require(type(flags) is int and 0 <= flags <= 63, 'positioner-flags')
|
||||
require(flags != 0 or requested == configured, 'zero-flags-no-adjustment')
|
||||
return {'flags': flags, 'request': requested, 'actual': configured, 'prediction_only': True}
|
||||
|
|
@ -0,0 +1,332 @@
|
|||
"""OFFLINE_TEST_ONLY: real final predicates; no headless spawn or real budget."""
|
||||
import copy,json,os,sys,tempfile,time,unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
|
||||
import cases,engine,fullscreen,gate,oom,oom_handler,plans,validators
|
||||
from geometry import assigned_origin,exterior_ipc,exact
|
||||
from PIL import Image,ImageDraw
|
||||
LAB=Path(os.environ['SMX_LAB']);KIT=Path(__file__).resolve().parents[1]
|
||||
|
||||
class Scientific(unittest.TestCase):
|
||||
def rejects(self,fn,*args,**kwargs):
|
||||
with self.assertRaises((ValueError,AssertionError,RuntimeError,KeyError)):fn(*args,**kwargs)
|
||||
def snapshot(self,width=800,scroll=800,output='HEADLESS-1',focus=3,count=3,gap=0):
|
||||
x=-width if output=='HEADLESS-1' else 0;cell=max(640,(width-(count-1)*gap)//count)
|
||||
return dict(tree=dict(type='workspace',layout='mosaic',name='1',id=20,output=output,rect=dict(x=x,y=0,width=width,height=600),mosaic=dict(rows=[dict(children=list(range(1,count+1)),scroll_x=scroll,focused=focus)]),nodes=[dict(id=i+1,focused=i+1==focus,rect=dict(x=x+i*(cell+gap),y=25,width=cell,height=575)) for i in range(count)]))
|
||||
def state(self):
|
||||
rect=dict(x=-800,y=0,width=800,height=600)
|
||||
owner=dict(type='con',id=40,pid=123,rect=dict(x=-800,y=0,width=400,height=600),nodes=[],floating=None)
|
||||
peer=dict(type='con',id=41,pid=124,rect=dict(x=-400,y=0,width=400,height=600),nodes=[])
|
||||
group=dict(type='con',id=30,layout='splith',fullscreen_mode=1,nodes=[owner,peer],rect=rect,floating_nodes=[])
|
||||
ws=dict(type='workspace',id=20,name='1',output='HEADLESS-1',layout='splith',nodes=[group],fullscreen_mode=1)
|
||||
return dict(owner=owner,workspace=ws,snapshot=dict(tree=ws,outputs=[dict(name='HEADLESS-1',rect=rect)]))
|
||||
def applied(self):return dict(source='post-command-xdg-configure-ack-map',group_id=30,owner_id=40,workspace_id=20)
|
||||
def test_fullscreen_exact_group(self):
|
||||
st=self.state();fullscreen.group_candidate(st,41,30,applied=self.applied())
|
||||
changes=[lambda g:g.update(fullscreen_mode=0),lambda g:g.update(nodes=g['nodes'][:1]),lambda g:g['nodes'][1].update(id=99),lambda g:g['nodes'][0]['rect'].update(width=800),lambda g:g.update(layout='splitv')]
|
||||
for change in changes:
|
||||
bad=copy.deepcopy(st);change(bad['snapshot']['tree']['nodes'][0]);self.rejects(fullscreen.group_candidate,bad,41,30,applied=self.applied())
|
||||
def test_fullscreen_peer_at_workspace(self):
|
||||
bad=self.state();g=bad['workspace']['nodes'][0];bad['workspace']['nodes'].append(g['nodes'].pop());self.rejects(fullscreen.group_candidate,bad,41,30,applied=self.applied())
|
||||
def test_pending_or_other_owner_workspace_not_applied(self):
|
||||
for mutation in ({'source':'pending'},{'owner_id':99},{'workspace_id':99},{'group_id':99}):
|
||||
a=self.applied();a.update(mutation);self.rejects(fullscreen.group_candidate,self.state(),41,30,applied=a)
|
||||
self.rejects(fullscreen.group_candidate,self.state(),41,30)
|
||||
def test_fullscreen_marker_alone_not_enough(self):
|
||||
st=self.state();st['workspace']['nodes'][0]['fullscreen_mode']=0;self.rejects(fullscreen.group_candidate,st,41,30,applied=self.applied())
|
||||
def field(self):
|
||||
baseline=Image.new('RGB',(800,600),(48,48,56));g=dict(origin=[-798,25],configured_A=[350,80,900,170],configured_B=[100,60,900,130],A=[-448,105,900,170],B=[-348,165,900,130])
|
||||
return baseline,g,plans.H[1]
|
||||
def test_whole_field_prediction_occlusion_and_A_only(self):
|
||||
b,g,o=self.field();pred=fullscreen.expected_h2(b,g,o,'logical',engine.COLORS,require_crossing=True)
|
||||
self.assertEqual(pred.getpixel((0,110)),engine.COLORS['POPUP_A']);self.assertEqual(pred.getpixel((0,170)),engine.COLORS['POPUP_B'])
|
||||
one=fullscreen.expected_h2(b,g,o,'logical',engine.COLORS,('POPUP_A',));self.assertEqual(one.getpixel((0,170)),engine.COLORS['POPUP_A'])
|
||||
for xy,color in [((799,599),(1,2,3)),((0,170),engine.COLORS['POPUP_A']),((0,110),(48,48,56))]:
|
||||
wrong=pred.copy();wrong.putpixel(xy,color);self.rejects(exact,pred,wrong)
|
||||
bad=copy.deepcopy(g);bad['A'][0]=10;self.rejects(fullscreen.expected_h2,b,bad,o,'logical',engine.COLORS,require_crossing=True)
|
||||
def test_migration_reveal_and_stale_camera(self):
|
||||
prior=cases.dynamic_geometry(self.snapshot(),expected_focus=3)
|
||||
moved=self.snapshot(scroll=1120,output='HEADLESS-2');actual=cases.dynamic_geometry(moved,prior,'migration',3);self.assertEqual(actual['expected_scroll'],1120)
|
||||
self.rejects(cases.dynamic_geometry,self.snapshot(scroll=800,output='HEADLESS-2'),prior,'migration',3)
|
||||
self.rejects(cases.dynamic_geometry,moved,prior,'resize',3)
|
||||
def test_current_geometry_varies_count_gap_and_viewport(self):
|
||||
prior=cases.dynamic_geometry(self.snapshot(scroll=500,count=4,gap=7,focus=4),expected_focus=4)
|
||||
moved=self.snapshot(width=960,scroll=4*640+3*7-960,output='HEADLESS-2',count=4,gap=7,focus=4)
|
||||
actual=cases.dynamic_geometry(moved,prior,'migration',4);self.assertEqual(actual['expected_scroll'],1621)
|
||||
def test_resize800960800_and_input_invalidation(self):
|
||||
a=cases.dynamic_geometry(self.snapshot(scroll=1100),expected_focus=3)
|
||||
b=cases.dynamic_geometry(self.snapshot(width=960,scroll=960),a,'resize',3)
|
||||
c=cases.dynamic_geometry(self.snapshot(scroll=960),b,'resize',3);self.assertEqual(c['expected_scroll'],960)
|
||||
self.rejects(cases.dynamic_geometry,self.snapshot(width=960,scroll=1100),a,'resize',3)
|
||||
def test_missing_or_other_refocus_order_bounds_pending(self):
|
||||
prior=cases.dynamic_geometry(self.snapshot(),expected_focus=3)
|
||||
for mutate in [lambda w:w['nodes'][2].update(focused=False),lambda w:w['mosaic']['rows'][0].update(focused=1),lambda w:w['mosaic']['rows'][0].update(scroll_x=9999),lambda w:w['mosaic']['rows'][0].update(children=[3,2,1]),lambda w:w['nodes'][2].update(focused=False)]:
|
||||
snap=self.snapshot(scroll=1120,output='HEADLESS-2');mutate(snap['tree']);self.rejects(cases.dynamic_geometry,snap,prior,'migration',3)
|
||||
bad=self.snapshot(scroll=0,focus=1);self.rejects(cases.dynamic_geometry,bad,prior,'migration',3)
|
||||
bad=self.snapshot(scroll=1120);bad['state_source']='pending-as-current';self.rejects(cases.dynamic_geometry,bad,prior,'migration',3)
|
||||
def close_log(self,roles=('TOPLEVEL','POPUP_A'),maps=()):
|
||||
out=[]
|
||||
for i,name in enumerate(roles):
|
||||
a=10+i*3;out.append(f'{name} CREATE wl={a} xdg={a+1} role={a+2}')
|
||||
if name in maps:out.append(name+' MAP wl='+str(a))
|
||||
out+=['CLOSE_RECEIVED','COMMAND close_owner']
|
||||
for i,name in reversed(list(enumerate(roles))):
|
||||
a=10+i*3;out += [f'{name} DESTROY role={a+2} xdg={a+1} wl={a}',f'{"xdg_toplevel" if i==0 else "xdg_popup"}#{a+2}.destroy()',f'xdg_surface#{a+1}.destroy()',f'wl_surface#{a}.destroy()']
|
||||
return '\n'.join(out+['SHM_CLOSURE created=1 released=1','EXIT error=0'])
|
||||
def test_root_only_close_is_valid_and_distinct(self):
|
||||
raw=self.close_log();validators.client_exit(raw,0,'root_failure');self.rejects(validators.client_exit,raw,0,'nested')
|
||||
self.rejects(validators.client_exit,raw+'\nPOPUP_B CREATE wl=99 xdg=98 role=97',0,'root_failure')
|
||||
self.rejects(validators.client_exit,raw.replace('CLOSE_RECEIVED',''),0,'root_failure')
|
||||
def test_nested_failure_A_mapped_B_unmapped(self):
|
||||
raw=self.close_log(('TOPLEVEL','POPUP_A','POPUP_B'),('POPUP_A',));validators.client_exit(raw,0,'nested_failure')
|
||||
self.rejects(validators.client_exit,raw+'\nPOPUP_B MAP wl=16',0,'nested_failure')
|
||||
self.rejects(validators.client_exit,raw.replace('POPUP_A MAP wl=13',''),0,'nested_failure')
|
||||
def test_close_negative_hierarchy_error_release_signal(self):
|
||||
raw=self.close_log()
|
||||
for bad in [raw.replace('POPUP_A DESTROY','TOPLEVEL DESTROY'),raw.replace('released=1','released=0'),raw.replace('error=0','error=12'),raw.replace('xdg_popup#15.destroy()',''),raw+'\nwl_display#1.error(wl_display#1, 1, "no memory")',raw+'\nPOPUP_A MAP wl=13']:
|
||||
self.rejects(validators.client_exit,bad,0,'root_failure')
|
||||
self.rejects(validators.client_exit,raw,-15,'root_failure')
|
||||
def test_wire_real_grabs_and_partial_maps(self):
|
||||
raw=(KIT/'wire-fixture.txt').read_text();validators.grab(raw)
|
||||
import re
|
||||
grab=re.search(r'POPUP_A GRAB_REQUEST[^\n]+',raw)[0];serial=re.search(r'serial=(\d+)',grab)[1]
|
||||
changes=[raw.replace('used='+serial,'used=999',1),raw.replace('POPUP_B MAP ','POPUP_B NOT_MAP ',1),re.sub(r'(POPUP_B CREATE[^\n]+parent_xdg=)\d+',r'\g<1>999',raw),re.sub(r'xdg_popup#\d+\.grab',r'xdg_popup#999.grab',raw,count=1)]
|
||||
for bad in changes:self.assertNotEqual(raw,bad);self.rejects(validators.grab,bad)
|
||||
def test_failed_popup_real_wire_no_map(self):
|
||||
raw=(KIT/'wire-fixture.txt').read_text();oom_handler.opening_before_map(raw,'POPUP_A','TOPLEVEL')
|
||||
grab=__import__('re').search(r'POPUP_A GRAB_REQUEST[^\n]+',raw)[0];serial=__import__('re').search(r'serial=(\d+)',grab)[1]
|
||||
self.rejects(oom_handler.opening_before_map,raw.replace('used='+serial,'used=999',1),'POPUP_A','TOPLEVEL')
|
||||
def test_no_memory_requires_wire(self):
|
||||
raw='wl_display#1.error(wl_display#1, 1, "no memory")\nEXIT error=12\n';oom_handler.expected_disconnect(raw,0)
|
||||
for bad in [raw.replace('.error(','.no_error('),raw.replace('error=12','error=0'),raw.replace(', 1,',', 2,')]:self.rejects(oom_handler.expected_disconnect,bad,0)
|
||||
def test_native_counter_and_NULL_association(self):
|
||||
env=dict(SMX_OOM_NONCE='a'*32,SMX_OOM_ROLE='root_popup')
|
||||
raw='SMX_OOM ENOMEM role=root_popup nonce='+env['SMX_OOM_NONCE']+' pid=123 xdg=0x10 xdg_parent=0x20 parent=0x30 tree=0x40 children=0 matched=1 failures=1 successes=0 initialized=0 listeners=0 published=0\nSMX_OOM result role=root_popup pid=123 xdg=0x10 xdg_parent=0x20 parent=0x30 tree=(nil) successes=0 failures=1'
|
||||
oom.native_failure(raw,env,123)
|
||||
for old,new in [('listeners=0','listeners=1'),('published=0','published=1'),('initialized=0','initialized=1'),('pid=123','pid=999'),('tree=(nil)','tree=0x40'),('xdg_parent=0x20','xdg_parent=0x99'),('matched=1','matched=2'),('nonce='+env['SMX_OOM_NONCE'],'nonce='+'b'*32)]:
|
||||
bad=raw.replace(old,new,1);self.rejects(oom.native_failure,bad,env,123)
|
||||
def test_B5_native_SHM_and_destination_titlebar_distinct(self):
|
||||
owner=dict(rect=dict(x=-800,y=25,width=640,height=575),window_rect=dict(x=2,y=0,width=636,height=573),deco_rect=dict(height=25))
|
||||
self.assertEqual(assigned_origin(owner),[-798,25]);self.assertEqual(exterior_ipc(owner),[-800,0,640,600])
|
||||
self.assertNotEqual([420,320],[owner['window_rect']['width'],owner['window_rect']['height']])
|
||||
def test_floating_null_tiled_and_actual_floating_rejected(self):
|
||||
st=self.state();path=[st['workspace'],st['workspace']['nodes'][0],st['owner']];st['workspace']['layout']='mosaic'
|
||||
self.assertTrue(validators.camera_eligible(path,st['workspace']) is False) # fullscreen exemption
|
||||
path[1]['fullscreen_mode']=0;self.assertTrue(validators.camera_eligible(path,st['workspace']))
|
||||
path[1]['floating_nodes']=[st['owner']];self.assertFalse(validators.camera_eligible(path,st['workspace']))
|
||||
|
||||
def test_B5_terminal_raw_codes_not_rewritten(self):
|
||||
terminal=dict(ipc_exit=1,stdout='',stderr='Unable to receive IPC response')
|
||||
self.assertTrue(validators.terminal_transport(terminal,0));self.assertEqual(terminal['ipc_exit'],1)
|
||||
self.assertFalse(validators.terminal_transport(terminal,1));self.assertFalse(validators.terminal_transport(dict(ipc_exit=1),0))
|
||||
self.assertFalse(validators.terminal_transport(dict(ipc_exit=1,stdout='',stderr=''),0))
|
||||
def test_positioner_adjust_same_actual_wire_required(self):
|
||||
raw=(KIT/'wire-fixture.txt').read_text()
|
||||
import re
|
||||
adapted=re.sub(r'(xdg_wm_base#\d+\.create_positioner\(new id xdg_positioner#(\d+)\))',lambda m:m[0]+'\nxdg_positioner#'+m[2]+'.set_constraint_adjustment(0)',raw)
|
||||
self.assertEqual(fullscreen.positioner_adjust(adapted),[0,0])
|
||||
self.rejects(fullscreen.positioner_adjust,adapted.replace('set_constraint_adjustment(0)','set_constraint_adjustment(1)',1))
|
||||
self.rejects(fullscreen.positioner_adjust,raw)
|
||||
|
||||
def test_native_availability_missing_identity_IPC_input_socket(self):
|
||||
expected=dict(pid=123,binary='/private/sway',binary_sha256='a'*64,build_id='b'*40,wlroots='/usr/lib/libwlroots-0.20.so')
|
||||
ident=dict(pid=123,exe=expected['binary'],sha256=expected['binary_sha256'],build_id=expected['build_id'],wlroots=[expected['wlroots']],maps='mapped '+expected['wlroots'],swaysock='/tmp/smx-offline/sway-ipc.123.sock',captured_before_cleanup=True)
|
||||
snap=dict(tree=dict(type='root',nodes=[dict(id=1)]),outputs=[dict(active=True),dict(active=True)])
|
||||
buttons=dict(reference=['BUTTON ns=100 serial=11 button=273 state=1','BUTTON ns=101 serial=12 button=273 state=0'])
|
||||
raw='wl_pointer#9.button(11, 100, 273, 1)\nwl_pointer#9.button(12, 101, 273, 0)'
|
||||
oom_handler.availability_certificate(ident,expected,snap,raw,buttons)
|
||||
for change in ({'pid':999},{'sha256':'c'*64},{'build_id':'c'*40},{'maps':'wrong'},{'captured_before_cleanup':False},{'swaysock':'/run/user/1000/sway.sock'}):
|
||||
bad=dict(ident,**change);self.rejects(oom_handler.availability_certificate,bad,expected,snap,raw,buttons)
|
||||
self.rejects(oom_handler.availability_certificate,ident,expected,dict(tree={},outputs=[]),raw,buttons)
|
||||
self.rejects(oom_handler.availability_certificate,ident,expected,snap,raw,{})
|
||||
self.rejects(oom_handler.availability_certificate,ident,expected,snap,raw.replace('273, 0','273, 1'),buttons)
|
||||
def test_predictions_precede_runtime_captures(self):
|
||||
# The real acquisition helper calls the production oracle first; a capture
|
||||
# side-effect changes the geometry to prove it cannot influence prediction.
|
||||
b,g,o=self.field();expected=fullscreen.expected_h2(b,g,o,'logical',engine.COLORS)
|
||||
from types import SimpleNamespace
|
||||
events=[]
|
||||
def capture(tag):
|
||||
events.append('capture');g['A'][0]=999;return [b,expected]
|
||||
session=SimpleNamespace(outputs=plans.H,frames={'HEADLESS-2':'logical'},record=lambda *args,**kwargs:events.append('prediction'),pair_capture=capture)
|
||||
fullscreen.measured_pair(session,'offline',b,g);self.assertEqual(events,['prediction','capture'])
|
||||
|
||||
class RepairPredicates(unittest.TestCase):
|
||||
rejects=Scientific.rejects
|
||||
def test_oom_pixels_both_outputs_and_final(self):
|
||||
b=Image.new('RGB',(40,40),(48,48,56));clean=[b.copy(),b.copy()]
|
||||
for failed in ('POPUP_A','POPUP_B'):
|
||||
oom_handler.popup_pixels(clean,b,failed)
|
||||
for output in (0,1):
|
||||
bad=[im.copy() for im in clean];bad[output].putpixel((7,9),engine.COLORS[failed])
|
||||
self.rejects(oom_handler.popup_pixels,bad,b,failed)
|
||||
alive=[b.copy(),b.copy()];alive[0].putpixel((7,9),engine.COLORS['POPUP_A'])
|
||||
oom_handler.popup_pixels(alive,b,'POPUP_B')
|
||||
for role in ('POPUP_A','POPUP_B'):
|
||||
for output in (0,1):
|
||||
bad=[im.copy() for im in clean];bad[output].putpixel((7,9),engine.COLORS[role])
|
||||
self.rejects(oom_handler.popup_pixels,bad,b,final=True)
|
||||
oom_handler.popup_pixels(clean,b,final=True)
|
||||
def test_root_input_real_delivery_wrong_wire_owner_point(self):
|
||||
from types import SimpleNamespace
|
||||
raw=(KIT/'wire-fixture.txt').read_text()
|
||||
# Real fixture establishes configured root identity; use its measured rectangle.
|
||||
proof=oom_handler.mapped_root(raw);x,y,w,h=proof['configure']
|
||||
origin=[-x,-y];image=Image.new('RGB',(800,600),engine.COLORS['POPUP_A'])
|
||||
def exercise(wrong=None):
|
||||
def click(name,point,action):
|
||||
self.assertEqual((name,action),('owner','right'))
|
||||
local=[point[0],point[1]]
|
||||
if wrong=='point':local[0]+=1
|
||||
target='TOPLEVEL' if wrong=='role' else 'POPUP_A'
|
||||
lines=[f'POINTER_BUTTON target={target} serial={i+10} button=273 state={v}' for i,v in enumerate((1,0))]
|
||||
tail=f'POINTER_MOTION target=POPUP_A x={local[0]} y={local[1]}\n'+'\n'.join(f'wl_pointer#9.button({i+10}, 100, 273, {v})' for i,v in enumerate((1,0)))
|
||||
if wrong=='wire':tail=tail.replace('273, 0','273, 1')
|
||||
buttons={'owner':lines,'reference':lines if wrong=='owner' else []}
|
||||
return {'owner':tail},buttons
|
||||
obj=SimpleNamespace(log=lambda name:raw,owner=lambda name:dict(origin=origin),outputs=[dict(scale=1,logical=[0,0,800,600])],frames={'HEADLESS-1':'logical'},click=click)
|
||||
obj.delivery=lambda *args:engine.Session.delivery(obj,*args)
|
||||
return oom_handler.surviving_root_input(obj,[image,image])
|
||||
self.assertEqual(exercise()['status'],'PARTIALLY_FEASIBLE')
|
||||
for wrong in ('wire','owner','point','role'):self.rejects(exercise,wrong)
|
||||
def test_git_actual_tree_inventory_and_modes(self):
|
||||
import subprocess
|
||||
top=KIT.parents[3];prefix=Path('tooling/probes/step20c-applied-state-input')
|
||||
raw=subprocess.run(['git','-C',str(top),'ls-tree','-rz','--full-tree','HEAD','--',str(prefix)],check=True,capture_output=True).stdout
|
||||
self.assertTrue(raw)
|
||||
files={};modes={}
|
||||
for row in raw.split(b'\0')[:-1]:
|
||||
meta,path=row.split(b'\t',1);rel=path[len(str(prefix).encode())+1:].decode();files[rel]=gate.sha(top/path.decode());modes[rel]='0o755' if meta.startswith(b'100755') else '0o644'
|
||||
gate.git_inventory(raw,str(prefix),files,modes)
|
||||
gate.published_sources(top,'HEAD',prefix,files,modes,gate.clean_environment(LAB))
|
||||
first=raw.split(b'\0')[0];meta,path=first.split(b'\t',1);mode,kind,oid=meta.split(b' ')
|
||||
for replacement in (b'100755' if mode==b'100644' else b'100644',b'120000'):
|
||||
bad=raw.replace(first,replacement+b' '+kind+b' '+oid+b'\t'+path,1)
|
||||
self.rejects(gate.git_inventory,bad,str(prefix),files,modes)
|
||||
self.rejects(gate.git_inventory,raw+first.replace(path,path+b'-extra')+b'\0',str(prefix),files,modes)
|
||||
self.rejects(gate.git_inventory,raw[len(first)+1:],str(prefix),files,modes)
|
||||
self.rejects(gate.git_inventory,raw,str(prefix)+'-wrong',files,modes)
|
||||
for invalid in ('0644','0o600','100644',644):
|
||||
bad=dict(modes);bad[next(iter(bad))]=invalid;self.rejects(gate.git_inventory,raw,str(prefix),files,bad)
|
||||
def test_source_modes_reject_nonportable_and_symlink(self):
|
||||
root=Path(tempfile.mkdtemp(dir=os.environ['TMPDIR']));p=root/'file';p.write_text('source');p.chmod(0o644)
|
||||
self.assertEqual(gate.source_modes(root),{'file':'0o644'})
|
||||
p.chmod(0o755);self.assertEqual(gate.source_modes(root),{'file':'0o755'})
|
||||
p.chmod(0o600);self.rejects(gate.source_modes,root)
|
||||
p.unlink();p.symlink_to('/dev/null');self.rejects(gate.source_modes,root)
|
||||
|
||||
class Admission(Scientific):
|
||||
def setUp(self):
|
||||
self.root=Path(tempfile.mkdtemp(prefix='gate-',dir=os.environ.get('TMPDIR',str(LAB/'scratch'))));self.ns=self.root/'OFFLINE_TEST_ONLY';self.ns.mkdir(mode=0o700)
|
||||
self.manifest=self.ns/'manifest.json';self.review=self.ns/'review.json';self.publication=self.ns/'publication.json'
|
||||
self.files=gate.source_files();self.m=dict(ticket=gate.TICKET,schema='native-closure-source-v1',author='fixture-author',files=self.files,modes=gate.source_modes(),plans=plans.RUNS,limits=plans.LIMITS)
|
||||
gate.durable(self.manifest,self.m)
|
||||
elf=self.ns/'fake-offline-ELF';elf.write_bytes(b'OFFLINE_TEST_ONLY never executed')
|
||||
identity=dict(binary=str(elf),binary_sha256=gate.sha(elf),wlroots='/usr/lib/libwlroots-0.20.so',wlroots_sha256=gate.WL_SHA,build_review='ACCEPTED',source_qualification='ACCEPTED',build_id='offline')
|
||||
runtime=self.ns/'runtime.json';gate.durable(runtime,dict(ticket=gate.TICKET,identities={name:identity for name in ('pristine','core','oom')}))
|
||||
# Fake payloads are explicitly OFFLINE; only validation and budget state are
|
||||
# exercised. No receipt can authorize Session (offline namespace is rejected).
|
||||
self.build=LAB/'followup-work/client-build';self.generated={name:gate.sha(self.build/name) for name in ('fullscreen-client','nested-client','static-reference','pointer-daemon')}
|
||||
compile=self.ns/'compile.json';gate.durable(compile,dict(status='PASS',runtime='NOT_RUN',generated=self.generated,inputs={str(p.relative_to(KIT)):gate.sha(p) for folder in ('clients','protocols') for p in (KIT/folder).iterdir()}))
|
||||
test=self.ns/'tests.json';gate.durable(test,dict(status='PASS',tests_run=1,failures=0,source_digest=gate.digest(self.files)))
|
||||
case=dict(oom_source_review='ACCEPTED',variant_identity='COREC_V2_NATIVE_OOM_DIAGNOSTIC',source_manifest_sha256=gate.sha(self.manifest),decision='ACCEPTED',client_build_review='ACCEPTED',client_build_receipt=str(compile),client_build_receipt_sha256=gate.sha(compile))
|
||||
self.r=dict(ticket=gate.TICKET,decision='ACCEPTED',independent=True,reviewer='offline-independent-fixture',manifest_sha256=gate.sha(self.manifest),runtime_manifest=str(runtime),runtime_manifest_sha256=gate.sha(runtime),runs={rid:case for rid in plans.RUNS},gates={name:dict(decision='ACCEPTED',manifest_sha256=gate.sha(self.manifest),receipt=str(test),receipt_sha256=gate.sha(test)) for name in ('FULLSCREEN_COMPARABILITY_OFFLINE','MIGRATION_REFOCUS_OFFLINE','OOM_ROOT_NESTED_ORACLE_OFFLINE')})
|
||||
gate.durable(self.review,self.r);commit='a'*40
|
||||
self.p=dict(ticket=gate.TICKET,decision='VERIFIED',offline_test_only=True,source_commit=commit,origin_sha=commit,github_sha=commit,manifest_sha256=gate.sha(self.manifest),source_digest=gate.digest(self.files),remote_observations={name:dict(exit=0,argv=['git','ls-remote',name,'refs/heads/main'],stdout=commit+'\trefs/heads/main\n') for name in ('origin','github')});gate.durable(self.publication,self.p)
|
||||
self.ledger=gate.Ledger(LAB,self.ns/'budget.json',offline=True);self.ledger.initialize()
|
||||
def validate(self,rid='OM-ROOT'):return gate.validate_review(LAB,self.manifest,self.review,self.publication,rid,offline=True)
|
||||
def admit(self,rid='OM-ROOT'):return self.ledger.admit(rid,self.manifest,self.review,self.publication)
|
||||
def test_real_validator_offline_namespace_and_unique_reservation(self):
|
||||
self.validate();a=self.admit();self.ledger.action_debit(a,'capture','hook');self.rejects(self.admit)
|
||||
self.rejects(self.ledger.action_debit,a,'hook');self.ledger.complete(a,dict(status='INCONCLUSIVE'));self.rejects(self.ledger.action_debit,a,'capture')
|
||||
self.rejects(engine.Session,plans.RUNS['OM-ROOT'],a)
|
||||
def test_historical_ids_and_tokens(self):
|
||||
for rid in ('O1','O2','C7','P1','C8','G2'):self.rejects(self.validate,rid)
|
||||
auth=self.admit();bad=dict(auth,token='historic-token');self.rejects(self.ledger.action_debit,bad,'capture')
|
||||
def test_self_review_and_wrong_SHA_reject_before_reservation(self):
|
||||
for mutate in [lambda r:r.update(reviewer='fixture-author'),lambda r:r.update(decision='PASS'),lambda r:r.update(manifest_sha256='0'*64),lambda r:r.update(gates={})]:
|
||||
bad=copy.deepcopy(self.r);mutate(bad);gate.durable(self.review,bad);self.rejects(self.admit)
|
||||
self.assertEqual(json.loads(self.ledger.path.read_text())['runs'],[])
|
||||
def test_publication_remote_observation_and_clone_bytes_binding(self):
|
||||
for mutate in [lambda p:p.update(github_sha='b'*40),lambda p:p.update(remote_observations={}),lambda p:p.update(source_digest='0'*64),lambda p:p.update(offline_test_only=False)]:
|
||||
bad=copy.deepcopy(self.p);mutate(bad);gate.durable(self.publication,bad);self.rejects(self.admit)
|
||||
def test_stale_source_manifest_and_superficial_gate(self):
|
||||
self.m['files']['graphics/plans.py']='0'*64;gate.durable(self.manifest,self.m);self.rejects(self.admit)
|
||||
gate.durable(self.manifest,{**self.m,'files':self.files});self.r['gates']['OOM_ROOT_NESTED_ORACLE_OFFLINE']['decision']='PASS';gate.durable(self.review,self.r);self.rejects(self.admit)
|
||||
def test_root_dependency_unresolved_cannot_consume_nested(self):
|
||||
self.rejects(self.admit,'OM-NESTED');root=self.admit();self.ledger.complete(root,dict(status='INCONCLUSIVE',scientific_cleanup=True));self.rejects(self.admit,'OM-NESTED')
|
||||
self.assertEqual(len(json.loads(self.ledger.path.read_text())['runs']),1)
|
||||
def test_root_qualified_allows_one_nested(self):
|
||||
root=self.admit();self.ledger.complete(root,dict(status='PARTIALLY_FEASIBLE',scientific_cleanup=True));self.admit('OM-NESTED');self.rejects(self.admit,'OM-NESTED')
|
||||
def test_optional_gpu_never_auto_admitted(self):
|
||||
for rid in ('GPU-G3','GPU-CTRL','OM-EXTRA'):self.rejects(self.admit,rid)
|
||||
def test_caps_duration_and_failed_reservation_spent(self):
|
||||
auth=self.admit()
|
||||
for _ in range(10):self.ledger.action_debit(auth,'capture')
|
||||
self.rejects(self.ledger.action_debit,auth,'capture')
|
||||
def expire(d):d['runs'][0]['started_ns']=time.monotonic_ns()-151_000_000_000;return None,d
|
||||
self.ledger.transaction(expire);self.rejects(self.ledger.action_debit,auth,'move');self.rejects(self.admit)
|
||||
def test_mode_symlink_hardlink_lock_attacks(self):
|
||||
self.ledger.path.chmod(0o644);self.rejects(self.admit);self.ledger.path.chmod(0o600)
|
||||
alias=self.ns/'hardlink';os.link(self.ledger.path,alias);self.rejects(self.admit);alias.unlink()
|
||||
lock=self.ledger.lock;lock.unlink();lock.symlink_to(self.manifest)
|
||||
with self.assertRaises(OSError):self.admit()
|
||||
def test_clean_environment_no_daily_authority(self):
|
||||
with patch.dict(os.environ,dict(SWAYSOCK='/run/user/daily',WAYLAND_DISPLAY='daily',DISPLAY=':0',LD_PRELOAD='evil',LD_LIBRARY_PATH='daily',WLR_RENDERER='daily',SMX_OOM_NONCE='daily')):
|
||||
e=gate.clean_environment(LAB)
|
||||
self.assertFalse(set(e)&{'SWAYSOCK','WAYLAND_DISPLAY','DISPLAY','LD_PRELOAD','LD_LIBRARY_PATH','WLR_RENDERER','SMX_OOM_NONCE'})
|
||||
self.assertTrue(all(Path(e[k]).is_relative_to(LAB) for k in ('TMPDIR','TMP','TEMP','XDG_CACHE_HOME','XDG_DATA_HOME','PYTHONPYCACHEPREFIX','CCACHE_DIR')))
|
||||
|
||||
def test_concurrent_reservation_single_winner(self):
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
def attempt(_):
|
||||
try:self.admit();return 'reserved'
|
||||
except ValueError:return 'refused'
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:out=list(pool.map(attempt,range(2)))
|
||||
self.assertEqual(sorted(out),['refused','reserved']);self.assertEqual(len(json.loads(self.ledger.path.read_text())['runs']),1)
|
||||
def test_source_addition_and_mutation_after_freeze(self):
|
||||
import shutil
|
||||
source=self.ns/'portable-source';shutil.copytree(gate.SOURCE_ROOT,source)
|
||||
with patch.object(gate,'SOURCE_ROOT',source):
|
||||
self.validate();(source/'unexpected.py').write_text('unreviewed=True\n');self.rejects(self.admit)
|
||||
(source/'unexpected.py').unlink();(source/'graphics/plans.py').chmod(0o755);self.rejects(self.admit);(source/'graphics/plans.py').chmod(0o644);(source/'graphics/plans.py').write_text('# mutated\n');self.rejects(self.admit)
|
||||
def test_real_watchdog_independently_kills_owned_child(self):
|
||||
import signal,subprocess,threading
|
||||
from types import SimpleNamespace
|
||||
child=subprocess.Popen(['prlimit','--core=0:0','--',sys.executable,'-B','-c','import signal,time; signal.signal(signal.SIGTERM,signal.SIG_IGN); print("READY",flush=True); time.sleep(20)'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,start_new_session=True,env=gate.clean_environment(LAB))
|
||||
self.assertEqual(child.stdout.readline().strip(),'READY')
|
||||
obj=SimpleNamespace(finished=threading.Event(),started=time.monotonic()-142.9,run=self.ns,proc_lock=threading.RLock(),expired=False,proc={'offline-owned-child':child},transient={})
|
||||
obj.signal_owned=lambda sig:engine.Session.signal_owned(obj,sig)
|
||||
thread=threading.Thread(target=engine.Session.watchdog,args=(obj,));thread.start()
|
||||
try:self.assertEqual(child.wait(timeout=4),-signal.SIGKILL);thread.join(timeout=1);self.assertTrue(obj.expired);self.assertFalse(thread.is_alive())
|
||||
finally:
|
||||
if child.poll() is None:os.killpg(child.pid,signal.SIGKILL);child.wait(timeout=1)
|
||||
obj.finished.set();thread.join(timeout=1);child.stdout.close();child.stderr.close()
|
||||
|
||||
def test_GPU_environment_and_same_identity_adapter(self):
|
||||
import gpu_handler
|
||||
from types import SimpleNamespace
|
||||
calls=[];session=SimpleNamespace(lab=LAB,spec=plans.RUNS['GPU-G3'],env=gate.clean_environment(LAB),debit=lambda *args:calls.append(args))
|
||||
adapter=gpu_handler.AdapterSession(session);self.assertEqual(adapter.spec['binary'],'gpu')
|
||||
adapter.env.update(MOSAIC_G3_DIR=str(LAB/'gpu-work/acquisition'),MOSAIC_G3_NONCE='a'*64,WLR_RENDERER='gles2')
|
||||
self.assertNotIn('MOSAIC_G3_DIR',session.env);self.assertEqual(session.compositor_environment['MOSAIC_G3_NONCE'],'a'*64)
|
||||
adapter.debit('mutation');self.assertEqual(calls,[('mutation','hook')]);self.rejects(adapter.debit,'hook')
|
||||
self.rejects(adapter.env.update,MOSAIC_G3_DIR='/run/user/daily',MOSAIC_G3_NONCE='a'*64,WLR_RENDERER='gles2')
|
||||
session.spec=dict(session.spec,binary='core');self.rejects(gpu_handler.AdapterSession,session)
|
||||
def test_GPU_gate_requires_independent_B4(self):
|
||||
self.rejects(self.admit,'GPU-G3');self.assertEqual(json.loads(self.ledger.path.read_text())['runs'],[])
|
||||
self.r['gates']['B4']=dict(status='PASS');gate.durable(self.review,self.r);self.rejects(self.admit,'GPU-G3')
|
||||
def test_GPU_binding_rejects_empty_or_unreviewed(self):
|
||||
import gpu_handler
|
||||
for case in ({},{'gpu_source_review':'ACCEPTED'},{'gpu_source_review':'ACCEPTED','gl_observability_preflight':'ACCEPTED','build_review':'ACCEPTED','native_gl_status':'FEASIBLE'}):
|
||||
self.rejects(gpu_handler.validate_binding,LAB,case,{},gate.sha(self.manifest))
|
||||
def test_GPU_module_origin_attack(self):
|
||||
import gpu_handler
|
||||
from types import SimpleNamespace
|
||||
from contextlib import ExitStack
|
||||
with ExitStack() as stack:
|
||||
for name in ('protocol','scenario','validate_source'):stack.enter_context(patch.dict(sys.modules,{name:SimpleNamespace(__file__='/foreign/'+name+'.py')}))
|
||||
self.rejects(gpu_handler.modules,LAB)
|
||||
|
||||
if __name__=='__main__':unittest.main(verbosity=2)
|
||||
59
tooling/probes/step20c-corec-v2-followup/graphics/oom.py
Normal file
59
tooling/probes/step20c-corec-v2-followup/graphics/oom.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Private source-only adapter to the independently reviewed graphics-kit Session.
|
||||
No native run entry point. O1/O2 remain NOT_ADMITTED until separate review.
|
||||
"""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
PLANS={
|
||||
'OM-ROOT':dict(status='NOT_ADMITTED',role='root_popup',max_captures=8,max_injections=1,requires_rootA_stable=False),
|
||||
'OM-NESTED':dict(status='NOT_ADMITTED',role='nested_popup',max_captures=8,max_injections=1,requires_rootA_stable=True),
|
||||
}
|
||||
CAPTURES=('reference-H2-a','reference-H2-b','pre-H1','pre-H2','post-H1','post-H2','final-H1','final-H2')
|
||||
def process_environment(lab,run,nonce):
|
||||
assert run in PLANS and re.fullmatch('[0-9a-f]{32}',nonce)
|
||||
lab=Path(lab);assert lab.is_absolute() and lab.resolve()==lab and lab.stat().st_uid==os.getuid()
|
||||
# Parent must use this update solely in Session's compositor spawn environment.
|
||||
# Do not put it into Session.env inherited by scientific clients/helpers.
|
||||
path=lab/'graphics-runs'/run/'run'/'oom-token'
|
||||
assert not path.exists() and not path.is_symlink()
|
||||
return dict(SMX_OOM_TOKEN_PATH=str(path),SMX_OOM_NONCE=nonce,SMX_OOM_ROLE=PLANS[run]['role'])
|
||||
def arm(session,env,admission,stable):
|
||||
run=session.spec['id'];assert run in PLANS
|
||||
# Separate reviewed admission must bind variant binary + OOM source freeze,
|
||||
# graphics engine freeze, independent R-OOM, build review and publication.
|
||||
assert admission==session.auth['prospective']
|
||||
assert admission['decision']=='ACCEPTED' and admission['source_manifest_sha256']==session.auth['manifest_sha256']
|
||||
assert admission.get('oom_source_review')=='ACCEPTED' and session.auth['build_review']=='ACCEPTED'
|
||||
assert session.auth['publication_sha256'] and admission['variant_identity']=='COREC_V2_NATIVE_OOM_DIAGNOSTIC'
|
||||
assert stable['startup'] and stable['owner'] and stable['neighbor'] and stable['reference_H2_twice']
|
||||
if PLANS[run]['requires_rootA_stable']:assert stable['rootA'] and stable['rootA_native_success']
|
||||
assert env['SMX_OOM_ROLE']==PLANS[run]['role']
|
||||
path=Path(env['SMX_OOM_TOKEN_PATH']);assert path.parent.resolve()==session.run.resolve()
|
||||
assert path.name=='oom-token' and not path.is_symlink()
|
||||
# The parent's reviewed OOM ledger extension must provide hook cap=1. Debit
|
||||
# precedes O_EXCL write; failed arm stays spent, with no retry/refund.
|
||||
session.debit('hook')
|
||||
fd=os.open(path,os.O_CREAT|os.O_EXCL|os.O_WRONLY|os.O_NOFOLLOW,0o600)
|
||||
try:
|
||||
os.fchmod(fd,0o600)
|
||||
token=f"SMXOOM1 {env['SMX_OOM_NONCE']} {env['SMX_OOM_ROLE']}\n".encode('ascii')
|
||||
assert os.write(fd,token)==len(token);os.fsync(fd)
|
||||
finally:os.close(fd)
|
||||
session.record('oom-arm',role=env['SMX_OOM_ROLE'],nonce=env['SMX_OOM_NONCE'],path=str(path))
|
||||
def native_failure(log,env,pid):
|
||||
lines=[line for line in log.splitlines() if 'SMX_OOM ENOMEM ' in line]
|
||||
assert len(lines)==1
|
||||
fields=dict(re.findall(r'([a-z_]+)=([^ ]+)',lines[0]))
|
||||
for key,value in dict(role=env['SMX_OOM_ROLE'],nonce=env['SMX_OOM_NONCE'],pid=str(pid),matched='1',failures='1',initialized='0',listeners='0',published='0').items():assert fields.get(key)==value,(key,fields)
|
||||
assert all(fields.get(k) not in (None,'(nil)','0x0') for k in ['xdg','xdg_parent','parent','tree'])
|
||||
assert fields.get('successes')==('0' if env['SMX_OOM_ROLE']=='root_popup' else '1')
|
||||
assert re.fullmatch(r'\d+',fields.get('children',''))
|
||||
# WHY: native children counts existing PARENT children, not the failed tree.
|
||||
# CONTRACT: preserve it without demanding impossible nested parent_children0.
|
||||
# INVARIANT: NULL result must bind same role/PID/xdg/parent, unpublished node.
|
||||
results=[line for line in log.splitlines() if 'SMX_OOM result ' in line and 'role='+env['SMX_OOM_ROLE']+' ' in line]
|
||||
assert len(results)==1,'unique targeted NULL caller result'
|
||||
result=dict(re.findall(r'([a-z_]+)=([^ ]+)',results[0]))
|
||||
for key in ('role','pid','xdg','xdg_parent','parent','successes','failures'):assert result.get(key)==fields[key]
|
||||
assert result.get('tree') in ('(nil)','0x0'),'failed tree returned non-NULL'
|
||||
fields.update(parent_children=fields['children'],children_scope='existing parent children, not failed tree',caller_result=result)
|
||||
return fields
|
||||
173
tooling/probes/step20c-corec-v2-followup/graphics/oom_handler.py
Normal file
173
tooling/probes/step20c-corec-v2-followup/graphics/oom_handler.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
"""EXPERIMENTAL prospective O1/O2 handler. No allocator or admission policy."""
|
||||
import errno,re,time,subprocess
|
||||
from pathlib import Path
|
||||
import cases,oom
|
||||
from gate import sha,need
|
||||
from geometry import solid_point,exact,count
|
||||
from engine import COLORS
|
||||
|
||||
def opening_before_map(raw,popup,parent):
|
||||
# WHY: native no-memory can precede configure/MAP. CONTRACT/INVARIANT:
|
||||
# validate actual serial/grab/parent wire, never await the failed popup MAP.
|
||||
rows=re.findall(rf'{popup} CREATE wl=(\d+) xdg=(\d+) role=(\d+) parent={parent} parent_xdg=(\d+)',raw)
|
||||
need(len(rows)==1,'unique failed popup identity');wl,xdg,role,parent_xdg=map(int,rows[0])
|
||||
parent_ids=re.findall(rf'{parent} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',raw)
|
||||
need(len(parent_ids)==1 and int(parent_ids[0][1])==parent_xdg,'failed popup exact parent')
|
||||
grabs=re.findall(rf'{popup} GRAB_REQUEST origin=wl_pointer.button target={parent} serial=(\d+) used=(\d+)',raw)
|
||||
need(len(grabs)==1 and grabs[0][0]==grabs[0][1] and int(grabs[0][0])>0,'failed popup real grab')
|
||||
serial=int(grabs[0][0]);button=raw.index(f'POINTER_BUTTON target={parent} serial={serial} button=272 state=1')
|
||||
wire=re.search(rf'wl_pointer#\d+\.button\({serial}, \d+, 272, 1\)',raw)
|
||||
create=re.search(rf'xdg_surface#{xdg}\.get_popup\(new id xdg_popup#{role}, xdg_surface#{parent_xdg}, xdg_positioner#\d+\)',raw)
|
||||
grab=re.search(rf'xdg_popup#{role}\.grab\(wl_seat#\d+, {serial}\)',raw)
|
||||
need(wire is not None and create is not None and grab is not None and wire.start()<button<create.start()<grab.start(),'failed grab wire association')
|
||||
return dict(popup=popup,parent=parent,serial=serial,wl=wl,xdg=xdg,role=role,parent_xdg=parent_xdg)
|
||||
|
||||
def mapped_root(raw):
|
||||
proof=opening_before_map(raw,'POPUP_A','TOPLEVEL')
|
||||
need(raw.count('POPUP_A MAP ')==1 and 'POPUP_A DESTROY ' not in raw and 'POPUP_A POPUP_DONE ' not in raw,'O2 same rootA mapped')
|
||||
cfg=re.findall(r'POPUP_A ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',raw)
|
||||
ack=re.findall(r'POPUP_A XDG_CONFIGURE serial=(\d+) ack',raw)
|
||||
need(len(cfg)==len(ack)==1,'O2 unique rootA configure/ACK')
|
||||
x,y,w,h=map(int,cfg[0]);serial=int(ack[0]);xdg=proof['xdg'];role=proof['role'];wl=proof['wl']
|
||||
rolewire=re.search(rf'xdg_popup#{role}\.configure\({x}, {y}, {w}, {h}\)',raw)
|
||||
ackwire=re.search(rf'xdg_surface#{xdg}\.ack_configure\({serial}\)',raw)
|
||||
nativecfg=re.search(rf'xdg_surface#{xdg}\.configure\({serial}\)',raw)
|
||||
attach=re.search(rf'wl_surface#{wl}\.attach\(wl_buffer#(\d+), 0, 0\)',raw)
|
||||
commits=list(re.finditer(rf'wl_surface#{wl}\.commit\(\)',raw))
|
||||
need(all((rolewire,ackwire,nativecfg,attach)) and len(commits)==2,'O2 rootA native configure/map evidence')
|
||||
buffer=attach[1]
|
||||
created=re.search(rf'create_buffer\(new id wl_buffer#{buffer}, 0, {w}, {h}, {w*4}, 1\)',raw)
|
||||
need(created is not None and commits[0].start()<rolewire.start()<nativecfg.start()<ackwire.start()<created.start()<attach.start()<commits[1].start()<raw.index('POPUP_A MAP '),'O2 rootA native ACK/SHM order')
|
||||
return dict(proof,configure=[x,y,w,h],ack=serial,buffer=int(buffer))
|
||||
|
||||
def expected_disconnect(raw,code):
|
||||
# WHY/CONTRACT/INVARIANT: retain raw no_memory code1 and client errno;
|
||||
# arbitrary errors, server death and missing EXIT never become normal close.
|
||||
errors=re.findall(r'wl_display#\d+\.error\([^\n]*?, (\d+), [^\n]*\)',raw)
|
||||
exits=re.findall(r'EXIT error=(\d+)',raw)
|
||||
need(code in (0,1) and errors==['1'] and len(exits)==1 and int(exits[0]) in (errno.ENOMEM,errno.EPROTO),'expected no-memory disconnect not established')
|
||||
return dict(exit=code,wire_error=errors,display_errno=exits,expected_enomem=True)
|
||||
|
||||
def retire_enomem(s):
|
||||
p=s.proc['owner'];raw=s.log('owner');proof=expected_disconnect(raw,p.poll())
|
||||
need(s.find('owner') is None,'disconnected owner remains in IPC')
|
||||
proof.update(name='owner',pid=p.pid,status='PASS',meaning='EXPECTED_ENOMEM_DISCONNECT; not ordinary close',raw_log=raw)
|
||||
s.closed.append(proof);s.record('owner-enomem-disconnect',**proof);return proof
|
||||
|
||||
def availability(s):
|
||||
need(s.comp.poll() is None,'critical compositor death')
|
||||
exe=Path(f'/proc/{s.comp.pid}/exe')
|
||||
need(exe.resolve()==Path(s.auth['binary']).resolve() and sha(exe)==s.auth['binary_sha256'],'critical compositor PID/executable changed')
|
||||
maps=Path(f'/proc/{s.comp.pid}/maps').read_text()
|
||||
paths={str(Path(line.split()[-1]).resolve()) for line in maps.splitlines() if len(line.split())>=6 and line.split()[-1].startswith('/') and 'libwlroots' in line.split()[-1]}
|
||||
need(paths=={s.auth['wlroots']} and sha(s.auth['wlroots'])==s.auth['wlroots_sha256'],'critical mapped wlroots changed')
|
||||
build=s.command_process(['readelf','-n',str(exe)])
|
||||
ids=re.findall(r'Build ID: ([0-9a-f]+)',build.stdout)
|
||||
need(build.returncode==0 and len(ids)==1 and ids[0]==s.auth['build_id'],'actual availability BuildID mismatch')
|
||||
need(not any('libscenefx' in line for line in maps.splitlines()),'unexpected dynamic SceneFX in OOM binary')
|
||||
return dict(pid=s.comp.pid,exe=str(exe.resolve()),sha256=sha(exe),build_id=ids[0],wlroots=list(paths),maps=maps,scenefx_link='static',swaysock=str(s.sock),captured_before_cleanup=True)
|
||||
|
||||
def availability_certificate(identity,expected,snapshot,neighbor_raw,buttons):
|
||||
# WHY: a final exit0 is not a post-injection live availability observation.
|
||||
# CONTRACT: exact surviving PID/exe/BuildID/maps + private IPC + fresh wire
|
||||
# press/release, saved before cleanup. INVARIANT: no superficial PASS receipt.
|
||||
need(identity.get('captured_before_cleanup') is True and identity['pid']==expected['pid'],'availability PID/time scope')
|
||||
need(identity['sha256']==expected['binary_sha256'] and identity['build_id']==expected['build_id'] and identity['exe']==expected['binary'],'availability ELF identity')
|
||||
need(identity['wlroots']==[expected['wlroots']] and expected['wlroots'] in identity['maps'] and 'libscenefx' not in identity['maps'],'availability mappings')
|
||||
sock=Path(identity['swaysock']);need(sock.parent.name.startswith('smx-') and sock.parent.parent in (Path('/tmp'),Path('/var/tmp')),'daily/foreign socket')
|
||||
need(snapshot['tree'].get('type')=='root' and snapshot['tree'].get('nodes') and len([o for o in snapshot['outputs'] if o.get('active')])==2,'live IPC tree/outputs absent')
|
||||
lines=buttons.get('reference',[]);events=[re.search(r'BUTTON ns=\d+ serial=(\d+) button=(\d+) state=(\d+)',line) for line in lines]
|
||||
need(len(events)==2 and all(events) and [list(map(int,e.groups()[1:])) for e in events]==[[273,1],[273,0]] and int(events[0][1])<int(events[1][1]),'neighbor fresh press/release absent')
|
||||
need(not any(v for name,v in buttons.items() if name!='reference'),'wrong client got neighbor input')
|
||||
for e in events:need(re.search(rf'wl_pointer#\d+\.button\({e[1]}, \d+, 273, {e[3]}\)',neighbor_raw),'neighbor actual wire absent')
|
||||
return dict(status='PARTIALLY_FEASIBLE',captured_before_cleanup=True,identity=identity,snapshot=snapshot,neighbor_buttons=buttons,neighbor_wire=neighbor_raw)
|
||||
|
||||
|
||||
def native_root_success(raw,pid):
|
||||
lines=[line for line in raw.splitlines() if 'SMX_OOM result ' in line and 'role=root_popup' in line and 'tree=' in line and 'failures=0' in line]
|
||||
need(len(lines)==1,'O2 rootA native success identity missing/ambiguous')
|
||||
fields=dict(re.findall(r'([a-z_]+)=([^ ]+)',lines[0]))
|
||||
need(fields.get('pid')==str(pid) and fields.get('tree') not in (None,'(nil)','0x0'),'O2 native rootA success')
|
||||
return fields
|
||||
|
||||
def popup_pixels(pair,baseline,failed=None,final=False):
|
||||
# WHY: exact H2 alone misses stale failed/retired popup pixels on H1.
|
||||
# CONTRACT/INVARIANT: inspect both outputs; only surviving A is allowed post B.
|
||||
exact(baseline,pair[1])
|
||||
forbidden=('POPUP_A','POPUP_B') if final else (failed,)
|
||||
for role in forbidden:
|
||||
need(role in ('POPUP_A','POPUP_B'),'failed role required')
|
||||
need(all(count(image,COLORS[role])==0 for image in pair),'failed/retired popup pixels '+role)
|
||||
return dict(status='PASS',outputs=['HEADLESS-1','HEADLESS-2'],forbidden=list(forbidden),H2='EXACT')
|
||||
|
||||
def surviving_root_input(s,post):
|
||||
# WHY: a neighbor click can dismiss the old grab before A input is measured.
|
||||
# CONTRACT/INVARIANT: fresh painted A, actual configure plus updated owner,
|
||||
# exclusive wire pair and pointer-local agreement, before outside dismissal.
|
||||
raw=s.log('owner');proof=mapped_root(raw);state=s.owner('owner')
|
||||
x,y,w,h=proof['configure'];rect=[state['origin'][0]+x,state['origin'][1]+y,w,h]
|
||||
point=solid_point(post[0],COLORS['POPUP_A'],s.outputs[0],s.frames['HEADLESS-1'],rect)
|
||||
tails,buttons=s.click('owner',point,'right')
|
||||
pair=s.delivery('owner',rect,point,tails,buttons,'POPUP_A')
|
||||
return dict(status='PARTIALLY_FEASIBLE',point=point,rectangle=rect,owner=state,wire=proof,pair=pair,tails=tails,buttons=buttons)
|
||||
|
||||
def run(s):
|
||||
s.closure_scenarios={'owner':'root_failure' if s.spec['id']=='OM-ROOT' else 'nested_failure'}
|
||||
cases.init_reference(s);s.debit('chain');s.launch_client('owner',s.spec['client'],s.spec['placement']);s.owner('owner')
|
||||
s.cmd(f'[con_id={s.find("owner")["id"]}] focus');state=s.calibrate('owner')
|
||||
baseline=cases.stable_reference(s);root_success=None
|
||||
if s.spec['id']=='OM-NESTED':
|
||||
# Owner origin is corroborated below by actual pointer-local delivery.
|
||||
origin=state['origin'];xy=[origin[0]+10,origin[1]+100]
|
||||
tails,buttons=s.click('owner',xy,'left',True)
|
||||
s.wait(lambda:'POPUP_A MAP ' in s.log('owner'));time.sleep(.15)
|
||||
root_success=native_root_success(s.log('compositor'),s.comp.pid)
|
||||
proof=mapped_root(s.log('owner'))
|
||||
s.delivery('owner',[*origin,420,320],xy,tails,buttons,'TOPLEVEL',proof['serial'])
|
||||
pre=s.pair_capture('oom-pre');popup_pixels(pre,baseline,'POPUP_A' if root_success is None else 'POPUP_B');before=s.snapshot('oom-before')
|
||||
if root_success is not None:
|
||||
need(count(pre[0],COLORS['POPUP_A'])>0 and count(pre[1],COLORS['POPUP_A'])==0,'nested precondition rootA sampled clip absent')
|
||||
s.save('oom-rootA-before-injection',dict(native_success=root_success,wire=proof,painted_H1=True,H2='EXACT',clip_scope='sampled; native generation NOT_PROVEN'))
|
||||
target='TOPLEVEL' if s.spec['id']=='OM-ROOT' else 'POPUP_A';popup='POPUP_A' if target=='TOPLEVEL' else 'POPUP_B'
|
||||
xy=solid_point(pre[0],COLORS[target],s.outputs[0],s.frames['HEADLESS-1'])
|
||||
stable=dict(startup=True,owner=True,neighbor=s.find('reference') is not None,reference_H2_twice=True,rootA=root_success is not None,rootA_native_success=root_success is not None)
|
||||
oom.arm(s,s.oom_env,s.auth['prospective'],stable)
|
||||
tails,buttons=s.click('owner',xy,'left',True)
|
||||
s.wait(lambda:'SMX_OOM ENOMEM ' in s.log('compositor'))
|
||||
wire=opening_before_map(s.log('owner'),popup,target)
|
||||
native=oom.native_failure(s.log('compositor'),s.oom_env,s.comp.pid)
|
||||
results=[line for line in s.log('compositor').splitlines() if 'SMX_OOM result ' in line and 'role='+s.oom_env['SMX_OOM_ROLE']+' ' in line]
|
||||
need(len(results)==1 and 'tree=(nil)' in results[0] and 'failures=1' in results[0],'native NULL caller outcome absent')
|
||||
ident=availability(s);s.save('oom-availability-identity',ident);after=s.snapshot('oom-after');s.save('oom-availability-ipc',dict(identity=ident,snapshot=after,captured_before_cleanup=True))
|
||||
need(popup+' MAP ' not in s.log('owner'),'failed popup unexpectedly mapped')
|
||||
owner_raw_exit=s.proc['owner'].poll();disconnected=None
|
||||
if owner_raw_exit is None:
|
||||
time.sleep(.15);owner_raw_exit=s.proc['owner'].poll()
|
||||
if owner_raw_exit is not None:disconnected=retire_enomem(s)
|
||||
post=s.pair_capture('oom-post');post_pixels=popup_pixels(post,baseline,popup)
|
||||
old_root=dict(status='NOT_EXERCISED',reason='O1 has no old rootA' if root_success is None else ('EXPECTED_ENOMEM_DISCONNECT' if disconnected else 'ROOT_A_DISMISSED'),raw=s.log('owner'))
|
||||
if root_success is not None and disconnected is None and 'POPUP_A DESTROY ' not in s.log('owner') and 'POPUP_A POPUP_DONE ' not in s.log('owner'):
|
||||
need(count(post[0],COLORS['POPUP_A'])>0 and count(post[1],COLORS['POPUP_A'])==0,'surviving rootA sampled clipping')
|
||||
old_root=surviving_root_input(s,post);old_root['native_success']=root_success
|
||||
s.save('oom-rootA-post-input',old_root)
|
||||
neighbor=s.find('reference');need(neighbor is not None,'surviving neighbor absent')
|
||||
xy=solid_point(post[1],(48,48,56),s.outputs[1],s.frames['HEADLESS-2'])
|
||||
tails,buttons=s.click('reference',xy)
|
||||
# WHY/CONTRACT/INVARIANT: a surviving real popup grab may consume the
|
||||
# outside click to dismiss; only a measured dismissal permits a fresh
|
||||
# neighbor click. This does not claim universal click-through.
|
||||
if not buttons.get('reference'):
|
||||
need('POPUP_DONE ' in tails.get('owner',''),'neighbor input missing without measured outside dismissal')
|
||||
s.save('oom-neighbor-outside-dismissal',dict(tails=tails,buttons=buttons))
|
||||
tails,buttons=s.click('reference',xy)
|
||||
lines=buttons.get('reference',[])
|
||||
events=[re.search(r'BUTTON ns=\d+ serial=(\d+) button=(\d+) state=(\d+)',line) for line in lines]
|
||||
need(len(events)==2 and all(events) and [list(map(int,e.groups()[1:])) for e in events]==[[273,1],[273,0]],'fresh neighbor button pair')
|
||||
for e in events:need(re.search(rf'wl_pointer#\d+\.button\({e[1]}, \d+, 273, {e[3]}\)',tails['reference']) is not None,'neighbor real wire')
|
||||
need(not any(v for k,v in buttons.items() if k!='reference'),'neighbor button not exclusive')
|
||||
live=availability_certificate(ident,dict(s.auth,pid=s.comp.pid),after,tails['reference'],buttons)
|
||||
s.save('oom-availability-live',live)
|
||||
if disconnected is None:s.close_client('owner',scenario=s.closure_scenarios['owner'])
|
||||
final=s.pair_capture('oom-final');final_pixels=popup_pixels(final,baseline,final=True)
|
||||
s.save('oom-measurements',dict(post_pixels=post_pixels,final_pixels=final_pixels,native=native,wire=wire,rootA_success=root_success,old_rootA=old_root,before=before,after=after,availability=ident,owner_raw_exit=owner_raw_exit,owner_disconnect=disconnected,neighbor_buttons=buttons,H2_pre_post_final='EXACT',captures=8,closure_scenario=s.closure_scenarios['owner'],complete_damage='NOT_PROVEN',native_stale_listeners='NOT_PROVEN',native_no_partial_popup='NOT_PROVEN; IPC has no popup-node schema',atomic_generation='NOT_PROVEN'))
|
||||
return 'PARTIALLY_FEASIBLE'
|
||||
21
tooling/probes/step20c-corec-v2-followup/graphics/pixels.py
Normal file
21
tooling/probes/step20c-corec-v2-followup/graphics/pixels.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""Exact binary RGB masks with explicit output origin, clip and occlusion.
|
||||
|
||||
Screenshots prove color/visible geometry only, never native-buffer identity.
|
||||
"""
|
||||
from PIL import Image,ImageChops,ImageDraw
|
||||
from model import *
|
||||
COLORS=dict(TOPLEVEL=(35,68,204),POPUP_A=(224,192,32),POPUP_B=(32,208,160))
|
||||
|
||||
|
||||
def color_geometry(im,target,rect,output,clip,occluders=()):
|
||||
require(im.size==(output.rectangle.width,output.rectangle.height),'replay-output-size')
|
||||
visible=intersect(rect,clip) if clip else rect
|
||||
expected=Image.new('L',im.size,0);draw=ImageDraw.Draw(expected)
|
||||
box=to_png(visible,output)
|
||||
if box.width and box.height:draw.rectangle((box.x,box.y,box.end[0]-1,box.end[1]-1),fill=255)
|
||||
for occ in occluders:
|
||||
ob=to_png(occ,output)
|
||||
if ob.width and ob.height:draw.rectangle((ob.x,ob.y,ob.end[0]-1,ob.end[1]-1),fill=0)
|
||||
actual=Image.new('L',im.size);actual.putdata([255 if px==COLORS[target] else 0 for px in im.get_flattened_data()])
|
||||
require(ImageChops.difference(expected,actual).getbbox() is None,'replay-paint-'+target)
|
||||
return dict(count=sum(actual.histogram()[1:]),bbox=actual.getbbox())
|
||||
17
tooling/probes/step20c-corec-v2-followup/graphics/plans.py
Normal file
17
tooling/probes/step20c-corec-v2-followup/graphics/plans.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
"""EXPERIMENTAL new-ticket intent; no plan grants admission by itself."""
|
||||
from copy import deepcopy
|
||||
CAPS={'FS-P':12,'FS-C':16,'DY-C':16,'OM-ROOT':10,'OM-NESTED':10,'GPU-G3':12,'OM-EXTRA':12,'GPU-CTRL':12}
|
||||
LIMITS=dict(runs=8,captures=110,captures_per_run=18,duration_s=150)
|
||||
H=[dict(name='HEADLESS-1',logical=[-800,0,800,600],mode=[800,600],scale=1,transform='normal'),dict(name='HEADLESS-2',logical=[0,0,800,600],mode=[800,600],scale=1,transform='normal')]
|
||||
def config(spec):
|
||||
return '\n'.join(f"output {o['name']} resolution {o['mode'][0]}x{o['mode'][1]} scale {o['scale']} transform {o['transform']} position {o['logical'][0]} {o['logical'][1]}" for o in spec['outputs'])+'\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\nshadows disable\nblur disable\n'
|
||||
RUNS={rid:dict(id=rid,binary='pristine' if rid=='FS-P' else 'core',mode='pristine' if rid=='FS-P' else 'mosaic',outputs=deepcopy(H),client='nested-client',placement='static_cross',axis=0,boundary=0,capture_limit=cap,action_limits=dict(spawn=12,tool=160,configuration=80,mutation=26,move=24,click=12,grab=4,camera=6,chain=2,source_marker=1),admission='INDEPENDENT_REVIEW_REQUIRED') for rid,cap in CAPS.items()}
|
||||
for rid in ('FS-P','FS-C'):RUNS[rid].update(client='fullscreen-client',placement='fullscreen_cross')
|
||||
for rid in ('OM-ROOT','OM-NESTED','OM-EXTRA'):
|
||||
RUNS[rid]['binary']='oom';RUNS[rid]['action_limits']['hook']=1
|
||||
for rid in ('GPU-G3','GPU-CTRL'):
|
||||
RUNS[rid].update(binary='gpu',admission='NOT_ADMITTED',reason='B4 and separately reviewed GPU adapter required')
|
||||
RUNS[rid]['action_limits']['hook']=3
|
||||
RUNS['OM-EXTRA'].update(admission='NOT_ADMITTED',reason='Distinct question, handler and additional independent review required')
|
||||
for spec in RUNS.values():spec['config']=config(spec)
|
||||
assert len(RUNS)==8 and sum(CAPS.values())==100 and max(CAPS.values())<=18
|
||||
124
tooling/probes/step20c-corec-v2-followup/graphics/predicates.py
Normal file
124
tooling/probes/step20c-corec-v2-followup/graphics/predicates.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Shared offline/live predicates: facts precede scientific classification.
|
||||
|
||||
CONTRACT: full RGB comparisons have no mask/tolerance; grabs require real logs.
|
||||
INVARIANT: dismissal, client destruction, clipping and layout remain distinct.
|
||||
"""
|
||||
import re
|
||||
from model import Rect,Space,Provenance,Output,popup,crossing,local_clip_to_global,require
|
||||
from pixels import color_geometry,COLORS
|
||||
from PIL import Image,ImageChops
|
||||
|
||||
class Defect(RuntimeError):
|
||||
"""A directly measured scientific contract failure, distinct from fixture ambiguity."""
|
||||
|
||||
def paint(im,target):
|
||||
im=im.convert('RGB');assert im.size==(800,600)
|
||||
mask=Image.new('L',im.size);mask.putdata([255 if p==COLORS[target] else 0 for p in im.get_flattened_data()])
|
||||
return dict(count=sum(mask.histogram()[1:]),bbox=mask.getbbox())
|
||||
|
||||
def point(im,target,origin):
|
||||
box=paint(im,target)['bbox'];assert box,('no measured paint',target)
|
||||
# WHY: A may be occluded by B; bbox center alone is not a valid hit point.
|
||||
x1,y1,x2,y2=box
|
||||
for y in range(y1+10,y2-10,5):
|
||||
for x in range(x1+10,x2-10,5):
|
||||
if all(im.getpixel((xx,yy))==COLORS[target] for yy in range(y-8,y+9) for xx in range(x-8,x+9)):
|
||||
return [x+origin,y]
|
||||
raise AssertionError('no solid measured interior '+target)
|
||||
|
||||
def equal(a,b):
|
||||
assert a.size==b.size==(800,600)
|
||||
rows=[(i%800,i//800,x,y) for i,(x,y) in enumerate(zip(a.get_flattened_data(),b.get_flattened_data())) if x!=y]
|
||||
return dict(equal=not rows,changed=len(rows),bbox=ImageChops.difference(a,b).getbbox(),pixels=rows)
|
||||
|
||||
def reference(a,b):
|
||||
result=equal(a,b)
|
||||
assert result['equal'] and all(c==(48,48,56) for c in a.get_flattened_data()),'unstable reference'
|
||||
return result
|
||||
|
||||
def configured(text,name):
|
||||
rows=re.findall(rf'{name} ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',text)
|
||||
assert rows,(name,'no configure')
|
||||
return list(map(int,rows[-1]))
|
||||
|
||||
def geometry(text,origin,provenance=None,axis=0,boundary=0):
|
||||
a=configured(text,'POPUP_A');b=configured(text,'POPUP_B')
|
||||
require(provenance is not None,'popup-owner-provenance-required')
|
||||
p=provenance
|
||||
top=Rect(Space.GLOBAL,*origin,0,0,p)
|
||||
ids={n:list(map(int,re.search(rf'{n} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text).groups())) for n in ('TOPLEVEL','POPUP_A','POPUP_B')}
|
||||
result={}
|
||||
parent=top
|
||||
for key,name,parent_name,c in [('A','POPUP_A','TOPLEVEL',a),('B','POPUP_B','POPUP_A',b)]:
|
||||
parent_id=int(re.search(rf'{name} CREATE .*parent_xdg=(\d+)',text)[1])
|
||||
ack=int(re.findall(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text)[-1])
|
||||
parent=popup(parent,Rect(Space.POPUP,*c,p),parent_id,ids[parent_name][1],ack,name+' MAP ' in text)
|
||||
result[key]=parent.values()
|
||||
result.update(configured_A=a,configured_B=b,origin=origin,provenance=dict(owner=p.owner,workspace=p.workspace,generation=p.generation,evidence=p.evidence,state=p.state),
|
||||
crossing_axis=axis,crossing_boundary=boundary,crossing={key:(result[key][axis]<boundary<result[key][axis]+result[key][axis+2]) for key in ('A','B')})
|
||||
return result
|
||||
|
||||
def mapped(text):
|
||||
for name in ['POPUP_A','POPUP_B']:
|
||||
assert text.count(name+' MAP ')==1 and name+' POPUP_DONE ' not in text and name+' DESTROY ' not in text,(name,'not mapped')
|
||||
|
||||
def protocol(text):
|
||||
assert not re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',text,re.I),'protocol error'
|
||||
mapped(text)
|
||||
ids={}
|
||||
for name in ['TOPLEVEL','POPUP_A','POPUP_B']:
|
||||
m=re.search(rf'{name} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text);assert m
|
||||
ids[name]=list(map(int,m.groups()))
|
||||
assert len({n for v in ids.values() for n in v})==9,'object identities reused'
|
||||
grabs=[]
|
||||
for name,parent in [('POPUP_A','TOPLEVEL'),('POPUP_B','POPUP_A')]:
|
||||
create=re.search(rf'{name} CREATE .*parent={parent} parent_xdg=(\d+)',text);assert create
|
||||
assert int(create[1])==ids[parent][1], 'wrong XDG parent'
|
||||
debug_parent=re.search(rf'xdg_surface#{ids[name][1]}\.get_popup\(new id xdg_popup#{ids[name][2]}, xdg_surface#{ids[parent][1]}, xdg_positioner#\d+\)',text)
|
||||
require(debug_parent is not None,'debug-parent-request')
|
||||
m=re.search(rf'{name} GRAB_REQUEST origin=wl_pointer.button target={parent} serial=(\d+) used=(\d+)',text);assert m
|
||||
serial,used=map(int,m.groups());assert serial==used and 0<serial<=0xffffffff
|
||||
button=f'POINTER_BUTTON target={parent} serial={serial} button=272 state=1'
|
||||
assert text.index(button)<m.start(),'grab serial not from earlier button'
|
||||
debug_button=re.search(rf'wl_pointer#\d+\.button\({serial}, \d+, 272, 1\)',text)
|
||||
require(debug_button is not None and debug_button.start()<m.start(),'debug-real-button-serial')
|
||||
role=text.index(name+' ROLE_CONFIGURE ');cfg=re.search(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text);assert cfg
|
||||
mp=text.index(name+' MAP ')
|
||||
assert m.start()<role<cfg.start()<mp,'configure ACK map order'
|
||||
popup_role=ids[name][2];xdg=ids[name][1];wl=ids[name][0]
|
||||
# WAYLAND_DEBUG is independent corroboration, not just fixture claims.
|
||||
request=re.search(rf'xdg_popup#{popup_role}\.grab\(wl_seat#\d+, {serial}\)',text);assert request
|
||||
ack=re.search(rf'xdg_surface#{xdg}\.ack_configure\({cfg[1]}\)',text);assert ack
|
||||
attach=re.search(rf'wl_surface#{wl}\.attach\(',text);assert attach
|
||||
commits=list(re.finditer(rf'wl_surface#{wl}\.commit\(\)',text));assert len(commits)>=2
|
||||
assert request.start()<commits[0].start()<role<ack.start()<attach.start()<commits[1].start()<mp
|
||||
actual=configured(text,name)
|
||||
require(re.search(rf'xdg_popup#{popup_role}\.configure\({actual[0]}, {actual[1]}, {actual[2]}, {actual[3]}\)',text) is not None,'debug-popup-configure')
|
||||
require(re.search(rf'xdg_surface#{xdg}\.configure\({cfg[1]}\)',text) is not None,'debug-xdg-configure')
|
||||
native_attach=re.search(rf'wl_surface#{wl}\.attach\(wl_buffer#(\d+), 0, 0\)',text[ack.start():mp])
|
||||
require(native_attach is not None,'popup-native-attach')
|
||||
native_buffer=int(native_attach[1])
|
||||
native_create=re.search(rf'create_buffer\(new id wl_buffer#{native_buffer}, 0, {actual[2]}, {actual[3]}, {actual[2]*4}, 1\)',text[role:mp])
|
||||
require(native_create is not None,'popup-native-configured-size')
|
||||
if parent=='POPUP_A':assert text.index('POPUP_A MAP ')<text.index(button)
|
||||
grabs.append(dict(popup=name,parent=parent,serial=serial,used=used,origin='wl_pointer.button',ids=ids[name],ack=int(cfg[1]),native_buffer=native_buffer,native_size=actual[2:]))
|
||||
assert grabs[0]['serial']<grabs[1]['serial'], 'distinct ordered serials'
|
||||
return dict(ids=ids,grabs=grabs,hierarchy='TOPLEVEL -> POPUP_A -> POPUP_B',status='PASS')
|
||||
|
||||
def pair(lines,target):
|
||||
events=[]
|
||||
for line in lines:
|
||||
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line);assert m
|
||||
events.append([m[1],*map(int,m.groups()[1:])])
|
||||
if len(events)!=2 or any(e[0]!=target for e in events) or [e[2:] for e in events]!=[[273,1],[273,0]]:
|
||||
raise Defect('wrong actual button recipient/pair: '+str(events))
|
||||
assert 0<events[0][1]<events[1][1]<=0xffffffff
|
||||
return events
|
||||
|
||||
def dismissal(text,buttons):
|
||||
if any('target=POPUP_A ' in b or 'target=POPUP_B ' in b for b in buttons):
|
||||
raise Defect('invisible popup received actual button')
|
||||
done=re.findall(r'(POPUP_[AB]) POPUP_DONE ',text)
|
||||
destroyed=re.findall(r'(POPUP_[AB]) DESTROY ',text)
|
||||
assert done==['POPUP_B','POPUP_A'] and destroyed==['POPUP_B','POPUP_A'],('dismissal order',done,destroyed)
|
||||
return dict(done=done,destroyed=destroyed,invisible_buttons=buttons,status='PASS',meaning='outside dismissal distinct from invisible hit')
|
||||
25
tooling/probes/step20c-corec-v2-followup/graphics/prepare.py
Normal file
25
tooling/probes/step20c-corec-v2-followup/graphics/prepare.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate protocols and compile C17 clients, without executing any client."""
|
||||
import argparse,json,shlex,subprocess
|
||||
from pathlib import Path
|
||||
from gate import canonical_lab,clean_environment,durable,sha,need,disable_dump
|
||||
|
||||
def prepare(lab):
|
||||
disable_dump();lab=canonical_lab(lab);src=Path(__file__).resolve().parent;build=lab/"followup-work/client-build";build.mkdir(parents=True,exist_ok=False)
|
||||
env=clean_environment(lab);commands=[]
|
||||
def invoke(argv):
|
||||
r=subprocess.run(list(map(str,argv)),env=env,capture_output=True,text=True,timeout=30)
|
||||
commands.append(dict(argv=list(map(str,argv)),exit=r.returncode,stdout=r.stdout,stderr=r.stderr))
|
||||
durable(lab/"followup-work/client-compile.json",dict(status="PENDING" if r.returncode==0 else "FAIL",commands=commands,runtime="NOT_RUN"))
|
||||
need(r.returncode==0,"client build command failed");return r.stdout.strip()
|
||||
versions={name:invoke([name,"--version"]) for name in ("cc","wayland-scanner","pkg-config")}
|
||||
flags=shlex.split(invoke(["pkg-config","--cflags","--libs","wayland-client"]))
|
||||
for protocol,xml in (("xdg-shell","xdg-shell.xml"),("virtual-pointer","wlr-virtual-pointer-unstable-v1.xml")):
|
||||
invoke(["wayland-scanner","client-header",src/"protocols"/xml,build/(protocol+"-client.h")])
|
||||
invoke(["wayland-scanner","private-code",src/"protocols"/xml,build/(protocol+"-protocol.c")])
|
||||
for name,protocol in (("fullscreen-client","xdg-shell"),("nested-client","xdg-shell"),("vertical-client","xdg-shell"),("saved-client","xdg-shell"),("static-reference","xdg-shell"),("pointer-daemon","virtual-pointer")):
|
||||
invoke(["cc","-std=c17","-O2","-Wall","-Wextra","-I",build,src/"clients"/(name+".c"),build/(protocol+"-protocol.c"),"-o",build/name,*flags])
|
||||
durable(lab/"followup-work/client-compile.json",dict(status="PASS",commands=commands,versions=versions,inputs={str(p.relative_to(src)):sha(p) for folder in ("clients","protocols") for p in (src/folder).iterdir()},generated={str(p.relative_to(build)):sha(p) for p in build.iterdir()},runtime="NOT_RUN"))
|
||||
|
||||
if __name__=="__main__":
|
||||
p=argparse.ArgumentParser();p.add_argument("--lab",required=True);a=p.parse_args();prepare(a.lab)
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_virtual_pointer_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2019 Josef Gajdusek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_virtual_pointer_v1" version="2">
|
||||
<description summary="virtual pointer">
|
||||
This protocol allows clients to emulate a physical pointer device. The
|
||||
requests are mostly mirror opposites of those specified in wl_pointer.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_axis" value="0"
|
||||
summary="client sent invalid axis enumeration value" />
|
||||
<entry name="invalid_axis_source" value="1"
|
||||
summary="client sent invalid axis source enumeration value" />
|
||||
</enum>
|
||||
|
||||
<request name="motion">
|
||||
<description summary="pointer relative motion event">
|
||||
The pointer has moved by a relative amount to the previous request.
|
||||
|
||||
Values are in the global compositor space.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="dx" type="fixed" summary="displacement on the x-axis"/>
|
||||
<arg name="dy" type="fixed" summary="displacement on the y-axis"/>
|
||||
</request>
|
||||
|
||||
<request name="motion_absolute">
|
||||
<description summary="pointer absolute motion event">
|
||||
The pointer has moved in an absolute coordinate frame.
|
||||
|
||||
Value of x can range from 0 to x_extent, value of y can range from 0
|
||||
to y_extent.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="x" type="uint" summary="position on the x-axis"/>
|
||||
<arg name="y" type="uint" summary="position on the y-axis"/>
|
||||
<arg name="x_extent" type="uint" summary="extent of the x-axis"/>
|
||||
<arg name="y_extent" type="uint" summary="extent of the y-axis"/>
|
||||
</request>
|
||||
|
||||
<request name="button">
|
||||
<description summary="button event">
|
||||
A button was pressed or released.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="button" type="uint" summary="button that produced the event"/>
|
||||
<arg name="state" type="uint" enum="wl_pointer.button_state" summary="physical state of the button"/>
|
||||
</request>
|
||||
|
||||
<request name="axis">
|
||||
<description summary="axis event">
|
||||
Scroll and other axis requests.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
|
||||
<arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
|
||||
</request>
|
||||
|
||||
<request name="frame">
|
||||
<description summary="end of a pointer event sequence">
|
||||
Indicates the set of events that logically belong together.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="axis_source">
|
||||
<description summary="axis source event">
|
||||
Source information for scroll and other axis.
|
||||
</description>
|
||||
<arg name="axis_source" type="uint" enum="wl_pointer.axis_source" summary="source of the axis event"/>
|
||||
</request>
|
||||
|
||||
<request name="axis_stop">
|
||||
<description summary="axis stop event">
|
||||
Stop notification for scroll and other axes.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="the axis stopped with this event"/>
|
||||
</request>
|
||||
|
||||
<request name="axis_discrete">
|
||||
<description summary="axis click event">
|
||||
Discrete step information for scroll and other axes.
|
||||
|
||||
This event allows the client to extend data normally sent using the axis
|
||||
event with discrete value.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
|
||||
<arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
|
||||
<arg name="discrete" type="int" summary="number of steps"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor" since="1">
|
||||
<description summary="destroy the virtual pointer object"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_virtual_pointer_manager_v1" version="2">
|
||||
<description summary="virtual pointer manager">
|
||||
This object allows clients to create individual virtual pointer objects.
|
||||
</description>
|
||||
|
||||
<request name="create_virtual_pointer">
|
||||
<description summary="Create a new virtual pointer">
|
||||
Creates a new virtual pointer. The optional seat is a suggestion to the
|
||||
compositor.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
|
||||
<arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor" since="1">
|
||||
<description summary="destroy the virtual pointer manager"/>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
<request name="create_virtual_pointer_with_output" since="2">
|
||||
<description summary="Create a new virtual pointer">
|
||||
Creates a new virtual pointer. The seat and the output arguments are
|
||||
optional. If the seat argument is set, the compositor should assign the
|
||||
input device to the requested seat. If the output argument is set, the
|
||||
compositor should map the input device to the requested output.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
File diff suppressed because it is too large
Load diff
63
tooling/probes/step20c-corec-v2-followup/graphics/run.py
Normal file
63
tooling/probes/step20c-corec-v2-followup/graphics/run.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
"""WHY/CONTRACT/INVARIANT: one new ID, reviewed source, no retry or auto-review."""
|
||||
import argparse,json,os,time
|
||||
from pathlib import Path
|
||||
from gate import Ledger,need,disable_dump,validate_review
|
||||
from plans import RUNS
|
||||
from engine import Session,storage_guard
|
||||
import cases
|
||||
|
||||
|
||||
def normal_remaining(s):
|
||||
for name in list(s.proc):
|
||||
if name not in ('owner','compositor','pointer') and s.proc[name].poll() is None:s.close_client(name,'close')
|
||||
|
||||
|
||||
def execute(rid,lab,manifest,review,publication):
|
||||
disable_dump();storage_guard(Path(lab))
|
||||
# WHY: missing replay inputs must not consume a run. The independent gate is
|
||||
# validated again inside admit, then before every action. Never repair a freeze.
|
||||
validate_review(lab,manifest,review,publication,rid)
|
||||
need(not (Path(lab)/'graphics-runs'/rid).exists(),'existing run directory; no overwrite/retry')
|
||||
auth=Ledger(lab).admit(rid,manifest,review,publication)
|
||||
s=None;failure=None;status='INCONCLUSIVE'
|
||||
try:
|
||||
if rid=='GPU-G3':
|
||||
from gpu_handler import GPUSceneSession,run as gpu_run
|
||||
s=GPUSceneSession(RUNS[rid],auth);status=gpu_run(s)
|
||||
else:
|
||||
s=Session(RUNS[rid],auth);s.start()
|
||||
if rid=='GPU-G3':pass
|
||||
elif rid in ('FS-P','FS-C'):
|
||||
from fullscreen import run,comparable
|
||||
status=run(s)
|
||||
if rid=='FS-C' and status=='PARTIALLY_FEASIBLE':
|
||||
control=s.lab/'graphics-runs/FS-P/run/fullscreen-comparability.json'
|
||||
need(control.is_file(),'FS-P measured comparable source missing')
|
||||
s.save('fullscreen-comparison',comparable(json.loads(control.read_text()),json.loads((s.run/'fullscreen-comparability.json').read_text())))
|
||||
elif rid=='DY-C':status=cases.dynamic_campaign(s)
|
||||
elif rid in ('OM-ROOT','OM-NESTED'):
|
||||
from oom_handler import run
|
||||
status=run(s)
|
||||
else:raise ValueError('NOT_ADMITTED: independent diagnostic adapter absent')
|
||||
normal_remaining(s)
|
||||
except Exception as exc:
|
||||
failure=f'{type(exc).__name__}: {exc}';status='INCONCLUSIVE'
|
||||
if s is not None:
|
||||
s.save('partial-failure',dict(status=status,failure=failure,events=s.events,captures_observed=sum(e['kind']=='capture' for e in s.events),compositor_exit=s.comp.poll() if hasattr(s,'comp') else None,owner_exit=s.proc['owner'].poll() if 'owner' in s.proc else None,critical_or_unresolved=True,no_retry=True))
|
||||
finally:
|
||||
if s is None:Ledger(lab).complete(auth,dict(status='INCONCLUSIVE',failure=failure,session_constructed=False))
|
||||
else:
|
||||
try:status=s.cleanup(status,failure)
|
||||
except Exception as exc:
|
||||
# Cleanup exceptions remain failures; autonomous watchdog still owns groups.
|
||||
failure=(failure or '')+f'; cleanup {type(exc).__name__}: {exc}'
|
||||
Ledger(lab).complete(auth,dict(status='INCONCLUSIVE',failure=failure,scientific_cleanup=False))
|
||||
status='INCONCLUSIVE'
|
||||
return dict(status=status,failure=failure,runner_exit=0 if status=='PARTIALLY_FEASIBLE' else 1)
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser(description='One independent new-ticket run, never retry/all')
|
||||
p.add_argument('run_id',choices=RUNS)
|
||||
for name in ('lab','manifest','review','publication'):p.add_argument('--'+name,required=True)
|
||||
a=p.parse_args();result=execute(a.run_id,a.lab,a.manifest,a.review,a.publication);print(json.dumps(result));raise SystemExit(result['runner_exit'])
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Run exact offline validators; report is never an independent acceptance."""
|
||||
import argparse,copy,importlib.util,json,sys,unittest
|
||||
from pathlib import Path
|
||||
from gate import canonical_lab,clean_environment,disable_dump,durable,source_files,digest,sha
|
||||
|
||||
def historical_replay(directory):
|
||||
import cases
|
||||
root=Path(directory);a=root/'resized-back-snapshot.json';b=root/'migrated-H2-snapshot.json'
|
||||
before=json.loads(a.read_text());after=json.loads(b.read_text());previous=cases.dynamic_geometry(before)
|
||||
current=cases.dynamic_geometry(after,previous,'migration',previous['focused'])
|
||||
mutant=copy.deepcopy(after)
|
||||
from ipc_predicates import workspace
|
||||
workspace(mutant['tree'],'1')['mosaic']['rows'][0]['scroll_x']=previous['scroll']
|
||||
try:cases.dynamic_geometry(mutant,previous,'migration',previous['focused'])
|
||||
except ValueError:negative=True
|
||||
else:raise AssertionError('stale historical C7 camera accepted')
|
||||
return dict(status='PASS',scope='historical IPC replay only; no runtime upgrade',inputs={str(a):sha(a),str(b):sha(b)},positive=current,negative_stale_camera_rejected=negative)
|
||||
|
||||
def validate(lab,historical=None):
|
||||
disable_dump();lab=canonical_lab(lab);clean_environment(lab)
|
||||
path=Path(__file__).parent/'offline/test_followup.py';spec=importlib.util.spec_from_file_location('test_followup',path);module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
|
||||
suite=unittest.defaultTestLoader.loadTestsFromModule(module);result=unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
replay=historical_replay(historical) if historical else dict(status='NOT_RUN',reason='historical replay input not supplied')
|
||||
receipt=dict(status='PASS' if result.wasSuccessful() else 'FAIL',tests_run=result.testsRun,failures=len(result.failures)+len(result.errors),skipped=len(result.skipped),source_digest=digest(source_files()),runtime='NOT_RUN',real_reservations=0,independent_review='NOT_PROVEN',historical_C7=replay)
|
||||
durable(lab/'followup-work/offline-tests.json',receipt);return result.wasSuccessful()
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('--lab',required=True);p.add_argument('--historical-c7');a=p.parse_args();raise SystemExit(0 if validate(a.lab,a.historical_c7) else 1)
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
"""Imported live/offline scientific predicates, not a parallel test model."""
|
||||
import re
|
||||
from geometry import need,exact,crossing,expected_outputs
|
||||
from predicates import protocol
|
||||
|
||||
def client_exit(log,code,scenario='observed'):
|
||||
# WHY: root OOM creates A, never B. CONTRACT: hierarchy-sensitive close;
|
||||
# no normal error0 close is substituted for a wire-proven no_memory path.
|
||||
# INVARIANT: every created role is destroyed exactly once, child first.
|
||||
need(scenario in ('observed','root_failure','nested_failure','nested','toplevel'),'unknown closure scenario')
|
||||
if 'TOPLEVEL CREATE ' in log:
|
||||
created=re.findall(r'^(POPUP_[AB]|TOPLEVEL) CREATE ',log,re.M)
|
||||
need(len(created)==len(set(created)) and created and created[0]=='TOPLEVEL','ambiguous created hierarchy')
|
||||
expected={'root_failure':['TOPLEVEL','POPUP_A'],'nested_failure':['TOPLEVEL','POPUP_A','POPUP_B'],'nested':['TOPLEVEL','POPUP_A','POPUP_B'],'toplevel':['TOPLEVEL']}.get(scenario,created)
|
||||
need(created==expected,'scenario created hierarchy differs')
|
||||
order=re.findall(r'^(POPUP_[AB]|TOPLEVEL) DESTROY',log,re.M)
|
||||
need(order==list(reversed(created)),'actual created hierarchy child-first destruction')
|
||||
failed={'root_failure':'POPUP_A','nested_failure':'POPUP_B'}.get(scenario)
|
||||
if failed:need(failed+' MAP ' not in log,'failed popup mapped despite NULL')
|
||||
if scenario=='nested_failure':need('POPUP_A MAP ' in log,'nested failure without mapped root')
|
||||
need('CLOSE_RECEIVED' in log and log.index('CLOSE_RECEIVED')<log.index('TOPLEVEL DESTROY'),'requested close before top destruction')
|
||||
# Wire destroys bind to the objects actually created, even if popup_done
|
||||
# legitimately retired a popup before the later explicit owner close.
|
||||
for name in created:
|
||||
match=re.search(rf'{name} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',log);need(match is not None,'created role IDs absent')
|
||||
wl,xdg,role=map(int,match.groups());stem='xdg_toplevel' if name=='TOPLEVEL' else 'xdg_popup'
|
||||
need(all(log.count(f'{obj}#{ident}.destroy()')==1 for obj,ident in ((stem,role),('xdg_surface',xdg),('wl_surface',wl))),'actual role wire destruction absent')
|
||||
need('CLOSE_RECEIVED' in log,'explicit close receipt absent')
|
||||
releases=re.findall(r'SHM_CLOSURE created=(\d+) released=(\d+)',log)
|
||||
need(len(releases)==1 and int(releases[0][0])>0 and releases[0][0]==releases[0][1],'client SHM release incomplete/unobserved')
|
||||
errors=re.findall(r'EXIT[^\n]*error=(\d+)',log)
|
||||
need(code==0 and len(errors)==1 and errors==['0'],'normal client exit/error differs')
|
||||
need(not re.search(r'protocol error|wl_display[^\n]*\.error\(',log,re.I),'wire error cannot be ordinary close')
|
||||
return dict(status='PASS',scenario=scenario,exit=code,wire_error=False)
|
||||
|
||||
def window(start,end,events):
|
||||
need(start<end and len(events)==1 and start<=events[0]<=end,'ambiguous event window')
|
||||
def owner(node,owner_id,workspace_id,path):
|
||||
need(node['id']==owner_id and [n['id'] for n in path if n['type']=='workspace']==[workspace_id],'wrong owner ancestry')
|
||||
def rgb(reference,actual,masked=False):
|
||||
need(not masked,'H2 occlusion/mask forbidden');return exact(reference,actual)
|
||||
def grab(log):return protocol(log)
|
||||
def applied(geometry,paint_origin,pointer_origin,state):
|
||||
need(state=='applied' and geometry==paint_origin==pointer_origin,'pending/uncorroborated origin')
|
||||
def crosses(rect,axis,boundary):need(crossing(rect,axis,boundary),'crossing not exercised')
|
||||
def shadow(before,after):need(before.tobytes()!=after.tobytes(),'shadow not exercised')
|
||||
def saved_identity(identity,binary_sha):
|
||||
need(identity['binary_sha256']==binary_sha,'wrong binary saved identity')
|
||||
need(identity.get('native') is True,'saved identity not observable')
|
||||
|
||||
def camera_eligible(path,ws):
|
||||
# Null flags are legitimate on IPC descriptors; branch membership remains
|
||||
# authoritative even when a child omits its floating flag.
|
||||
floating=any((n.get('floating') or 'auto_off').endswith('on') for n in path)
|
||||
floating |= any(c in p.get('floating_nodes',[]) for p,c in zip(path,path[1:]))
|
||||
return ws.get('layout')=='mosaic' and not floating and not any(n.get('type')=='con' and n.get('fullscreen_mode') for n in path)
|
||||
|
||||
def owner_context(node,path,pid,workspace_name,output,layout):
|
||||
need(path and path[-1]==node and node.get('pid')==pid,'owner path/pid mismatch')
|
||||
ws=[n for n in path if n.get('type')=='workspace'];outs=[n for n in path if n.get('type')=='output']
|
||||
need(len(ws)==len(outs)==1,'owner ancestry must include workspace/output')
|
||||
need(ws[0].get('name')==workspace_name and ws[0].get('output')==output and outs[0].get('name')==output and ws[0].get('layout')==layout,'wrong mapped workspace/output/layout')
|
||||
need(not any(c in p.get('floating_nodes',[]) for p,c in zip(path,path[1:])),'owner floating ancestry')
|
||||
need(not any((n.get('floating') or 'auto_off').endswith('on') for n in path),'owner floating flag')
|
||||
owner(node,node['id'],ws[0]['id'],path)
|
||||
return ws[0]
|
||||
|
||||
def selected_workspace(snapshot,name,output,layout):
|
||||
from ipc_predicates import workspace
|
||||
ws=workspace(snapshot['tree'],name)
|
||||
need(ws.get('output')==output and ws.get('layout')==layout,'pre-map workspace/output/layout')
|
||||
rows=[r for r in snapshot['workspaces'] if r.get('name')==name]
|
||||
need(len(rows)==1 and rows[0].get('focused') and rows[0].get('output')==output,'pre-map workspace not selected')
|
||||
return ws
|
||||
|
||||
def fullscreen_ancestor(path):
|
||||
rows=[n for n in path[:-1] if n.get('type')=='con' and n.get('fullscreen_mode') and n.get('nodes')]
|
||||
need(rows,'nonleaf current fullscreen ancestor NOT EXERCISED')
|
||||
return rows[-1]
|
||||
|
||||
def hidden_survival(log,initial,injections):
|
||||
need(not any(e.get('action')!='move' for e in injections),'hide/return contaminated by dismissal click')
|
||||
fresh=grab(log)
|
||||
need(fresh['ids']==initial['ids'] and fresh['grabs']==initial['grabs'],'same live grabbed chain required')
|
||||
return fresh
|
||||
|
||||
def standard_spill(images,colors):
|
||||
# Native clip identity is unavailable on CORE: demonstrate effective removal
|
||||
# through both continued popup paints, then freshly corroborated pointer input.
|
||||
need(all(any(px==colors[n] for px in images[1].get_flattened_data()) for n in ('POPUP_A','POPUP_B')),'standard continuation for both A/B NOT EXERCISED')
|
||||
|
||||
def evidence_identity(record,binary_sha):
|
||||
need(record.get('binary_sha256')==binary_sha and record.get('measurement_kind')=='raw','wrong SHA or superficial status')
|
||||
|
||||
|
||||
def terminal_transport(terminal,compositor_exit):
|
||||
# WHY: swaymsg may see EOF after the compositor processed exit successfully.
|
||||
# CONTRACT/INVARIANT: preserve raw codes/logs; no status-only terminal receipt.
|
||||
return bool(terminal is not None and terminal.get('ipc_exit') in (0,1) and compositor_exit==0 and isinstance(terminal.get('stdout'),str) and isinstance(terminal.get('stderr'),str) and (terminal['ipc_exit']==0 or terminal['stdout'] or terminal['stderr']) and 'wait_error' not in terminal and 'ipc_error' not in terminal)
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
[22:46:38.282527] {Default Queue} -> wl_display#1.get_registry(new id wl_registry#2)
|
||||
[22:46:38.282546] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
|
||||
[22:46:38.282664] {Display Queue} wl_display#1.delete_id(3)
|
||||
[22:46:38.282677] {Default Queue} wl_registry#2.global(1, "wl_fixes", 1)
|
||||
[22:46:38.282685] {Default Queue} wl_registry#2.global(2, "wl_shm", 2)
|
||||
[22:46:38.282692] {Default Queue} -> wl_registry#2.bind(2, "wl_shm", 1, new id [unknown]#4)
|
||||
[22:46:38.282699] {Default Queue} wl_registry#2.global(3, "zwp_linux_dmabuf_v1", 5)
|
||||
[22:46:38.282703] {Default Queue} wl_registry#2.global(4, "wp_linux_drm_syncobj_manager_v1", 1)
|
||||
[22:46:38.282708] {Default Queue} wl_registry#2.global(5, "wl_compositor", 6)
|
||||
[22:46:38.282712] {Default Queue} -> wl_registry#2.bind(5, "wl_compositor", 4, new id [unknown]#5)
|
||||
[22:46:38.282716] {Default Queue} wl_registry#2.global(6, "wl_subcompositor", 1)
|
||||
[22:46:38.282720] {Default Queue} wl_registry#2.global(7, "wl_data_device_manager", 3)
|
||||
[22:46:38.282724] {Default Queue} wl_registry#2.global(8, "zwlr_gamma_control_manager_v1", 1)
|
||||
[22:46:38.282728] {Default Queue} wl_registry#2.global(9, "zxdg_output_manager_v1", 3)
|
||||
[22:46:38.282732] {Default Queue} wl_registry#2.global(10, "ext_idle_notifier_v1", 2)
|
||||
[22:46:38.282736] {Default Queue} wl_registry#2.global(11, "zwp_idle_inhibit_manager_v1", 1)
|
||||
[22:46:38.282740] {Default Queue} wl_registry#2.global(12, "zwlr_layer_shell_v1", 5)
|
||||
[22:46:38.282744] {Default Queue} wl_registry#2.global(13, "xdg_wm_base", 5)
|
||||
[22:46:38.282751] {Default Queue} -> wl_registry#2.bind(13, "xdg_wm_base", 3, new id [unknown]#6)
|
||||
[22:46:38.282756] {Default Queue} wl_registry#2.global(14, "zwp_tablet_manager_v2", 1)
|
||||
[22:46:38.282760] {Default Queue} wl_registry#2.global(15, "org_kde_kwin_server_decoration_manager", 1)
|
||||
[22:46:38.282764] {Default Queue} wl_registry#2.global(16, "zxdg_decoration_manager_v1", 1)
|
||||
[22:46:38.282768] {Default Queue} wl_registry#2.global(17, "zwp_relative_pointer_manager_v1", 1)
|
||||
[22:46:38.282772] {Default Queue} wl_registry#2.global(18, "zwp_pointer_constraints_v1", 1)
|
||||
[22:46:38.282776] {Default Queue} wl_registry#2.global(19, "wp_presentation", 2)
|
||||
[22:46:38.282780] {Default Queue} wl_registry#2.global(20, "wp_alpha_modifier_v1", 1)
|
||||
[22:46:38.282784] {Default Queue} wl_registry#2.global(21, "zwlr_output_manager_v1", 4)
|
||||
[22:46:38.282788] {Default Queue} wl_registry#2.global(22, "zwlr_output_power_manager_v1", 1)
|
||||
[22:46:38.282792] {Default Queue} wl_registry#2.global(23, "zwp_input_method_manager_v2", 1)
|
||||
[22:46:38.282796] {Default Queue} wl_registry#2.global(24, "zwp_text_input_manager_v3", 1)
|
||||
[22:46:38.282799] {Default Queue} wl_registry#2.global(25, "ext_foreign_toplevel_list_v1", 1)
|
||||
[22:46:38.282803] {Default Queue} wl_registry#2.global(26, "zwlr_foreign_toplevel_manager_v1", 3)
|
||||
[22:46:38.282807] {Default Queue} wl_registry#2.global(27, "ext_session_lock_manager_v1", 1)
|
||||
[22:46:38.282812] {Default Queue} wl_registry#2.global(28, "ext_workspace_manager_v1", 1)
|
||||
[22:46:38.282816] {Default Queue} wl_registry#2.global(29, "zwlr_export_dmabuf_manager_v1", 1)
|
||||
[22:46:38.282820] {Default Queue} wl_registry#2.global(30, "zwlr_screencopy_manager_v1", 3)
|
||||
[22:46:38.282824] {Default Queue} wl_registry#2.global(31, "ext_image_copy_capture_manager_v1", 1)
|
||||
[22:46:38.282828] {Default Queue} wl_registry#2.global(32, "ext_output_image_capture_source_manager_v1", 1)
|
||||
[22:46:38.282832] {Default Queue} wl_registry#2.global(33, "zwlr_data_control_manager_v1", 2)
|
||||
[22:46:38.282836] {Default Queue} wl_registry#2.global(34, "ext_data_control_manager_v1", 1)
|
||||
[22:46:38.282840] {Default Queue} wl_registry#2.global(35, "wp_security_context_manager_v1", 1)
|
||||
[22:46:38.282844] {Default Queue} wl_registry#2.global(36, "wp_viewporter", 1)
|
||||
[22:46:38.282848] {Default Queue} wl_registry#2.global(37, "wp_single_pixel_buffer_manager_v1", 1)
|
||||
[22:46:38.282852] {Default Queue} wl_registry#2.global(38, "wp_content_type_manager_v1", 1)
|
||||
[22:46:38.282856] {Default Queue} wl_registry#2.global(39, "wp_fractional_scale_manager_v1", 1)
|
||||
[22:46:38.282860] {Default Queue} wl_registry#2.global(40, "ext_foreign_toplevel_image_capture_source_manager_v1", 1)
|
||||
[22:46:38.282872] {Default Queue} wl_registry#2.global(41, "wp_tearing_control_manager_v1", 1)
|
||||
[22:46:38.282886] {Default Queue} wl_registry#2.global(42, "zxdg_exporter_v1", 1)
|
||||
[22:46:38.282890] {Default Queue} wl_registry#2.global(43, "zxdg_importer_v1", 1)
|
||||
[22:46:38.282894] {Default Queue} wl_registry#2.global(44, "zxdg_exporter_v2", 1)
|
||||
[22:46:38.282898] {Default Queue} wl_registry#2.global(45, "zxdg_importer_v2", 1)
|
||||
[22:46:38.282903] {Default Queue} wl_registry#2.global(46, "xdg_activation_v1", 1)
|
||||
[22:46:38.282907] {Default Queue} wl_registry#2.global(47, "xdg_toplevel_tag_manager_v1", 1)
|
||||
[22:46:38.282911] {Default Queue} wl_registry#2.global(48, "wp_cursor_shape_manager_v1", 2)
|
||||
[22:46:38.282915] {Default Queue} wl_registry#2.global(49, "wp_color_representation_manager_v1", 1)
|
||||
[22:46:38.282919] {Default Queue} wl_registry#2.global(50, "zwp_virtual_keyboard_manager_v1", 1)
|
||||
[22:46:38.282923] {Default Queue} wl_registry#2.global(51, "zwlr_virtual_pointer_manager_v1", 2)
|
||||
[22:46:38.282927] {Default Queue} wl_registry#2.global(52, "zwp_keyboard_shortcuts_inhibit_manager_v1", 1)
|
||||
[22:46:38.282931] {Default Queue} wl_registry#2.global(53, "zwp_pointer_gestures_v1", 3)
|
||||
[22:46:38.282935] {Default Queue} wl_registry#2.global(54, "ext_transient_seat_manager_v1", 1)
|
||||
[22:46:38.282939] {Default Queue} wl_registry#2.global(55, "wl_seat", 9)
|
||||
[22:46:38.282944] {Default Queue} -> wl_registry#2.bind(55, "wl_seat", 5, new id [unknown]#7)
|
||||
[22:46:38.282948] {Default Queue} wl_registry#2.global(57, "zwp_primary_selection_device_manager_v1", 1)
|
||||
[22:46:38.282953] {Default Queue} wl_registry#2.global(58, "wl_output", 4)
|
||||
[22:46:38.282957] {Default Queue} wl_registry#2.global(59, "wl_output", 4)
|
||||
[22:46:38.282961] {Default Queue} wl_callback#3.done(16)
|
||||
[22:46:38.282966] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
|
||||
[22:46:38.283205] {Display Queue} wl_display#1.delete_id(3)
|
||||
[22:46:38.283217] {Default Queue} discarded wl_shm#4.format(0)
|
||||
[22:46:38.283222] {Default Queue} discarded wl_shm#4.format(1)
|
||||
[22:46:38.283227] {Default Queue} discarded wl_shm#4.format(875709016)
|
||||
[22:46:38.283231] {Default Queue} discarded wl_shm#4.format(875708993)
|
||||
[22:46:38.283235] {Default Queue} discarded wl_shm#4.format(875710274)
|
||||
[22:46:38.283239] {Default Queue} discarded wl_shm#4.format(842094674)
|
||||
[22:46:38.283242] {Default Queue} discarded wl_shm#4.format(842088786)
|
||||
[22:46:38.283246] {Default Queue} discarded wl_shm#4.format(892426322)
|
||||
[22:46:38.283249] {Default Queue} discarded wl_shm#4.format(892420434)
|
||||
[22:46:38.283253] {Default Queue} discarded wl_shm#4.format(909199186)
|
||||
[22:46:38.283257] {Default Queue} discarded wl_shm#4.format(808665688)
|
||||
[22:46:38.283260] {Default Queue} discarded wl_shm#4.format(808665665)
|
||||
[22:46:38.283264] {Default Queue} discarded wl_shm#4.format(1213351746)
|
||||
[22:46:38.283268] {Default Queue} discarded wl_shm#4.format(1211384408)
|
||||
[22:46:38.283271] {Default Queue} discarded wl_shm#4.format(1211384385)
|
||||
[22:46:38.283275] {Default Queue} discarded wl_shm#4.format(942950210)
|
||||
[22:46:38.283279] {Default Queue} discarded wl_shm#4.format(942948952)
|
||||
[22:46:38.283282] {Default Queue} discarded wl_shm#4.format(942948929)
|
||||
[22:46:38.283286] {Default Queue} wl_seat#7.name("seat0")
|
||||
[22:46:38.283291] {Default Queue} wl_seat#7.capabilities(1)
|
||||
[22:46:38.283296] {Default Queue} -> wl_seat#7.get_pointer(new id wl_pointer#8)
|
||||
[22:46:38.283300] {Default Queue} wl_callback#3.done(16)
|
||||
[22:46:38.283305] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#3)
|
||||
[22:46:38.283310] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#9, wl_surface#3)
|
||||
[22:46:38.283319] {Default Queue} -> xdg_surface#9.get_toplevel(new id xdg_toplevel#10)
|
||||
[22:46:38.283324] {Default Queue} -> xdg_toplevel#10.set_title("Mosaic Nested Grab Probe")
|
||||
[22:46:38.283328] {Default Queue} -> xdg_toplevel#10.set_app_id("org.mosaic.NestedGrabProbe")
|
||||
TOPLEVEL CREATE wl=3 xdg=9 role=10 placement=v_both_cross
|
||||
[22:46:38.283338] {Default Queue} -> wl_surface#3.commit()
|
||||
[22:46:38.283679] {Default Queue} xdg_wm_base#6.ping(17)
|
||||
[22:46:38.283698] {Default Queue} -> xdg_wm_base#6.pong(17)
|
||||
[22:46:38.283708] {Default Queue} xdg_toplevel#10.configure(0, 0, array[0])
|
||||
TOPLEVEL ROLE_CONFIGURE w=0 h=0
|
||||
[22:46:38.283716] {Default Queue} xdg_surface#9.configure(18)
|
||||
TOPLEVEL XDG_CONFIGURE serial=18 ack
|
||||
[22:46:38.283724] {Default Queue} -> xdg_surface#9.ack_configure(18)
|
||||
[22:46:38.284502] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#11, fd 5, 966000)
|
||||
[22:46:38.284513] {Default Queue} -> wl_shm_pool#11.create_buffer(new id wl_buffer#12, 0, 420, 575, 1680, 1)
|
||||
[22:46:38.284520] {Default Queue} -> wl_surface#3.attach(wl_buffer#12, 0, 0)
|
||||
[22:46:38.284525] {Default Queue} -> wl_surface#3.damage_buffer(0, 0, 420, 575)
|
||||
[22:46:38.284530] {Default Queue} -> wl_surface#3.commit()
|
||||
[22:46:38.284534] {Default Queue} -> wl_buffer#12.destroy()
|
||||
[22:46:38.284538] {Default Queue} -> wl_shm_pool#11.destroy()
|
||||
TOPLEVEL MAP wl=3 xdg=9
|
||||
[22:46:38.285508] discarded [unknown]#12.[event 0](0 fd, 8 byte)
|
||||
[22:46:38.285514] {Display Queue} wl_display#1.delete_id(12)
|
||||
[22:46:38.285519] {Display Queue} wl_display#1.delete_id(11)
|
||||
[22:46:38.285524] {Default Queue} xdg_toplevel#10.configure(636, 573, array[20])
|
||||
TOPLEVEL ROLE_CONFIGURE w=636 h=573
|
||||
[22:46:38.285532] {Default Queue} xdg_surface#9.configure(19)
|
||||
TOPLEVEL XDG_CONFIGURE serial=19 ack
|
||||
[22:46:38.285539] {Default Queue} -> xdg_surface#9.ack_configure(19)
|
||||
[22:46:38.583097] {Default Queue} wl_pointer#8.enter(24, wl_surface#3, 238.00000000, 275.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=24 x=238.00 y=275.00
|
||||
[22:46:38.583124] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:38.583129] {Default Queue} wl_pointer#8.leave(25, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=25
|
||||
[22:46:38.583137] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:39.867771] {Default Queue} wl_pointer#8.enter(30, wl_surface#3, 3.00000000, 5.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=30 x=3.00 y=5.00
|
||||
[22:46:39.867787] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:39.867792] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:39.968604] {Default Queue} wl_pointer#8.leave(31, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=31
|
||||
[22:46:39.968649] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.117908] {Default Queue} wl_pointer#8.enter(32, wl_surface#3, 3.00000000, 5.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=32 x=3.00 y=5.00
|
||||
[22:46:40.117939] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.117943] {Default Queue} wl_pointer#8.button(33, 1002, 272, 1)
|
||||
POINTER_BUTTON target=TOPLEVEL serial=33 button=272 state=1
|
||||
[22:46:40.117951] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#11)
|
||||
[22:46:40.117956] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#12, wl_surface#11)
|
||||
[22:46:40.117960] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#13)
|
||||
[22:46:40.117964] {Default Queue} -> xdg_positioner#13.set_size(250, 170)
|
||||
[22:46:40.117967] {Default Queue} -> xdg_positioner#13.set_anchor_rect(50, 480, 1, 1)
|
||||
[22:46:40.117970] {Default Queue} -> xdg_positioner#13.set_anchor(5)
|
||||
[22:46:40.117973] {Default Queue} -> xdg_positioner#13.set_gravity(8)
|
||||
[22:46:40.117976] {Default Queue} -> xdg_surface#12.get_popup(new id xdg_popup#14, xdg_surface#9, xdg_positioner#13)
|
||||
POPUP_A CREATE wl=11 xdg=12 role=14 parent=TOPLEVEL parent_xdg=9
|
||||
POPUP_A GRAB_REQUEST origin=wl_pointer.button target=TOPLEVEL serial=33 used=33
|
||||
[22:46:40.117984] {Default Queue} -> xdg_popup#14.grab(wl_seat#7, 33)
|
||||
[22:46:40.117988] {Default Queue} -> wl_surface#11.commit()
|
||||
[22:46:40.117991] {Default Queue} -> xdg_positioner#13.destroy()
|
||||
[22:46:40.117994] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.118126] {Display Queue} wl_display#1.delete_id(13)
|
||||
[22:46:40.118129] {Default Queue} xdg_popup#14.configure(50, 480, 250, 170)
|
||||
POPUP_A ROLE_CONFIGURE x=50 y=480 w=250 h=170
|
||||
[22:46:40.118135] {Default Queue} xdg_surface#12.configure(34)
|
||||
POPUP_A XDG_CONFIGURE serial=34 ack
|
||||
[22:46:40.118140] {Default Queue} -> xdg_surface#12.ack_configure(34)
|
||||
[22:46:40.118286] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#13, fd 5, 170000)
|
||||
[22:46:40.118298] {Default Queue} -> wl_shm_pool#13.create_buffer(new id wl_buffer#15, 0, 250, 170, 1000, 1)
|
||||
[22:46:40.118313] {Default Queue} -> wl_surface#11.attach(wl_buffer#15, 0, 0)
|
||||
[22:46:40.118317] {Default Queue} -> wl_surface#11.damage_buffer(0, 0, 250, 170)
|
||||
[22:46:40.118320] {Default Queue} -> wl_surface#11.commit()
|
||||
[22:46:40.118322] {Default Queue} -> wl_buffer#15.destroy()
|
||||
[22:46:40.118325] {Default Queue} -> wl_shm_pool#13.destroy()
|
||||
POPUP_A MAP wl=11 xdg=12
|
||||
[22:46:40.118959] discarded [unknown]#15.[event 0](0 fd, 8 byte)
|
||||
[22:46:40.118962] {Display Queue} wl_display#1.delete_id(15)
|
||||
[22:46:40.118965] {Display Queue} wl_display#1.delete_id(13)
|
||||
[22:46:40.197365] {Default Queue} wl_pointer#8.button(35, 1003, 272, 0)
|
||||
POINTER_BUTTON target=TOPLEVEL serial=35 button=272 state=0
|
||||
[22:46:40.197412] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.298184] {Default Queue} wl_pointer#8.leave(36, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=36
|
||||
[22:46:40.298225] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585174] {Default Queue} wl_pointer#8.enter(37, wl_surface#3, 3.00000000, 5.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=37 x=3.00 y=5.00
|
||||
[22:46:40.585201] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585208] {Default Queue} wl_pointer#8.leave(38, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=38
|
||||
[22:46:40.585221] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585227] {Default Queue} wl_pointer#8.enter(39, wl_surface#11, 4.00000000, 5.00000000)
|
||||
POINTER_ENTER target=POPUP_A wl=11 serial=39 x=4.00 y=5.00
|
||||
[22:46:40.585240] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585246] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.685838] {Default Queue} wl_pointer#8.leave(40, wl_surface#11)
|
||||
POINTER_LEAVE target=POPUP_A serial=40
|
||||
[22:46:40.685909] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.835646] {Default Queue} wl_pointer#8.enter(41, wl_surface#11, 4.00000000, 5.00000000)
|
||||
POINTER_ENTER target=POPUP_A wl=11 serial=41 x=4.00 y=5.00
|
||||
[22:46:40.835675] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.835682] {Default Queue} wl_pointer#8.button(42, 1005, 272, 1)
|
||||
POINTER_BUTTON target=POPUP_A serial=42 button=272 state=1
|
||||
[22:46:40.835696] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#13)
|
||||
[22:46:40.835704] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#15, wl_surface#13)
|
||||
[22:46:40.835712] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#16)
|
||||
[22:46:40.835719] {Default Queue} -> xdg_positioner#16.set_size(170, 130)
|
||||
[22:46:40.835726] {Default Queue} -> xdg_positioner#16.set_anchor_rect(20, 80, 1, 1)
|
||||
[22:46:40.835733] {Default Queue} -> xdg_positioner#16.set_anchor(5)
|
||||
[22:46:40.835738] {Default Queue} -> xdg_positioner#16.set_gravity(8)
|
||||
[22:46:40.835745] {Default Queue} -> xdg_surface#15.get_popup(new id xdg_popup#17, xdg_surface#12, xdg_positioner#16)
|
||||
POPUP_B CREATE wl=13 xdg=15 role=17 parent=POPUP_A parent_xdg=12
|
||||
POPUP_B GRAB_REQUEST origin=wl_pointer.button target=POPUP_A serial=42 used=42
|
||||
[22:46:40.835761] {Default Queue} -> xdg_popup#17.grab(wl_seat#7, 42)
|
||||
[22:46:40.835768] {Default Queue} -> wl_surface#13.commit()
|
||||
[22:46:40.835775] {Default Queue} -> xdg_positioner#16.destroy()
|
||||
[22:46:40.835781] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.835930] {Display Queue} wl_display#1.delete_id(16)
|
||||
[22:46:40.835937] {Default Queue} xdg_popup#17.configure(20, 80, 170, 130)
|
||||
POPUP_B ROLE_CONFIGURE x=20 y=80 w=170 h=130
|
||||
[22:46:40.835949] {Default Queue} xdg_surface#15.configure(43)
|
||||
POPUP_B XDG_CONFIGURE serial=43 ack
|
||||
[22:46:40.835960] {Default Queue} -> xdg_surface#15.ack_configure(43)
|
||||
[22:46:40.836118] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#16, fd 5, 88400)
|
||||
[22:46:40.836128] {Default Queue} -> wl_shm_pool#16.create_buffer(new id wl_buffer#18, 0, 170, 130, 680, 1)
|
||||
[22:46:40.836135] {Default Queue} -> wl_surface#13.attach(wl_buffer#18, 0, 0)
|
||||
[22:46:40.836142] {Default Queue} -> wl_surface#13.damage_buffer(0, 0, 170, 130)
|
||||
[22:46:40.836149] {Default Queue} -> wl_surface#13.commit()
|
||||
[22:46:40.836155] {Default Queue} -> wl_buffer#18.destroy()
|
||||
[22:46:40.836168] {Default Queue} -> wl_shm_pool#16.destroy()
|
||||
POPUP_B MAP wl=13 xdg=15
|
||||
Loading…
Reference in a new issue