Skip to content

refactor(runtime): run the keyboard as a profile on a pure engine - #36

Open
inayayousfi wants to merge 32 commits into
mainfrom
refactor/generic-runtime-actions
Open

inayayousfi wants to merge 32 commits into
mainfrom
refactor/generic-runtime-actions

Conversation

@inayayousfi

@inayayousfi inayayousfi commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

inayayousfi directed the work and made every decision.
gpt-5.6-sol, running in OpenCode, and Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Summary

This branch started as a generic action and event queue. It now also merges main up to 0.18.1 and turns the runtime into an engine that knows nothing about keyboards. The bundled US ISO keyboard is a profile written against that engine, in the shape a Lua config will have.

  • Route every event and action through one registered, plain-data queue that only drains on the Qt thread, so keyboard listener callbacks can no longer run handlers on the listener thread.
  • Describe profiles as plain data plus functions. A binding fn(state) -> value re-runs only when the state it read changes, and a callback fn(ctx, event) returns actions. State lives in declared, typed variables (see State registry below), and true is not 1.
  • Add curated node kinds (window, grid, box, stack, button, label, spacer), attachments by reference (dwell, pointer locator, hot corners, lock-screen panel), observed state (input.*, keyboard.*, windows.*), and explicit effects (keyboard.down/up/tap/type_text, showing, hiding, closing, and moving windows, process.spawn, logging).
  • Move keyboard conventions into python_defaults/axidev_osk/std, and the bundled keyboard with its colors and font into python_defaults/default_profile.py, then delete key modes, hooks, state tags, the keyboard grid, and the prompt and permission dialogs from the engine.
  • Rewrite AGENTS.md as a map of the engine, the standard library, and profiles, and write each module's behavior in its docstrings. The remaining Lua work is tracked in Define Lua config architecture #8.

Architecture

Everything under src/axidev_osk/ except python_defaults/ is the engine. It owns windows, the queue, central state, platform integration, keyboard output, and process lifecycle, and it holds no layout, legend, latch, or prompt logic. A Lua loader is expected to replace python_defaults/ by producing the same maps and functions, so the engine does not change when Lua arrives.

The default keyboard should behave as before. Quit confirmation, the Linux permission prompts, hot-corner show and hide, Ghost, Dwell, and second-launch activation now come from the profile instead of engine code. SIGTERM still quits without asking, a profile that handles no quit request quits directly, and other quit requests reach the profile with their reason (signal, stdin_closed, or window_closed).

Every window keeps Qt's close rule unless it sets default_close to false: closing it closes it, and closing the last visible window asks to quit. The default keyboard turns the rule off and asks through its own prompt.

Plasma lock-screen panels keep the same supervisor protocol. The profile's secure_input_panel attachment names the window they show, and the panel now answers ERROR when keyboard output cannot start.

State registry

State is now a registry of declared variables. A profile declares each one with axidev_osk.var(type, default). Variables in the root config's state are shared by every profile and survive a profile switch, while a profile's own state resets when that profile starts. Reading or writing a path nobody declared fails, values are checked against their type, and None resets a variable to its default or removes a map entry. Every value field of a node, window, attachment, or theme accepts a binding, so window.set_opacity, window.block_input, window.unblock_input, and dwell.set_enabled are gone.

Profiles also get three generic input primitives. timer.start (id, delay_ms, repeat) and timer.cancel (id) schedule timer.fired events; restarting an ID replaces its timer, and a profile switch drops every timer. Buttons report hover through on_enter and on_leave (button.entered, button.left). Any node can set reports_pointer, and the engine then posts node.pointer_pressed, node.pointer_moved (while pressed) and node.pointer_released with the node and the pointer's x and y, for gestures such as swipes. Buttons lose their active and latched options: how a key looks while down or latched is profile policy, so std.lit sets those style properties from bindings instead. The window chrome stays in the engine, because on Windows a window keeps the operating system's frame and a profile could not replace it there.

