Skip to content

Add Support for Linux#69

Draft
SudoWatson wants to merge 24 commits into
petoncle:mainfrom
SudoWatson:linux-support
Draft

Add Support for Linux#69
SudoWatson wants to merge 24 commits into
petoncle:mainfrom
SudoWatson:linux-support

Conversation

@SudoWatson

@SudoWatson SudoWatson commented Jul 18, 2026

Copy link
Copy Markdown

Adds support for Linux operating system, currently only X11 environments (not yet Wayland)

Fixes #58

Still in development

AI was used to assist development. Will not merge without every line being reviewed by a human

SudoWatson and others added 17 commits July 8, 2026 21:17
Rebases linux-support onto petoncle/mousemaster main after PR petoncle#64 was
reviewed and merged with structural changes. Key adaptations from the
old draft to the merged interfaces:

- LinuxKeyboard implements KeyboardController (was Keyboard)
- LinuxMouse implements MouseController (was PlatformMouse)
- LinuxClock implements Clock (was PlatformClock)
- LinuxPlatform.reset() takes MouseManager (was MouseController)
- KeyRegurgitator is now a plain class wrapping KeyboardController;
  LinuxKeyRegurgitator deleted, KeyboardLayoutProvider deleted
- LinuxPlatform.activeKeyboardLayout() replaces keyboardLayoutProvider()

