Skip to content

Tags: petoncle/mousemaster

Tags

nightly

Toggle nightly's commit message
Keep the click animation and drag alive after a hint click

93

Toggle 93's commit message
Build 93

- **The indicator animates**: when the indicator changes, it now eases into the new one instead of jumping to it. Sizes, thicknesses, opacities, blur radiuses, offsets and fill percents are interpolated, and the polygon morphs two edges at a time. Everything with no meaningful in-between — the colors, the label, the position — switches instead, at the start of the transition or at its end, as `transition-animation-switch-at` says. The animation belongs to the indicator being transitioned *to*, so each direction picks its own duration and easing:
    ```properties
    normal-mode.indicator.size=26 | _{ismousepressing} -> 39
    normal-mode.indicator.transition-animation-duration-millis=200 | _{ismousepressing} -> 50
    normal-mode.indicator.transition-animation-easing=smootherstep
    normal-mode.indicator.transition-animation-switch-at=end | _{ismousepressing} -> start
    ```
    Above, a click grows the indicator and colors it in 50ms, and releasing takes 200ms to shrink back, keeping the color until it has. A change that interrupts a running animation eases from where it had reached, so nothing snaps. Every shipped configuration now animates a click: the indicator fills solid in the button's color, bursts outward into a ring, then eases back. The animation currently looks like this:

    https://github.com/user-attachments/assets/2f2c21b6-972b-4365-863b-943a5f1f360f

- **The mouse states are built-in virtual keys**: `indicator.<state>.<property>` is gone. Any property, and any combo, can branch on `ismoving`, `iswheeling`, `ismousepressing`, `isleftmousepressing`, `ismiddlemousepressing`, `isrightmousepressing`, `isunhandledkeypressing` and `isidling`, which now means the mouse alone: not moving, not wheeling, no button pressed.
    ```properties
    normal-mode.indicator.color=#FF0000 | _{iswheeling} -> #FFFF00 | _{ismousepressing} -> #00FF00
    ```
    The old per-state lines still load, with a warning: `idle` becomes the property's default value and every other state a branch on its key.

- **A screen filter reads as a key**: outside hint properties, a filter such as `_{300%}` or `_{3840x2160-150%}` is a key mousemaster presses while the active screen matches it, so any property, command or mode switch can branch on it, and it can share a block with a real key. On a hint property it keeps its own meaning: there it selects the screens a value is drawn on, since one hint mesh can span several.
    ```properties
    grid-mode.grid.line-thickness=1 | _{300%} -> 0.66667
    normal-mode.press.left=_{3840x2160-300%} +leftbutton
    ```

- **A mode can extend several modes**: `normal-mode=_indicator-mode _clipboard-mode`. The mode's own properties come first, then each parent in turn, and the first one to define a property keeps it.

- **A virtual key is declared on its own line**: `virtual-key.iszoom=released`, `virtual-key.showkeys=pressed`, so the declaration can sit next to the macros that press the key. The `virtual-keys=` list still loads, with a warning.

- **Key logging**: `logging.redact-keys` becomes `logging.key-redaction=none` (the default), `pseudonymize` or `anonymize`. `pseudonymize` logs a key that types a character as `key#1`, a name stable for that run only, so a press and its release still match and a key that was never released is still visible, without the log spelling out what was typed. `log-last-key-events-on-exit=true` logs the last key events in combo syntax, with the milliseconds between them (`[+a-377 -a-32 +leftwin]`); they are always logged when a stuck key is detected. Both are there to report a combo that stopped working without leaving `logging.level=TRACE` on.

- **Fixes**: A completed combo no longer runs its command a second time without a new key event. A hint grid opened with no previous selection is centered on the screen, like the zoom already was. A decoration covering the whole hint grid is centered on the grid instead of on the part one screen shows. The zoom and a selected hint now cope with a point that falls outside every screen. A hint label's shadow is blurred over its glyphs rather than over its whole cell. A Windows key event is identified by its virtual key in addition to its scan code, so an extended key such as volume up is no longer taken for the letter that shares its scan code. mousemaster reloads the system cursors at startup, repairing the cursor a killed run left behind. On macOS, a jump to an odd pixel no longer pins the mouse on a Retina screen.