Every interaction now reaches the profile as an event, and the engine decides nothing about what it means. button.pressed and button.released carry {node, button}, where button is left, right, middle, back, forward or other; every other mouse button presses a button as the left one does, and node.pointer_pressed and node.pointer_released carry the button too. Dragging a window's title bar or resize grip posts window.dragged or window.resize_dragged with the window and the distance, and a finished dwell posts dwell.completed with the window and the point. The engine no longer moves, resizes or clicks on its own: std.with_handlers maps these events to window.move_by, the new window.resize_by, and the new window.click (left button unless the action names another), so the default profile behaves as before, and a profile can give a right click, a drag or a dwell another meaning, or none. AGENTS.md states this rule, along with which code may call services, what the engine promises on every platform, and that frequent observations are opt-in.

Built-ins are now optional, and AGENTS.md says when a feature belongs in a profile, the standard library, or the engine. Any mouse button drags the title bar and the resize grip and presses the close button, and window.dragged, window.resize_dragged, window.drag_started and window.close_requested name it; std moves and resizes only on the left button. Nodes can opt in to node.pointer_hovered and node.pointer_left (reports_hover), node.wheel (reports_wheel), and node.geometry_changed (report_geometry), which records nodes.<id>.position and nodes.<id>.size. Hot corners can be turned off (enabled) or only report (fires_on_rest), and post hot_corner.entered and hot_corner.left. The pointer locator glows each button in its locator_color, and std.locator computes the old palette from the keys' reported geometry, so all 89 keys keep their colors. Dwell's thresholds and indicator, the hot-corner indicator, and the locator's poll interval are options. Window placement is an anchor, one of 9 names or any x and y, and keep_on_screen can let a floating window leave its screen.

The parity tests changed on purpose with this. Ghost used to send block and opacity actions for the keyboard window. It now sets std.ghosted.keyboard, and the window's opacity and input block follow that flag. Dwell now toggles settings.dwell.enabled, a variable in the root config, instead of the removed dwell.keyboard-dwell.enabled. The layout, key outputs, latches, and the quit, permission, and second-launch flows are checked as before.

Platform changes

  • Linux Wayland: each overlay window asks LayerShellQt for a layer surface through LayerShellQt::Window::get, called with ctypes, instead of the app setting QT_WAYLAND_SHELL_INTEGRATION=layer-shell for the whole process. Only overlay windows become layer surfaces, and the "already has a shell integration" warning is gone. The Linux launcher checks that this function exists; without it, the app falls back to the X11 overlay backend.
  • All platforms: showing a minimized window restores it. Before, only Windows did that.
  • Failed keyboard actions now report action.failed instead of being dropped silently.

Testing

  • QT_QPA_PLATFORM=offscreen python -m unittest discover -s tests: 472 passed, 1 skipped on Linux.
  • Parity tests compare the default profile with measurements taken from the original keyboard: grid position, span, and minimum size of all 89 keys, all 87 outputs, latching (including the shared left and right latches), Caps Lock, Ghost, Dwell, hot corners, and the quit, permission, and second-launch flows.
  • pyright: 0 errors. flake8 --select=F,E9,W6 src: clean.
  • Tried on a KDE Plasma Wayland desktop: the keyboard, hot corners, the quit prompt, and shutdown. Wayland protocol logs confirmed that every overlay window gets a layer surface.
  • Not yet run on Hyprland, niri, X11, a Windows machine, the Nix package, or the Plasma lock screen.
  • The state registry has not been tried on a desktop yet; the KDE Plasma run above predates it.

Written by inayayousfi, typed by gpt-5.6-sol running in OpenCode.
Every call here is inayayousfi's, and no agent acted on its own.

Replace concrete runtime command and event DTOs with registered native-data
messages, then migrate built-in producers, handlers, and tests.

Add full-source Pyright checks and document the queue contract.
Written by inayayousfi, typed by gpt-5.6-sol running in OpenCode.
Every call here is inayayousfi's, and no agent acted on its own.