Also carries over GridWindow, HintMeshWindow, Nix flake, and project
docs from the old branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Qt is bundled inside the JAR via qtjambi-native-linux-x64 (same as
Windows bundles DLLs via qtjambi-native-windows-x64). The dev shell
only needs JDK21/Maven for building, and the system libs that the
bundled Qt .so files will dlopen at runtime (xcb, xkbcommon, GL, etc).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread pom.xml
Comment on lines +188 to +190
<exclude>**/*.dll</exclude>
<exclude>**/*.so</exclude>
<exclude>**/*.so.*</exclude>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any new *.so* files in src/main/resources. Did you forget to commit them maybe?

SudoWatson and others added 7 commits July 19, 2026 18:01
* Fingerprint SendInput with dwExtraInfo and change default value of --ignore-injected-events to false (petoncle#66)

* Reimplement drawing of grid with Qt

* Implement grid transition animation

* Add grid line opacity, background, and fade animation

* Fade the grid from its current opacity with InOutQuad easing, and fade a new grid in when it replaces one mid fade-out

* Change default grid background color and opacity to red 0.1

* Update GraalVM reflect config after changes to grid rendering

* 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).

* Add fit cell sizing and last-selected-hint-cell grid area for recursive hint grids

hint.grid-cell-sizing=fit divides the hint area into exactly
grid-max-row/column-count cells (pixel sizes ignored); grid-area=last-selected-hint-cell
narrows the grid to the last selected cell. Together they enable neru-style
recursive grids built as one mode per depth level. Adds neru.properties and an
author.properties recursive-hint chain, plus a reference-doc note.

* Fix rounding issue when drawing subgrid

* Add subgrid-closed option and fix open subgrid interior lines

* Draw the subgrid as a nested hint mesh

* Extract Qt color and font helpers out of WindowsOverlay

* Extract GridRenderer out of WindowsOverlay

* Extract IndicatorRenderer and shared StackedShadowEffect out of WindowsOverlay

* Extract ScreenshotWidget out of WindowsOverlay

* Extract HintMeshRenderer out of WindowsOverlay

* Re-resolve grid-cell-width/height on variable mutation by pointing mutation paths at the nested cellSizing record

In author configuration, hint3 zoomed would break.

* Add subsubgrid for neru-like dots in the recursive grid

* Implement built-in combo variable isidling

* Move cross-platform indicator detection and positioning into IndicatorRenderer

* Move the renderer classes out of the qt package into a dedicated renderer package

* Port zoom edge-clamp fix into shared qt/ScreenshotWidget

Our Linux-only qt/ZoomWindow (now being retired in favor of the
shared ScreenshotWidget from petoncle/main's overlay refactor) had a
hardware-confirmed fix: clamp the zoom source rect to the captured
pixmap's bounds and shrink the target rect by the same scale, so
regions with no captured pixels stay black instead of leaving a
transparent gap near screen edges/corners. ScreenshotWidget lacked
this. Port it in before consolidating Linux onto this shared class,
since Linux uses it as the sole, persistent zoom-rendering mechanism
(no live-magnifier handoff like Windows has), so the gap would be
visible in steady state rather than only during a brief transition.

* Rewrite LinuxOverlay onto petoncle/main's shared renderer architecture

Replace our hand-rolled qt/GridWindow, qt/HintMeshWindow, qt/ZoomWindow
(deleted in the previous commit) with the same platform-agnostic
GridRenderer, HintMeshRenderer, IndicatorRenderer, and ScreenshotWidget
that WindowsOverlay now uses post-refactor. LinuxOverlay becomes a thin
adapter: it owns the renderer instances, supplies TransparentWindow via
the existing X11-flagged base class, and handles only X11-specific
window management (real setTopmost() via Qt raise(), plus applying the
same window flags to GridRenderer's/ScreenshotWidget's own widgets,
which - being shared with Windows - only set FramelessWindowHint
themselves and rely on the platform to add topmost/click-through
styling, the way Windows does via native WS_EX_* calls).

This fixes a real bug for free: our old HintMeshWindow silently dropped
the Zoom parameter passed to setHintMesh(), which HANDOFF.md root-caused
as the cause of second-level hint overlays rendering at screen-center
instead of the zoomed location, and third-level/history-mode hints
being unreadably small. HintMeshRenderer correctly transforms hint-box
geometry by Zoom internally.

It also adds a real mouse indicator to Linux for the first time -
setIndicator()/hideIndicator() were previously complete no-op stubs.
Since MouseController has no cursorVisualCenter()/mouseSize() lookup on
Linux, a fixed cursor-size fallback is used for now (mirrors Windows'
own fallback-when-lookup-fails pattern); a real XFixesGetCursorImage
lookup is a follow-up. Active-screen resolution reuses the existing,
already platform-agnostic ScreenManager.nearestScreenContaining()
rather than adding new X11 code.

The one-tick-deferred screenshot-capture mechanism for zoom (hide our
own windows, grab the screen one tick later once the hide is actually
flushed, since Linux has no capture-exclusion API like Windows'
WDA_EXCLUDEFROMCAPTURE) is preserved, just re-targeted at the new
renderers' windows instead of the old bespoke classes.

LinuxPlatform gains lastMouseX()/lastMouseY() getters (exposing the
XQueryPointer-polled position already tracked for Fix 4's mouse-move
listener notifications) for the indicator glue, forwards mouse moves
to LinuxOverlay.mouseMoved() for live indicator repositioning (Windows
does this via its low-level hook; X11 has no such hook), and now calls
overlay.setTopmost() on a periodic timer mirroring WindowsPlatform's
own 200ms re-assertion against WMs that don't fully respect
WindowStaysOnTopHint.

* Fix overlay windows swallowing all clicks by making them click-through

GridRenderer/HintMeshRenderer's hide() calls never actually unmap the
underlying window - they only clear the drawn content and reset
opacity, leaving the real window mapped indefinitely once first shown
(GridRenderer's widget spans the entire virtual desktop;
HintMeshRenderer's per-screen windows span entire screens). This is
safe by design on Windows, where WS_EX_TRANSPARENT is applied once at
HWND creation, making the window permanently click-through regardless
of its "logical" visibility.

The Linux port never added the Qt equivalent
(WA_TransparentForMouseEvents), so the first time any grid or hint
mesh was shown, its window became a permanent, invisible, full-desktop
click blocker - swallowing every click (both our own synthetic
XTestFakeButtonEvent clicks and real physical clicks) from that point
forward, in every mode including idle-mode, until the process was
killed.

Fix: set WA_TransparentForMouseEvents in TransparentWindow's
constructor (covers indicator + hint-mesh windows, both created via
TransparentWindow) and in LinuxOverlay.applyX11OverlayFlags() (covers
GridRenderer's widget and ScreenshotWidget, which are shared with
Windows and don't extend TransparentWindow).

Found via hardware testing: clicking (both program-driven and
physical trackpad) stopped working entirely as soon as any hint or
grid overlay had been shown once, in any mode afterward.

* Make overlay windows click-through via the X11 Shape extension

Qt's WA_TransparentForMouseEvents attribute (added in the previous
commit) turned out not to be enough on its own for these frameless,
override-redirect (X11BypassWindowManagerHint) windows - confirmed via
hardware testing that clicks still didn't pass through.

Add real, guaranteed click-through by clearing each overlay window's
X11 input shape directly via the XShape extension
(XShapeCombineRectangles with a null/empty rectangle list on
ShapeInput) - the same mechanism compositors and other click-through
overlay tools use, independent of Qt's platform-plugin behavior for
this specific window configuration.

Applied uniformly to all four overlay window types via
applyX11OverlayFlags(): the indicator window, each per-screen hint
mesh window (via a new createStyledHintMeshWindow() factory passed to
HintMeshRenderer, mirroring WindowsOverlay's own factory pattern),
GridRenderer's widget, and ScreenshotWidget.

* Fix indicator doubled position

* Fix modifier keys being blocked

---------

Co-authored-by: petoncle <albert.petoncle47@gmail.com>
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.

Linux version want

2 participants