// Virtual-pointer injector mode for the F15 demo:
//
// f15_popup inject <menu|edge>
//
// Plugs ONE `zwlr_virtual_pointer_v1` device in and scripts a whole popup
// scenario inside that single device session (motion_absolute against the
// 1280x720 headless output). A per-gesture plug/unplug (the wlrctl model)
// would flap the seat's pointer capability mid-grab — whether THAT dismisses
// a grab popup must stay a deliberate measurement, not an accident of the
// test driver. Coordinates are baked against ./run.sh's sway layout:
//
// menu window wsi-f15-popup floats at (320,120) 640x480. Right-click at
// global (500,300) = surface (180,180) opens the menu; after the
// demo's reposition probe (+24,+16 offset) the popup's expected
// global origin is (525,317) — the hover/click coordinates target
// the REPOSITIONED items; a final click at (150,650) lands outside
// the window (compositor-side dismissal → popup_done chain).
// edge window wsi-f15-edge floats at (632,232); right-click at global
// (1232,682) = surface (600,450) — the 160x96 popup cannot fit
// down-right (output is 1280x720), the compositor must constrain.
// The device is then HELD ~4 s while ./run.sh delivers Esc via a
// wtype virtual keyboard.
module (module) injectinject;
import c; // ImportC shim — wsi_vpm_* / wsi_vptr_* are hand-marshalled (see ./c.c)
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) inject.fprintf = int core.stdc.stdio.fprintf(shared(core.stdc.stdio._IO_FILE)* stream, scope const(char*) format, scope const ...) nothrow @nogcfprintf, (alias) inject.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf, (alias shared global) inject.stderr = shared(core.stdc.stdio._IO_FILE*) core.stdc.stdio.stderrstderr;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stringD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h
Source
core/stdc/string.d
string : (alias) inject.strcmp = int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogcstrcmp;
__gshared wl_proxy* _error_ inject.gi_mgrgi_mgr;
__gshared wl_seat* _error_ inject.gi_seatgi_seat;
__gshared wl_display* _error_ inject.gi_dpygi_dpy;
__gshared wl_proxy* _error_ inject.gi_ptrgi_ptr;
extern (C) void inject.injGlobalinjGlobal(void* data, wl_registry* (parameter) wl_registry* regreg, uint name,
const(char)* iface, uint ver) nothrow @nogc
{
if (strcmp(iface, "zwlr_virtual_pointer_manager_v1") == 0)
gi_mgr = cast(wl_proxy*) wsi_registry_bind(reg, name,
&wsi_virtual_pointer_manager_interface, 1);
else if (strcmp(iface, wl_seat_interface.name) == 0 && gi_seat is null)
gi_seat = cast(wl_seat*) wsi_registry_bind(reg, name, &wl_seat_interface, 1);
}
extern (C) void inject.injGlobalRemoveinjGlobalRemove(void* data, wl_registry* (parameter) wl_registry* regreg, uint name) nothrow @nogc
{
}
__gshared wl_registry_listener _error_ inject.gi_registryListenergi_registryListener = {&injGlobal, &injGlobalRemove};
enum uint (constant) uint inject.BTN_LEFT = 272uBTN_LEFT = 0x110, (constant) uint inject.BTN_RIGHT = 273uBTN_RIGHT = 0x111;
void void inject.absMove(uint x, uint y) nothrow @nogcabsMove(uint (parameter) uint xx, uint (parameter) uint yy) nothrow @nogc
{
wsi_vptr_motion_absolute(gi_ptr, 0, x, y, 1280, 720);
wsi_vptr_frame(gi_ptr);
wl_display_flush(gi_dpy);
}
void void inject.click(uint button, uint holdMs = 80u) nothrow @nogcclick(uint (parameter) uint buttonbutton, uint (parameter) uint holdMsholdMs = 80) nothrow @nogc
{
wsi_vptr_button(gi_ptr, 0, button, 1);
wsi_vptr_frame(gi_ptr);
wl_display_flush(gi_dpy);
usleep(holdMs * 1000);
wsi_vptr_button(gi_ptr, 0, button, 0);
wsi_vptr_frame(gi_ptr);
wl_display_flush(gi_dpy);
}
void void inject.pause(uint ms) nothrow @nogcpause(uint (parameter) uint msms) nothrow @nogc
{
usleep(ms * 1000);
}
int int inject.injectMain(const(char)[] scenario)injectMain(const(char)[] (parameter) const(char)[] scenarioscenario)
{
gi_dpy = wl_display_connect(null);
if (gi_dpy is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: inject: no Wayland compositor\n");
return 0;
}
wl_registry* (local variable) _error_ regreg = wsi_display_get_registry(gi_dpy);
wsi_registry_add_listener(reg, &gi_registryListener, null);
wl_display_roundtrip(gi_dpy);
if (gi_mgr is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: inject: no zwlr_virtual_pointer_manager_v1\n");
wl_display_disconnect(gi_dpy);
return 0;
}
gi_ptr = wsi_vpm_create_virtual_pointer(gi_mgr, gi_seat);
wl_display_roundtrip(gi_dpy); // device plugged → seat gains the capability
void inject.pause(uint ms) nothrow @nogcpause(400); // demo creates its wl_pointer
if ((parameter) const(char)[] scenarioscenario.(field) ulong const(char)[].lengthlength >= 4 && (parameter) const(char)[] scenarioscenario[0 .. 4] == "edge")
{
void inject.absMove(uint x, uint y) nothrow @nogcabsMove(1232, 682); // inside wsi-f15-edge, near the output corner
void inject.pause(uint ms) nothrow @nogcpause(400);
void inject.click(uint button, uint holdMs = 80u) nothrow @nogcclick((constant) uint inject.BTN_RIGHT = 273uBTN_RIGHT); // → constrained popup + grab
void inject.pause(uint ms) nothrow @nogcpause(4000); // hold the device while run.sh sends Esc via wtype
}
else // menu
{
void inject.absMove(uint x, uint y) nothrow @nogcabsMove(500, 300); // inside wsi-f15-popup
void inject.pause(uint ms) nothrow @nogcpause(400);
void inject.click(uint button, uint holdMs = 80u) nothrow @nogcclick((constant) uint inject.BTN_RIGHT = 273uBTN_RIGHT); // → popup1 + grab(press serial)
void inject.pause(uint ms) nothrow @nogcpause(1700); // popup maps; demo fires the reposition probe (+24,+16)
void inject.absMove(uint x, uint y) nothrow @nogcabsMove(595, 336); // hover item 0 (repositioned popup)
void inject.pause(uint ms) nothrow @nogcpause(250);
void inject.absMove(uint x, uint y) nothrow @nogcabsMove(595, 364); // hover item 1
void inject.pause(uint ms) nothrow @nogcpause(250);
void inject.absMove(uint x, uint y) nothrow @nogcabsMove(595, 392); // hover item 2 ("submenu >")
void inject.pause(uint ms) nothrow @nogcpause(300);
void inject.click(uint button, uint holdMs = 80u) nothrow @nogcclick((constant) uint inject.BTN_LEFT = 272uBTN_LEFT); // → popup2 (nested, grabbed) parented on popup1
void inject.pause(uint ms) nothrow @nogcpause(800);
void inject.absMove(uint x, uint y) nothrow @nogcabsMove(150, 650); // outside the window entirely
void inject.pause(uint ms) nothrow @nogcpause(300);
void inject.click(uint button, uint holdMs = 80u) nothrow @nogcclick((constant) uint inject.BTN_LEFT = 272uBTN_LEFT); // → compositor dismisses: popup_done chain
void inject.pause(uint ms) nothrow @nogcpause(600);
}
wsi_vptr_destroy(gi_ptr); // device unplugged → capability drop
wsi_vpm_destroy(gi_mgr);
wl_display_roundtrip(gi_dpy);
wl_display_disconnect(gi_dpy);
int core.stdc.stdio.fprintf(shared(core.stdc.stdio._IO_FILE)* stream, scope const(char*) format, scope const ...) nothrow @nogcfprintf((shared global) shared(core.stdc.stdio._IO_FILE*) core.stdc.stdio.stderrstderr, "inject: done scenario=%.*s\n", cast(int) (parameter) const(char)[] scenarioscenario.(field) ulong const(char)[].lengthlength, (parameter) const(char)[] scenarioscenario.(field) const(char)* const(char)[].ptrptr);
return 0;
}