Run type checking only after PySide6, axidev-io, and Axidev OSK are
available to the selected Python interpreter.
Written by inayayousfi, typed by gpt-5.6-sol running in OpenCode.
Every call here is inayayousfi's, and no agent acted on its own.

Move component policy and keyboard output into root SourcePath behavior
bindings backed by runtime state snapshots and ordered hooks.

Keep controls render-only, isolate keyboard backend I/O, and migrate the
bundled config and tests to explicit visual IDs and behavior maps.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Bring main's 0.18.1 features onto the event/action queue: Plasma
lock-screen panels, dwell clicking and its toggle key, the pointer
locator, display recovery, layer-shell dragging, right-click
activation, and observed Caps Lock state.

Keep buttons render-only. Keys with keyboard output follow the
system's Caps Lock and Num Lock state, the Dwell key is a latchable
action button, and key outputs register again whenever keyboard output
starts so lock-screen cycles keep working. The lock-screen worker
answers its supervisor from runtime events instead of exceptions.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Backend listeners call the dispatcher from their own threads, and the
first thread to start a drain used to run every pending handler.
Messages from other threads now wait in a locked inbox, and a Qt wake
drains them on the Qt thread.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Type the new runtime handler factories, the hot-corner overlay
protocol, and Qt application lookups without changing behavior.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Add PURIFICATION_PLAN.md with the settled decisions, the target
architecture, and the steps for turning the runtime into an engine
configured by profiles, and point AGENTS.md to it.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Decode root configs made of plain data and functions, with errors that
name the config path. Keep durable state in one tree that records what
each binding reads, so a binding re-runs only when that state changes.
Run profile callbacks from the on table and report failures as
callback.failed.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Report every keystroke as input.key and record key, lock, keyboard, and
window state that profiles can read. Add keyboard.down, keyboard.up,
keyboard.tap, keyboard.type_text, process.spawn with process.exited,
and log actions.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Build grid, box, stack, button, label, and spacer nodes from profile
config with live bindings and node callbacks, and host them in runtime
windows alongside the existing keyboard grid.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Add python_defaults with the osk surface a Lua module will mirror, and
osk.std keys, windows, and prompts built only from engine primitives.
Add the window opacity and input-block actions they use.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Describe the US ISO keyboard, its theme, prompts, and attachments as a
profile built on osk.std. Parity tests compare it with measurements
taken from the original keyboard widget.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Decode dwell, pointer locator, hot corners, and lock-screen panel
attachments, check their targets when a profile starts, and install
dwell clicking and the pointer locator on the windows they name.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Move startup, quit, prompts, permission setup, hot corners, and the
lock-screen panel to the profile. Delete behaviors, key modes, state
tags, the keyboard grid, and the other engine code that held keyboard
policy. Rewrite AGENTS.md around the engine, the standard library, and
profiles.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

The single-instance service now reports app.activated instead of
showing a window itself, but the default profile did not handle it.
Show the keyboard on app.activated, and update the Windows-only
single-instance test to expect the event.
@inayayousfi inayayousfi changed the title refactor(runtime): add generic action and event queue refactor(runtime): run the keyboard as a profile on a pure engine Oct 9, 2026
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Fix the bugs a review of this branch found. The lock-screen worker no
longer hangs at shutdown, window.close closes the window, a failed
binding re-runs, one-shot AltGr holds the real key, left and right
modifiers share a latch again, and state follows Lua's rules for nil,
empty tables, and true versus 1.

Give each fact one owner. The window manager alone tracks live windows,
observed state roots are declared by the code that writes them, the
dispatcher owns action.failed, and Ghost keeps its own flag, cleared
when the engine reports the window unblocked. The palette, font, and
hot-corner colors move into the profile.

Windows keep Qt's close rule unless they set default_close to false.
Quit requests carry their reason, shutdown stops the profile first, and
a failing step no longer blocks the exit. Failed keyboard actions report
action.failed, and the lock-screen panel answers ERROR when keyboard
output cannot start.