- **Also**: A property cycle is now reported as an error instead of silently dropping the properties in it. Less work is done per key press, per screen enumeration and per indicator render. The download is smaller: the unused Qt plugins are gone, the remaining libraries are embedded compressed, and the macOS bundle is trimmed. The author configuration and [its documentation](https://github.com/petoncle/mousemaster/blob/main/docs/author.md) were rewritten, and neru's hint mode now moves the mouse as a hint is typed.

92

Toggle 92's commit message
Build 92

- **macOS support**: mousemaster now runs on macOS, with everything Windows has: mouse movement, clicks and scrolling, the grid, the indicator, hints (grid, position history and UI hints), zoom, key remapping and macros. Download **mousemaster-macos.zip** below — it holds a universal app bundle that runs on both Apple Silicon and Intel. The keyboard is read through the Karabiner virtual HID device, so Karabiner-Elements has to be installed, and mousemaster has to be started as root. macOS asks for a few permissions: **Input Monitoring** always, **Accessibility** for UI hints, and **Screen Recording** for zoom. See the [installation steps](https://github.com/petoncle/mousemaster#macos). macOS support is new and testers are wanted: please report what does and does not work in an issue.

    Two built-in keys, `iswindows` and `ismacos`, let one configuration file cover both:
    ```properties
    ui-hint-mode.hint.font-size=10 | _{ismacos} -> 13
    hint1-mode.hint.font-name=Segoe UI Symbol | _{ismacos} -> Helvetica Neue
    ```
    On macOS, `leftwin`/`rightwin` are the command keys and `leftalt`/`rightalt` the option keys.

- **Several position histories**: a history is named like a mode, ending in `-position-history`, and every command and hint mode names the one it acts on. A history can also keep a separate list per app, so a position saved in one app never shows up in another.
    ```properties
    browser-position-history.max-size=8
    browser-position-history.isolation=active-app
    app-alias.browserapp=firefox.exe chrome.exe edge.exe
    normal-mode.browser-position-history.save-position=_{browserapp} +f2
    browser-hint-mode.hint.type=browser-position-history
    ```
  The global `max-position-history-size` is now `position-history.max-size`. Since `hint.type` can be combo-triggered, one hint mode can show different histories: `position-hint-mode.hint.type=position-history | _{browserapp} -> browser-position-history`.

- **Virtual keys replace variables**: `variable.`, `set-variable`, `unset-variable` and `reset-variables` are gone. A virtual key does everything a variable did and reads like a key: declare it in the global `virtual-keys` list, press it from a macro with `#`/`+`, release it with `~`/`-`, and test it with `_{}`/`^{}`. Unlike a variable, it can share a precondition block with a real key. A `+` prefix in the declaration makes it pressed from the start. `isidling` is now a built-in virtual key.
    ```properties
    virtual-keys=iszoom +showkeys
    hint-mode.macro.setiszoom=^{iszoom} +z -> #iszoom
    hint-mode.macro.unsetiszoom=_{iszoom} +z -> ~iszoom
    hint-mode.zoom.percent=1.0 | _{iszoom} -> 2.0
    hint-mode.hint.box-color=#000000 | _{iszoom rightalt} -> #FF00FF
    ```

- **Screen-specific values are a precondition now**: the `property.3840x2160=value` suffix is replaced by a branch on the screen, like a branch on a key. A filter is a resolution (`3840x2160`), a scale (`150%`), or both (`3840x2160-150%`), and it can sit in the same block as a key. Each screen resolves its own value when the hints are drawn, so a hint mesh covering several screens gets a value per screen.
    ```properties
    hint1-mode.hint.box-border-radius=1 | _{200%} -> 3
    ```
    `screen-alias` names a set of screens once, like a key alias, so a screen is written down in one place instead of once per property:
    ```properties
    screen-alias.largescreen=3840x2160-100% 3440x1440-100%
    hint1-mode.hint.grid-max-column-count=25 | _{largescreen} -> 40
    ```
    The built-in configurations were rewritten around this and lost about a thousand lines of per-resolution duplication.

- **Every length scales with the screen**: paddings, border thicknesses, radiuses, border lengths, shadow blurs and offsets and grid line thicknesses are now in the pixels of a 100% screen, and the screen's scale grows them, exactly as it already grew font sizes. Hints and grids therefore keep their proportions on a 200% or 300% screen with no per-screen value at all. To pin a length to a pixel count on one scale, override it there and divide: `hint.box-border-thickness=1 | _{150%} -> 0.67` draws a single pixel at 150%.

- **UI hints have their own area**: `hint.ui-area` selects the windows UI hints look in (`active-screen`, `all-screens`, `active-window`), instead of borrowing `hint.grid-area`. The two are now exclusive: `grid-area` only applies to `hint.type=grid`, and setting it on a UI hint mode is rejected.

- **Hint box frames**: `box-framed` and `prefix-box-framed` (default `true`) choose whether the outer frame around the whole grid is drawn, or only the lines between the boxes. A border is also never drawn where an enclosing one already draws it, so hint boxes no longer double the prefix box's border.

- **Also**: The documentation moved from `configuration/` to [`docs/`](https://github.com/petoncle/mousemaster/tree/main/docs), and every built-in configuration now has a keyboard layout image and is linked from the readme. The neo-mousekeys configurations gained a recursive hint mode, on _r_, drilling with _u_ _i_ _o_ _j_ _k_ _l_ _m_ _,_ _._. mousemaster now exits when the console it was started from is closed, and the executable carries proper version information in the Windows Task Manager.

91

Toggle 91's commit message
Build 91

- **Faster hints**: Label shadows are now computed using every available CPU cores. Hint text is now drawn with greyscale antialiasing instead of ClearType, so it looks slightly different. The hint grids that are slow to build are now prepared while mousemaster starts

- **New zoom rendering**: The zoom is now drawn with Direct3D instead of the Windows Magnification API.

- **Faster startup**: The executable is now built with GraalVM 25 instead of 21, which improved configuration parsing performance. The indicator and the zoom window are also prepared at startup, so the first mode that shows them no longer pays for it.

- **Snappier key handling**: Less work is done per key press.

- **UI hints beyond the active window**: `hint.type=ui` can now hint every visible window at once, not just the active one. `grid-area` selects how far it looks: `active-screen` (the new default), `all-screens`, or `active-window`. Scanning several windows takes longer than scanning one.
    ```properties
    ui-hint-mode.hint.type=ui
    ui-hint-mode.hint.grid-area=active-screen
    ```

- **Fixes**: Entering a mode that owns no recursive hint grid level no longer drops a level. Hint boxes at the right and bottom edge of a merged hint container were cut by a pixel. Hint labels no longer change appearance once you type the first key of a hint. UI hints no longer land on the browser's other windows, in Firefox as well as in Chrome. A line ending in an escaped backslash (`\\`) is no longer joined with the next line.

- **Also**: The neru configuration now shows the keys of the recursive hint grids by default.

90

Toggle 90's commit message
Build 90

- **Recursive hint grids**: A hint grid can now narrow into the cell you just selected, so a small grid of positional keys (e.g. 3x3) drills down to a precise point in a few keystrokes. `grid-cell-sizing=fit` divides the area into exactly `grid-max-row-count` x `grid-max-column-count` cells whatever the screen size, and `grid-area=last-selected-hint-cell` restricts the next level to the selected cell. One mode per depth level.
    ```properties
    recursive-hint2-mode.hint.grid-cell-sizing=fit
    recursive-hint2-mode.hint.grid-max-row-count=3
    recursive-hint2-mode.hint.grid-max-column-count=3
    recursive-hint2-mode.hint.grid-area=last-selected-hint-cell
    ```

https://github.com/user-attachments/assets/9140b3be-6109-4a90-a842-0a22c7e0e562

- **New neru configuration**: A configuration built around recursive hints. Download **neru.properties** below, or [see its documentation](configuration/neru.md).

- **Decorations**: Purely visual grids drawn over the hints, at three nesting levels: `decoration-` spans the whole hint grid, `subdecoration-` is drawn inside each hint cell (a preview of the next level's keys), and `subsubdecoration-` inside each of those. Each level has its own labels, font, fill, border and border length, so you can draw anything from a full preview grid to a single `+` mark. This replaces the previous `subgrid-`/`subsubgrid-` properties.
    ```properties
    hint-mode.hint.subdecoration-max-row-count=3
    hint-mode.hint.subdecoration-max-column-count=3
    hint-mode.hint.subdecoration-label-keys=recursivehintkey
    hint-mode.hint.decoration-label-override=plus
    ```

- **Hint grid area is now a size and a center**: `grid-area` sets the area's size (`active-screen`, `active-window`, `all-screens`, `last-selected-hint-cell`) and `grid-area-center` sets what it is centered on (`screen-center`, `mouse`, `last-selected-hint`, `active-window-center`). They combine freely, and `grid-area-width-percent`/`grid-area-height-percent` scale the area down.
    ```properties
    hint-mode.hint.grid-area=active-window
    hint-mode.hint.grid-area-center=mouse
    hint-mode.hint.grid-area-width-percent=0.5
    ```

- **Indicator as the mouse cursor**: `indicator.render-as-cursor` turns the indicator into the actual system cursor, with the real cursor glyph composited on top, so it stays crisp and never lags behind the pointer. Like other indicator properties, it can be changed per app or per variable. The system cursor is now also restored when mousemaster is closed with Ctrl+C or by closing its console.
    ```properties
    normal-mode.indicator.render-as-cursor=true
    ```

- **Variables**: `variable.<name>=true|false` declares a variable and gives it the value it starts with. A new built-in variable, `isidling`, is true while the mouse is not moving, clicking or wheeling.
    ```properties
    variable.iszoom=true
    normal-mode.indicator.idle.opacity=1 | _{isidling} -> 0.2
    ```

- **Zoom on the selected hint cell**: `zoom.area-size-source=last-selected-hint-cell` magnifies exactly the cell you selected, instead of a percentage of the screen.
    ```properties
    hint2-mode.zoom.area-size-source=last-selected-hint-cell
    hint2-mode.zoom.center=last-selected-hint
    ```

- **Smoother hint animations**: Hint box borders now morph along with the boxes during a transition, and an interrupted transition continues from where it is instead of restarting or snapping. Box borders are also drawn on non-grid hints, such as UI hints.

- **Nightly builds**: Every push to `main` now publishes a **nightly** prerelease, for trying out changes before they are tagged.

- **Also**: `virtual-keys` declares keys that exist only inside the combo system, for macros to use as internal signals without colliding with a real key. `font-vertical-alignment=middle` centers hint and decoration labels on their glyph box. `hint.label-override` draws a fixed marker in every cell instead of its keys. The built-in configurations now use a fitted subgrid for their second hint pass, with fonts and grid sizes tuned per screen resolution, and cover more resolutions. The DEBUG logs are more compact, and show the current mode in a column of their own. Attempted a fix for the JIS Japanese keyboard layout.

89

Toggle 89's commit message
Build 89

- **Grid drawn with Qt, with animations and styling**: The grid is now rendered with Qt, like the hints and the indicator, instead of the old GDI drawing. The grid has an optional transition animation when it moves or resizes, and configurable line opacity and background fill. The animation is enabled by default with a 100ms duration.
    ```properties
    grid-mode.grid.transition-animation-enabled=true
    grid-mode.grid.transition-animation-duration-millis=100
    grid-mode.grid.fade-animation-enabled=true
    grid-mode.grid.fade-animation-duration-millis=100
    grid-mode.grid.line-opacity=1.0
    grid-mode.grid.background-color=#FF0000
    grid-mode.grid.background-opacity=0.1
    ```

https://github.com/user-attachments/assets/b395505e-9a06-4ec8-b361-81323c7b3fb4

- **Injected event fingerprinting**: mousemaster now tags its own injected input with a fingerprint, so it can tell its own events apart from events injected by other software (like kanata).

88

Toggle 88's commit message
Build 88

- **Indicator fade animation**: The indicator fades in and out when it appears, disappears, or changes color (e.g. on mode switch), instead of popping abruptly. Enabled by default with a 100ms duration.
    ```properties
    normal-mode.indicator.fade-animation-enabled=true
    normal-mode.indicator.fade-animation-duration-millis=100
    ```
  The fade is automatically suppressed when the surrounding mode change also changes zoom, to avoid the faded indicator clashing with the zoom screenshot.

- **Hint fade animation**: Hints fade in when they first appear and out when they disappear. Enabled by default with a 100ms duration.
    ```properties
    hint-mode.hint.fade-animation-enabled=true
    hint-mode.hint.fade-animation-duration-millis=100
    ```

- **Mutation cascade for indicator and hint styling**: When a mutation changes a base font or indicator property, the related properties are automatically updated as well. For example, mutating `hint.font-color` also mutates `selected-font-color`, `focused-font-color`, and the prefix variants. Mutating `indicator.idle.color` also mutates the `move`, `wheel`, and `mouse-press` states. Properties explicitly set in the configuration are not overwritten.

- Hint grid column count now works correctly with a single row (previously threw an exception in some configurations).

- UI hint mode now skips thread-shared sibling windows that aren't on a monitor the focused window touches. Owned popups, dropdowns, and dialogs are still included regardless of monitor.

- **`--version` command line flag**: Prints the version and commit id, then exits.
    ```
    > mousemaster.exe --version
    mousemaster v88 (abc1234)
    ```

- The build commit id is now logged at startup, making it easier to identify which build is running.

- **Variable combo preconditions** (experimental feature): Variables are boolean flags that persist across mode switches, settable from combos and usable as combo preconditions. This is a step up from key-based mode property mutations (introduced in v87), since a variable stays set until you clear it; surviving key releases.

  Variables are set, unset, or cleared with three new properties:
    ```properties
    mode.set-variable.iszoom=combo           set iszoom when combo matches
    mode.unset-variable.iszoom=combo         unset iszoom when combo matches
    mode.clear-variables=combo               unset all variables
    ```

  Variables are referenced in combo preconditions with the same `_{}` syntax as keys:
    ```properties
    # Toggle iszoom on/off with z, change zoom percent and hint cell size based on it
    hint-mode.set-variable.iszoom=_{!iszoom} +z
    hint-mode.unset-variable.iszoom=_{iszoom} +z
    hint-mode.zoom.percent=1 | _{iszoom} -> 30
    hint-mode.hint.grid-cell-width=10 | _{iszoom} -> 320
    hint-mode.hint.grid-cell-height=6 | _{iszoom} -> 180
    ```

  In the author configuration, variables are now used to model click-after-hint behavior, slow/zoom toggles, and the "no move" toggle, replacing several single-purpose modes that previously existed only to carry a flag.

  See documentation: https://github.com/petoncle/mousemaster/blob/main/configuration/combo-reference.md#variables

87

Toggle 87's commit message
Build 87

- **Mode property mutation**: Mode properties can now change dynamically based on which keys are held, without switching to a separate mode. The syntax uses `|` and `->` inline in the property value:
    ```properties
    key-alias.slow=leftshift
    key-alias.fast=v b
    # Hold leftshift for slow movement, v or b for fast.
    normal-mode.mouse.max-velocity=2200 | _{slow} -> 350 | _{fast} -> 4500
    ```
  When the modifier key is released, the value reverts automatically.

  Another example: a hint mode can be configured to click on hint selection when a modifier is held (see the built-in configurations for full examples). With mutations, you could change the background color when the modifier is held to give visual feedback that a click will follow:
    ```properties
    hint-mode.to.click-after-hint-mode=_{leftshift} +hintkey
    hint-mode.hint.box-color=#FFFFFF | _{leftshift} -> #FFFF00
    ```

  A more advanced example: the indicator label text can be mutated to display the last pressed key (or last two keys) to create a typing trail:
  <p align="center">
  <img style="width: 100%; height: 100%;" alt="" src="https://github.com/user-attachments/assets/ba9b0310-d8e0-450a-9e8e-e0144676df69" />
  </p>

  Any property can react to held keys, including visual properties like colors, font sizes, and opacity. It eliminates the need for single-purpose modes: for example, the speed modifier modes (slow-mode, fast-mode) in the built-in configurations have been replaced by inline mutations.

- Implemented optional zooming animation.

- Fixed indicator label shadow stacking.

86

Toggle 86's commit message
Build 86

- **Mouse acceleration easing**: Choose how the mouse accelerates from initial velocity to max velocity using the `acceleration-easing` property. Available easing functions: `linear`, `polynomial-N` (where N is the exponent), `smoothstep`, `smootherstep`, `logarithmic`, `exponential`. The default is `smootherstep`, which feels natural and smooth.
    ```properties
    normal-mode.mouse.acceleration-easing=smootherstep
    ```

- **Mouse deceleration**: When all direction keys are released, the mouse now decelerates to a stop instead of stopping instantly. Configured in pixels/second².
    ```properties
    normal-mode.mouse.deceleration=20
    ```

85

Toggle 85's commit message
Build 85

- **Combo system overhaul**: Major expansion of the combo syntax with wait moves, tap shorthand, negated moves, any-order move set, and ignored key sets. These new building blocks enable patterns like tap-hold, tap-dance, oneshot modifiers, chords, and zippy chords directly within combo expressions.
  See documentation: https://github.com/petoncle/mousemaster/blob/main/configuration/combo-reference.md

- **Alias remapping in macros**: Aliases can now be remapped per-key in macros, allowing a single macro definition to produce different outputs depending on which alias key was pressed.
    ```properties
    key-alias.dir.us-qwerty=i j k l
    normal-mode.macro.pressarrow=+dir -> dir=uparrow leftarrow downarrow rightarrow -> +dir
    ```

- **Default indicator redesign**: The default indicator is now a semi-transparent circle centered on the cursor with state-specific colors: red (idle/move), yellow (wheel), green (left click), magenta (middle click), cyan (right click).

- **Mode timeout deprecated**: The `timeout` property is replaced by wait moves in combo expressions.
    ```properties
    # Old (deprecated):
    oneshot-mode.timeout.duration-millis=250
    oneshot-mode.timeout.mode=idle-mode
    # New:
    oneshot-mode.to.idle-mode=wait-250
    ```

- Fixed overlay native crashes.
- Fixed keyboard state not being reset when the active keyboard layout changes.