Ask LayerShellQt for a layer surface per window through Window::get
instead of setting QT_WAYLAND_SHELL_INTEGRATION for the whole process,
which removes a warning per window. Wrapped labels and windows keep the
height their text needs.

Merge runtime/events.py and runtime/actions.py into
runtime/app_messages.py, register every event in build_engine, and
share the test fakes in tests/support.py.

Platform changes: minimized windows are restored on every platform, and
only overlay windows become layer surfaces.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Quitting becomes one more message in the queue. app.quit waits until
nothing else is queued, then shutdown closes the queue first, so later
messages are dropped and logged instead of running against a profile
that is stopping.

Check every profile in the root config when it is decoded, not only the
active one. A profile restart reports state.changed for the paths it
changed. A stack page cannot set visible, and a style cannot set a
property the engine or its node kind sets. A window action naming an
unknown window reports action.failed.

The engine owns no colors: hot corners and the pointer locator take
their colors as required options, given by the default profile.

Other fixes: one-shot keys release exactly the keys their press held,
None counts as absent in std options and osk.read, a held key whose
release fails stays held, labels show a literal &, hot corners only
react to the corners configured, a duplicate service name is rejected,
the lock-screen panel answers a second prepare, and a failed layer-shell
attach is logged.

Share the keyboard test context and window, hot-corner, and node test
helpers.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

State becomes a registry of declared variables that the engine stores
and type-checks without knowing what they mean. Where a variable is
declared sets its lifetime: the root config's state survives profile
switches, a profile's state resets. Writes and reads of undeclared
paths fail, and None resets a variable to its default.

Every value option accepts a binding, while identity and structure stay
static. Values a binding sets lose their setter actions, so
window.set_opacity, window.block_input, window.unblock_input, and
dwell.set_enabled go away. Record this as step 8.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Every state value now lives in a variable declared with osk.var, with a
type and a default. The engine declares what it observes, the root
config declares variables every profile shares, which survive a
restart, and a profile declares its own, which reset when it starts.
Reads and writes of undeclared paths fail, values are checked against
their type, and None resets a variable or removes a map entry.

Every value field of a node, window, attachment, or theme accepts a
binding. When state a record read changes, the whole record decodes
again and only the changed fields are applied; an invalid result is
reported and the last value stays. At load every binding runs against
the default state, so a bad one rejects the config with its path.

window.set_opacity, window.block_input, window.unblock_input, and
dwell.set_enabled are removed. Ghost sets std.ghosted and the window's
opacity and input block follow it, with the Ghost button marked
passes_input_block. The default profile declares its opacity and dwell
settings in the root state and binds the keyboard window and the dwell
attachment to them. Window state reports position and size instead of
opacity and input blocking.

The parity test changes on purpose: Ghost and Dwell now set state
instead of sending window and dwell actions. AGENTS.md describes the
registry, and step 8 of the plan is done.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Quitting can no longer loop forever. An action registered when_idle,
such as app.quit, waits in its own list until the queue and the inbox
are empty, and such actions then run one at a time. A key event waiting
in the inbox or a second quit used to keep re-queueing it. A failing
event handler no longer skips the other handlers of that event. A
window's Qt event handlers post their state reports and close requests,
so profile callbacks never run inside a Qt event.

The config decoder checks more at load, in every profile: an attachment
naming an unknown window, more attachments of a kind than its limit
allows, and a stack whose current names no page. A binding is
registered before its failure is reported, and a fallback that cannot
apply raises once and leaves nothing bound. A failed start runs the
shutdown steps before raising.

Other fixes: setting a value that creates a map entry counts as a
change, a theme without a color or font goes back to the app's own,
and a bound minimum size applies on a window's first build. Decoded
values are compared the way Lua compares them, so true is not 1.

The binding tracker alone keeps the last applied values and hands them
to each applier. Context is gone: the engine carries the dispatcher,
services receive only the dispatcher, the relative-pointer service is
looked up by name, and hot corners follow display changes through the
queue. ApplicationRuntime requires its root config, so the engine no
longer imports python_defaults. Key names get their canonical spelling
only in the keyboard service.

The parity test changes on purpose: every prompt and notice now hides
when closed instead of asking to quit, which looped while the keyboard
was hidden. AGENTS.md describes idle actions, posting, handler
failures, and the new load checks.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

The helper package that profiles build their config with, and the
standard library inside it, now live in python_defaults/axidev_osk, and
every profile, test, and doc writes axidev_osk.button(...) and
axidev_osk.std instead of osk. The docs name the future Lua table
axidev_osk too. Mentions of the Windows osk.exe keep their name.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Each attachment kind now owns its attach step, so installing no longer
switches on four kind names and a new kind cannot decode and then do
nothing. An attachment whose service is missing, such as hot corners
on the lock-screen panel, is logged instead of dropped silently.

Button presses and title-bar drags post their events like the other
Qt handlers, and the dispatcher takes posted messages one batch at a
time, so a second press sees the state the first one set. A binding
that fails when it is bound posts its report instead of draining the
queue inside bind.

Other fixes: a palette binding that returns None leaves the role
absent, and a style property changing from true to 1 is applied.

One owner per fact: window.move_by registers with the other window
actions, the engine's widget property names live in
widget_properties.py, every attachment default lives on its settings
record, and the test helpers move from runtime/testing.py into
tests/support.py with the members only tests used. The hot-corner
header comment no longer calls the controller temporary.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

AGENTS.md now maps the codebase: it states that the codebase is the
engine, services that talk only through the queue and dispatcher, and
that python_defaults/ is temporary until the Lua layer replaces it.
What each module does stays in its docstrings, which now also state
the behavior AGENTS.md used to describe: event handler failures and
the drain warning, booleans never equal to numbers, state kept only
in memory, dotted names in state views, quit reasons and SIGTERM,
observed variables, and when check_options runs.

PURIFICATION_PLAN.md is deleted because its steps are done; the open
Lua work is tracked in issue #8.

Also remove typed action builders nothing calls: keyboard_up,
keyboard_tap, keyboard_type_text, process_spawn, log_info, log_warn,
log_error, and linux_open_permission_setup.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

The service takes the dispatcher since services stopped receiving the
whole context, but the tests still passed the engine. On Windows the
activation then failed inside a Qt slot and never reached the profile,
which failed every Windows CI run; the test is skipped elsewhere.
…oks to std

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

The primitives now leave profiles only verbosity as a limit.

Timers: timer.start and timer.cancel run timers by ID, and timer.fired
reports each one; stopping the profile cancels them, so long-press,
auto-hide, and hold-to-confirm are profile code.

Hover: buttons take on_enter and on_leave, which always alternate even
when Qt repeats an enter.

Pointer reports: any node with reports_pointer posts
node.pointer_pressed, node.pointer_moved, and node.pointer_released with
the position in its own pixels, over its children too, without taking
their clicks, so a swipe across a grid of keys reaches the profile.

The engine button no longer has active and latched, which only set the
stylesheet properties pressed, latched, and interactionState. std.lit
sets them through style properties, and std keys and the Dwell button
use it, so the theme is unchanged. Node kinds no longer reserve
properties of their own.

The title bar stays in the engine: on Windows the window keeps the
operating system's frame, so the engine does not promise profiles a
title bar of their own; windows/chrome.py says why.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

A dwell click on a button runs animateClick, which releases it from a
100 ms timer, and the tests checked once after a fixed 150 ms wait. On
a busy CI runner the timer fired after the check and the click was
missed. The tests now process events until the click or the end of the
fade arrives, failing after 2 s. Waits that check that nothing happens
keep their fixed delay.
…se button

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

The dwell indicator's size, dot range, and fade time were constants in
the engine, while the hot-corner indicator already took its size as an
option. They are now the dwell options indicator_size_px,
indicator_min_dot_px, indicator_max_dot_px, and indicator_fade_ms, with
the old values as defaults, so a profile can enlarge the dot for a user
who cannot see a small one.

A stylesheet already sizes every part of the engine-drawn chrome by
object name; only the close button's text was fixed. A window's
close_label value now sets it, and it may follow state.

AGENTS.md now says the engine fixes no color, font, size, timing, or
visible text a user may need to change.
…t it means

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Four interactions were decided by the part that saw them. A button
turned a right click into a left one and ignored the middle button,
dragging the title bar or the resize grip moved or resized the window
directly, and dwell clicked the widget under the pointer itself.

Now each is reported with what was observed. button.pressed and
button.released, and the pointer press and release reports, carry the
mouse button. The title bar and grip post window.dragged and
window.resize_dragged, and a finished dwell posts dwell.completed with
its screen point. The engine adds the effects window.resize_by and
window.click (with a mouse button), and std.with_handlers gives the
reports their usual meaning: move, resize, and a left click. Standard
keys ignore the mouse button, so left and right still do the same.

AGENTS.md now states that rule, the promise of the same behavior on
every platform, which code may call services, and opt-in reports for
frequent observations, and maps the windows, components, services,
and platform packages.
…t from

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

AGENTS.md now states how a feature is promoted: from a profile to the
standard library when most profiles need it, and to an engine built-in
only for speed, native access, or a platform detail. Every built-in
can be turned off, the engine still reports the raw interactions it is
built from, and its code states why it stays in the engine.

Any mouse button now drags the title bar and the resize grip and
presses the close button, and the events name it. std moves and
resizes only on the left button; the close button still closes on any
button unless the window turns off default_close. Back and forward
press buttons like right and middle.

Nodes can opt in to node.pointer_hovered and node.pointer_left
(reports_hover), node.wheel (reports_wheel), and node.geometry_changed
(report_geometry), which records nodes.<id>.position and size.

Hot corners can be disabled (enabled), can report only
(fires_on_rest), and post hot_corner.entered and hot_corner.left. Their
indicator sizes and opacity are options, and the theme colors carry
their own alpha.

The pointer locator glows each button in its locator_color. The
palette moved out of the engine into std.locator, which colors keys
from their reported geometry; the default keyboard keeps all 89
original colors.

Dwell's reversal angle, acceleration, poll interval, and indicator
opacities are options. Window placement is an anchor, one of 9 named
ones or any x and y, and keep_on_screen can turn off the screen clamp
for floating windows.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Fix two bugs: showing a window again no longer shrinks it back to its
minimum, and the pointer locator follows each button's shape instead of
its bounding rectangle.

Make the remaining fixed behavior optional or reported:

- windows: fit_content (default on) and observed content_size;
  window.move_to and window.resize_to actions; observed screens from
  display.configuration_changed, which moves to engine_messages
- chrome: drag start and end are reported on every platform, the grip
  reports window.resize_drag_started and window.resize_drag_ended, and
  title-bar padding and spacing come from the stylesheet
  (qproperty-spacing); the default theme keeps the same geometry
- dwell: pause_while_pressed (default on)
- grid: bindable row_stretch and column_stretch
- buttons: every mouse button presses, unnamed ones report "other"
- std.locator: colored, on and palette take opts
…ndows

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

The mask of every mouse button but the left one was ~LeftButton. On the
Windows CI runner, with PySide6 6.12, that complement matched no button,
so back, forward, and unnamed buttons stopped pressing a Button there.
The mask is now AllButtons without LeftButton.
… Wayland refuses

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

A plain window (always_on_top off) ignored window.move_by and
window.resize_by, and so move_to and resize_to, without telling the
profile. It now moves and resizes as asked. Wayland gives an ordinary
window no way to choose its position, so a move there raises and the
profile receives action.failed with that reason.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant