This documentation set accompanies the AutoHotkey v2.0.26 Linux port (MonoEven/Autohotkey_Linux). The pages in this site are the official AutoHotkey v2 documentation, adapted for the Linux port. Only AutoHotkey v2 syntax is supported; the AutoHotkey v1 language, the v1-to-v2 migration material, and all Windows-specific behavior are not part of this port.
These pages mirror the official AutoHotkey v2 documentation, so their examples fall into three groups on Linux:
C:\... are treated as plain (oddly named) files on
Linux; use Linux paths (/tmp/..., A_Desktop,
~) in your own scripts.ComObject, ComObjGet, ...) add a Linux
example block at the top of their Examples section that uses native
.so libraries or the D-Bus bus; the original Windows
examples are kept for reference only.ComObjArray, ComCall,
SendMessage and OnMessage are kept for reference;
their Linux wrappers raise a documented error. Adapted features such as
TrayTip/TraySetIcon and the file-backed registry have Linux examples instead
of being mislabeled unavailable. Many examples are short snippets that need
surrounding setup; the generated catalog links that setup.Complete Linux catalog: FUNCTION_COVERAGE.md
maps all 370 IMPL entries to exact Linux-executed source lines and
environment profiles; examples/generated/ contains one function
page each (case-insensitive filenames included), and 230 pages also retain an
upstream code example. CI rejects omissions and generator drift and runs every
unattended curated example. Interactive and destructive operations have explicit
interaction/safety-boundary examples.
The generated worklist records 370 implemented functions and three
explicit unavailable Windows COM boundaries (ComObjArray/Connect/Query);
TrayTip is a desktop notification and TraySetIcon a StatusNotifierItem tray
icon. 1170 doc-check assertions pass under Xvfb (including 29
for DllCall against native .so libraries and migration errors,
and 20 for the D-Bus COM layer). The port has two display backends:
input_backend.h), which
selects automatically:
1::/^1::/F12::
grab and suppress the physical key with no per-binding dialog). The
companion extension (extension/ in the repo) is a thin
D-Bus broker (io.github.autohotkey.GlobalHotkeys1) that
registers accelerators through
Meta.Display.grab_accelerator() +
Main.wm.allowKeybinding() (the whitelist step the portal
backend performs via shellDBus) and relays activations back; it never
parses AHK, executes commands or forks.
Verified on Ubuntu 25.10 + GNOME 49 (E2E: physical
1 fires the hotkey with no confirmation dialog, the key
is suppressed from the focused app, exiting the script restores the
key, kill -9 releases the grab fail-open, Ctrl+1/F12
and key-repeat work, and a gnome-shell extension reload
(disable/enable) is followed by automatic re-registration).
Protocol hardening (v2): activations are delivered directed
to the registering owner only (no broadcast); hotkey ids are
owner-scoped (<unique-bus-name>/<hotkey>);
ClearOwner takes no owner argument (the caller's own
unique name is used); registrations are batched
(RegisterMany/UnregisterMany) with bounded
timeouts, and only the Activated signal is consumed
(Deactivated is explicitly ignored).
Capability scope (v1): exclusive hotkeys only — ~1::
passthrough, remaps, a & b:: and full wildcards are
NOT in this backend (evdev/ahk-inputd lane); 1 up:: has
its signal source (accelerator-deactivated is relayed) but is not yet
consumed by the runtime.org.autohotkey.linux.desktop, and ahk --diag
reports portal-app-id-resolvable). Verified on the
GNOME 49 VM: the backend is present (interface version 1 with
CreateSession/BindShortcuts/ListShortcuts, Activated/Deactivated
signals) and auto selection routes to it once the extension is
disabled — with no spurious error, because the functional
probe finds the backend. The runtime watches the portal bus-name owner:
after a crash/restart it discards stale session/bind handles, recreates
the session and rebinds the same desired shortcuts. A two-owner fault
oracle proves both activations reach one AHK PID; session-bus reconnect
attempts are throttled to 500 ms.| GNOME version | Native hotkey backend | VM-verified |
|---|---|---|
| 45-47 | AHK shell extension only (no Global Shortcuts portal backend exists); auto reports a loud install-the-extension error when neither is present | declared, not verified |
| 48+ | AHK shell extension, or the Global Shortcuts portal backend (auto falls back to it when the extension is absent) | 49 verified (extension E2E + portal backend presence + auto fallback + deny/cancel regression) |
AHK_INPUT_BACKEND=auto|x11|portal|gnome-shell|evdev
(auto: X11 session → X11; GNOME Wayland with the extension installed
→ gnome-shell; GNOME without the extension but with a Global Shortcuts
portal backend live on the session bus (shipped since GNOME 48) →
portal; GNOME <48 without the extension → portal plus a loud,
actionable startup error pointing to the extension install (a portal
selection there could never bind, so silent auto-selection was a trap);
other Wayland → portal. Selecting evdev first connects to a
per-user broker in $XDG_RUNTIME_DIR, then to the packaged
socket-activated /run/ahk-inputd.sock; an explicit
AHK_INPUTD_SOCKET overrides both. Automatic discovery never
trusts /tmp).
The GNOME/portal decision is made by a functional probe
(read the org.freedesktop.portal.GlobalShortcuts version
property on the session bus), not by version sniffing, and is reported by
ahk --diag as gnome-major,
portal-global-shortcuts and
portal-app-id-resolvable.
AHK_FORCE_GLOBAL_SHORTCUTS=1 keeps its documented meaning:
explicitly require the standard Portal backend (it is not reused for the
GNOME Shell backend); only 1/true/yes/on count as true, and
an unknown AHK_INPUT_BACKEND value prints a clear startup
warning instead of silently running a different backend. An
XWayland/Xorg session remains the stable fallback on all systems.HotkeyBackendGet() exposes the same schema used by
ahk_core --diag and the generated table below.
Capability schema: caps_version=2. The values below are implementation capabilities, not current health. Query HotkeyBackendGet() for state, generation, coverage, route guarantees and compatibility outcome.
| Backend | Global | Suppress | ~ pass | Key-up | Wildcard | Bare | Zero-confirm | Dynamic | Multi-owner | Scan code | Custom combo | Character stream | Provenance | SendLevel gate | Direct Unicode |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| x11 | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | yes | heuristic | yes | yes |
| portal | yes | yes | no | no | no | no | no | yes | no | no | no | no | none | no | no |
| gnome-shell | yes | yes | no | no | no | yes | yes | yes | yes | no | no | no | none | no | no |
| evdev | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | authoritative | yes | no |
| libei | no | no | no | no | no | no | no | no | no | no | no | no | none | no | yes |
heuristic provenance means process-local/device heuristics. The inputd v2 broker stamps authority/generation/event and transaction identity and therefore reports authoritative; XTEST without broker sideband remains heuristic or unknown. A no value remains no until a matching behavior test is added.
Normalized input pipeline: ordinary, wildcard, left/right-modifier and key-up X11-grab, inputd broker and in-process evdev hotkeys use the same versioned event acceptance, generation-aware logical/physical state reducer, consumer level gate, unique-resolution matcher, key-up ownership, decision and dispatch path. InputHook key/char callbacks and Hotstring raw/held matching consume the same accepted-event context and emit consumer decisions/outcomes; broker capture reuses the existing acceptance rather than reducing twice, while X11 raw and selected-grab domains remain explicitly separate. HotkeyBackendGet() exposes pipeline_version, pipeline_mode, state_source and reducer_generation. The default is AHK_INPUT_PIPELINE=active; mirror keeps legacy hotkey dispatch while emitting equivalence traces, and legacy is the rollback path. Set AHK_INPUT_PIPELINE_TRACE=/path/events.jsonl to record capture→reduce→match/consumer-decision→dispatch/callback-outcome. Custom prefix/suffix combos use the same decision/dispatch path, and broker remap decisions link each authoritative replacement transaction to its physical parent. Callback HotIf rules use protocol-v2 DECISION_REQUEST/REPLY: the broker holds the physical down for at most one shared 60 ms event budget, tries the next arbitration candidate when a dynamic criterion is false, replays on timeout or owner disconnect when no later winner applies, suppresses a true decision with sticky key-up, and downgrades a rule to observe-only after three timeouts. Dynamic leases refresh every 60 s. IME composition/focus transaction normalization remains a later host-integration milestone.
scXXX, logical VK/keysym, and
Unicode text. X11 keymap/state comes from xkbcommon-x11 and refreshes on
MappingNotify. Character Send searches the live layout for the least
modified key (none, Shift, AltGr, Shift+AltGr) before using the serialized
temporary-Unicode mapping fallback. The evdev lane implements custom
A & B prefix state (wildcard modifiers, key-up/tilde and
delayed standalone-prefix behavior); X11/portal/GNOME remain false until
the M2 raw-event design can implement it without fragile passive grabs.
SendText/
Send and hotstring replacements now deliver non-ASCII
characters instead of dropping them. On X11/XWayland every Unicode
character is sent as a key event mapped to its keysym (a spare keycode
is temporarily remapped when the layout has no binding for the
character, exactly like xdotool); Chinese, Japanese, Korean, accented
Latin, Emoji and combining marks all work, and the spare-keycode borrow
is now serialized through an X selection lease so two concurrent AHK
processes can never overwrite each other’s temporary mapping
(check0820). On a pure Wayland session non-ASCII runs now use a custom
xkb keymap uploaded to the virtual keyboard (the wtype model, R3 §6-U3):
the character sits on its own keycode with the exact glyph as the
keysym, so Greek/CJK/Cyrillic/Latin-ext inject layout-independently on
wlroots compositors (sway/Hyprland); the default keymap is restored
afterwards. Compositors without the virtual-keyboard protocol
(GNOME/KWin) keep the
controlled clipboard-paste fallback (set clipboard →
Ctrl+V → restore) on compositors with either the
virtual-keyboard protocol (wlroots/sway) or an accessible
/dev/uinput (GNOME/KWin; input group or root); the
fallback waits for the target to actually pull the offer, restores an
originally-empty clipboard to empty, can be disabled with
AHK_WAYLAND_PASTE=0 and warns before first use (sensitive
text / password managers). On a compositor with no injection path at
all the send raises a clear error naming the character instead of
silently dropping it.
XTEST→libei zero-code bridge (VM-tested, negative on Ubuntu
24.04): Xwayland ≥23.2 can bridge XTEST requests into the desktop
via libei (RemoteDesktop portal) with no client changes, but only when
the Xwayland package is built with libei. Tested on the
Ubuntu 24.04 GNOME 49 VM (Xwayland 24.1.6): the binary links no libei
and contains no EIS symbols; XTEST from this port reaches XWayland
windows (verified: SendText → raw-Xlib X window
received all key events) but does not reach a focused
Wayland-native window (ptyxis read loop got nothing), and no
RemoteDesktop/ConnectToEIS portal traffic is generated. So on this
distro the zero-code path is not available; it would need a
distro whose Xwayland ships libei (e.g. Fedora or a
--enable-libei build).libei-1.0 and liboeffis-1.0, pure-Wayland
Send/SendText/pointer/button/scroll injection
prefers an XDG RemoteDesktop session. liboeffis owns
CreateSession→SelectDevices→Start/consent→ConnectToEIS; the
runtime then binds only seat-granted capabilities, waits for resumed
devices, starts a monotonically sequenced emulation transaction and frames
every event. AHK_LIBEI=required forbids privileged
virtual-keyboard/uinput fallback and returns an explicit error on denial,
disconnect or missing capability; AHK_LIBEI=0 disables this
route. HotkeyBackendGet() exposes build/library/portal
versions, generation, state, runtime keyboard/pointer/button/scroll/TEXT
grants, keymap generation and outcome. TEXT is enabled only when both the
build is libei 1.6+ and the compositor actually grants it; otherwise the
EIS XKB keymap is used, and an unrepresentable character fails explicitly.
Per-device modifier snapshots drive key selection; AHK-held Ctrl/Alt/Super
remain command modifiers around the key and are excluded from printable
level lookup, while temporary Shift/Level3 keys are framed and neutralized.
Outcomes mean submitted-to-libei or
eis-processed; target delivery/consumption remain
unknown because EI provides no application acknowledgement.
Release packages include a same-version feature-off
ahk_core_pack template, so --pack remains a
dependency-free single ELF even when the interactive runtime links libei.
FileInstall resource failures abort before output replacement; footer lengths
use checked arithmetic and successful packs finalize through an atomic rename.
The packed executable uses the feature-off template and does not inherit
interactive libei capability: --pack probes that template with
--pack-info instead of assuming it matches, prints an explicit
notice when a capability is lost, and embeds a manifest naming the release
and capabilities actually inside the executable
(--version/--pack-info/--diag
report it; HotkeyBackendGet().libei_build_enabled answers the
same for scripts). A packed executable forwards its arguments to the
embedded script (A_Args, with -- to force it and
/script PATH as the override), and
A_IsCompiled is 1 for the whole packed process lifetime.
Generic tar/AppImage/RPM payloads privately carry their matched optional
libei/liboeffis SONAMEs; Debian packages use build-version-floored runtime
dependencies.
Keymaps/modifier masks and sequences are per device; the planner waits for
the requested capability even when another device became ready first.
Removal reloads the replacement keymap and increments its client-side
emulation sequence, while replacement during PressDuration fails without
moving the old key-up onto the new device. A portal-owned EIS disconnect
enters reauth-required. The CI oracle talks directly to an
independent EIS receiver and does not claim to validate portal UI/consent;
M6b still requires GNOME/KDE real-host CreateSession/Start/ConnectToEIS,
pause/revoke, libei 1.6 TEXT and persistence recovery matrices.Gui/
GuiControl/Menu work on X11/XWayland and need
a display; pure headless sessions raise a clear error).ComObject("service")
(or ComObjGet) creates a proxy for a D-Bus service; method calls and
property access map to D-Bus method/property calls; ComValue wraps
typed values (integers, doubles, strings, booleans, bytes). Windows
COM interfaces (IUnknown/IDispatch pointers, SafeArrays, events) do
not exist on Linux; ComObjQuery/ComObjConnect/ComObjArray raise an
error. Known Windows ProgIDs such as Excel.Application raise
migration guidance instead of becoming malformed D-Bus proxies.
Remote D-Bus method calls use bounded, non-blocking pending calls
(default 5 s, configurable via AHK_COM_CALL_TIMEOUT_MS)
that pump script timers and events during the wait. On Wayland, a
compositor restart is recovered in the same process: the session is
rebuilt on the new socket with an incremented
wayland_generation (visible via
HotkeyBackendGet() and --diag)..dll or known Windows library name raises
explicit Linux-native FFI migration guidance and is never silently
rewritten to a similarly named .so.SoundGet*/SoundSet* mixer functions are available via
pactl (PipeWire/PulseAudio) with an ALSA amixer
fallback; SoundPlay (paplay/aplay) and SoundBeep
(terminal/X11 bell) work too. TrayTip is delivered as a
standard desktop notification through org.freedesktop.Notifications
(GNOME Shell / KDE / XFCE all host it); with no notification daemon it is
a silent no-op (documented "never a critical error"). TraySetIcon
registers a StatusNotifierItem with the desktop's tray
(org.kde.StatusNotifierItem + com.canonical.dbusmenu):
the default IconName is autohotkey and its
non-empty IconPixmap comes directly from the upstream
AutoHotkey icon_main.ico/ahk16.png asset; an
explicit image path overrides both. A default menu offers Pause / Suspend /
Reload / Exit. No tray watcher is a silent no-op. The menu is the script's
A_TrayMenu (customizable via A_TrayMenu.Add); the
SNI dbusmenu renders its items and clicks invoke the callbacks. The item
registers under the well-known
org.kde.StatusNotifierItem-<pid>-1 name so the watcher
removes it on exit. Self-contained compiled scripts are available via
ahk_core --pack Output Script.ahk, including
FileInstall resources and A_IsCompiled.SendEvent
paces each key by SetKeyDelay (inter-key) and
PressDuration (down→up); explicit delay values are
honored, the default (-1 = "system speed") keeps the fast
XTEST behavior. SendInput is a fast batch (no sleeps;
SetKeyDelay does not affect it, per the docs) and its keys
are marked self-injected so the script’s own hotkeys/hotstrings do
not re-fire ("unload the hook during SendInput"). A grabbed key that
SendInput injects is consumed rather than delivered to the target —
on X11 the passive grab intercepts every press of it (the same
limitation as ~ passthrough on servers that re-activate the
grab). SendPlay is SendEvent with the
SetKeyDelay ,, Play variants (no X11 journal exists, so the
injection depth equals Event; documented platform adaptation).
Send resolves by SendMode for pacing but keeps
the historical trigger semantics: its events still activate the
script’s own grabs. Documented deviation: on Windows, SendMode
"Input" + Send also unloads the hook; on Linux use
SendInput() for that semantic.SendLevel (the self-injection tracker). A self-sent event
only fires a hotkey whose #InputLevel is ≥ the event's
SendLevel (input at level L cannot trigger a lower-level hotkey), and an
InputHook only receives self-sent input whose SendLevel is
≥ its I (MinSendLevel) option. Physical input is
unaffected. Tested: SendLevel 1 + a default (#InputLevel 0)
hotkey is not triggered by Send; SendLevel 2 +
InputHook("I1") receives the input.ahk --parity FuncName prints the level and a one-line note;
A_ParityLevel("FuncName") returns the level (1..4) for
defensive scripts (e.g. skip a P4 feature before calling it). The
classification lives in tests/doccheck/parity.tsv and is
compiled into the binary by tools/gen_parity.py (CI checks
the generated header stays in sync). AHK_STRICT_PARITY=warn|error
turns the classification into a migration aid: the first call of any
P3/P4 function prints a warning (warn) or raises
(error), so a Windows script silently running on a
fake-compatible function surfaces explicitly. It covers both the
upstream g_BIF table (RegRead, ...) and the Linux table;
class-based P4 functions (ComObjArray, ...) already raise their own
clear errors. Each function's own documentation page
(docs/lib/*.htm) carries a color-coded parity badge
(green P2 adapted / amber P3 simulated / red P4 unavailable), injected
by tools/gen_parity_badges.py and kept in sync by CI.XIRawEvent.sourceid) and detects the XTEST devices via their
XTEST Device property. The XI2 raw-event stream then
classifies every key event as XTEST-injected or physical, which makes the
passthrough/self suppression marks definitive: a physical press is never
swallowed by a stale injection mark (a real repeat within the old 1 s
window can no longer be mis-consumed). When XI 2.1 is unavailable the
tap is disabled and the time-window heuristic stays in charge.
ahk --diag reports xi2-sourceid and
xi2-xtest-dev. Verified: XTEST device detected on Xvfb and
the GNOME Xwayland session; the sourceid premise (XTEST events carry the
XTEST device as sourceid) confirmed on both; the full "physical press not
swallowed" end-to-end path needs a real keyboard and is verified only in
principle (the gate blocks solely on a definitive PHYSICAL
classification, and everything is XTEST under Xvfb).See CHECK_REPORT.md for the complete per-module report.
The project is in technology-preview status: the language coverage
is broad and the X11/XWayland backend is usable for real automation, but
native-Wayland global hotkeys, cross-app control automation, and parts of
Unicode input remain young; treat new installs accordingly. Release policy
(check0820): all releases since v2.0.26-linux.15 remain published on
the GitHub Releases page, each with a CKSUMS.txt (SHA-256)
alongside the .tar.gz and .deb, so
ahk --update <VER> can genuinely upgrade or
downgrade (rollback) to any published release. Releases before linux.15
were removed when the new installer line shipped; their source remains on
the v2.0.26-linux.* git tags. The latest release is
v2.0.26-linux.23.
Highlights of this release:
Hotkey() works on Wayland: the X-only guard is
gone — Hotkey() routes through the unified input backend
(gnome-shell/portal/evdev) and reports per-backend errors; verified
end-to-end on the GNOME 49 VM (F12 and Ctrl+1 fire through the
extension, disable → enable re-registers automatically).Activated sender check now matches the broker’s
unique bus name (D-Bus delivers :1.NNN, not the
well-known name) and the extension’s GNOME-49 version-guard
import was corrected; live VM verification (see the "Native Wayland
backend" section).ControlGetText/ControlSetText/
ControlClick and A_ControlSendMode="atspi" drive
accessible text/actions on Wayland. Cache.GetItems bulk discovery,
application-scoped WinTitle matching and a 2s total budget are active.
Selection-backed list APIs and Value-backed scalar Get/Set are real (Qt
QListWidget/QSlider callbacks prove effects and clear/error semantics).
Each operation updates A_LastError with Linux errno (0 success;
match/interface/bus/timeout failures have stable codes). AT-SPI calls use
bounded pending waits which pump GTK/Wayland/hotkey/timer events; diagnostics
publish pending-call/pump-slice counts and nested AT-SPI reports EBUSY.
WinTitle probes/cache requests run concurrently and load only the matching
app, so large unrelated trees cannot spend the target's budget. Action
accepts old GetNactions or the official NActions property; Value writes are
read back and fail EIO if a bridge silently ignores them. Real-host tests
cover GTK 3.24, Qt 6.9, OpenJDK 21/Java ATK 0.42.1 and LibreOffice Calc
25.8.7. Java Value-write and Calc virtual-cell APIs remain explicit gaps.
VS Code/Electron 1.134 exposes its window/Document but not Monaco source
content (window-only support).extensions/vscode-ahk-linux/
packages syntax, run/stop/tasks, diagnostics and a backend capability tree
as a VSIX. A real VS Code extension-host oracle verifies activation,
--diag parsing and script execution. VSC-2 D1 enables the Linux
DBGp runtime (--debug host:port); D2 contributes the
ahk-linux inline DAP debugger for breakpoints, continue/step,
stack/scopes/scalars/evaluate and termination. Independent protocol/DAP
oracles and a VS Code extension-host tracker pass (required in core CI). D3 Array/Map/Object
trees support nested handles and paging (20-item/two-page oracle). Exception
filters stop caught errors at throw, expose Message and continue into catch.
Persistent idle Pause is bounded below 500 ms and exposes an explicit
no-active-frame Global scope. D-Bus compatibility objects expose only local
adapter metadata and typed values without remote calls. Same-PID detach and
reconnect work for idle/running scripts through a signal-safe SIGUSR2
trigger; IDE socket crashes leave the script alive detached.ImeGetState() retains the
framework/XKB summary and ImeStatus() reports engine, preedit,
listener scope and counters. A private IBus eavesdrop listener and Fcitx5
InputContext1 path freeze preedit and feed only commit text to Hotstring/
InputHook as normalized IME_COMMIT events. GNOME IBus/libpinyin real GTK
input proves nihao→你好, Hotstring, canceled
preedit cleanliness and InputHook matching. Fcitx5 has CI protocol coverage;
real desktop/Flatpak visibility remains explicit. See
docs/IME-Integration.md.examples/gui/syntax_studio.ahk
combines ten lessons, ListBox filtering, syntax cards, runnable examples,
editable practice, Check/Run/Reset/Copy navigation, TreeView concepts and
status/activity panels. The VM captured overview, lesson, filter, Check and
Run states; the core CI runs an X11 interaction oracle that verifies lesson
selection, filtering, practice pass, exit code 0 and output creation.ahk-inp-lease) that owns a
duplicate device fd and releases EVIOCGRAB unless the main
loop heartbeats within 1.5 s; the in-process SIGALRM
watchdog remains as a second layer, and a device whose lease cannot be
armed is refused with an explicit message instead of being held
unsupervised. The in-process evdev
fallback filters keyboard-only devices, rejects composite pointer nodes, preflights uinput and releases
all local grabs on replay failure. The broker applies one capability policy
to v1/v2, drops and reopens a device after SYN_DROPPED, keeps
physical state per device and publishes stable device identities. Its
systemd unit uses Type=notify and reports healthy/degraded
readiness. Portal/GNOME registered accelerators expose adapted, not
hook-equivalent, suppression guarantees. Automatic routing rejects required
the negotiated grant is absent; an explicit portal/GNOME backend override is
allowed as an adapted opt-in and does not claim hook-equivalent behavior.
The local evdev lane preserves raw
keycode/value/repeat replay and uses an independent 1.5-second grab
watchdog plus SYN_DROPPED/EVIOCGKEY reconciliation. Replacement and
synthetic held-key ownership is tracked per device/transaction.Display pointer and reconnects. No Xlib request is made from
the fatal callback, and this is a display-lane reset rather than a
promise to restore every GUI/clipboard/input registration.send_with_reply_and_block.
IME listener lifetime and state snapshots are synchronized across dispatch
and script status calls.INCR chunks; ClipboardAll validates MIME names,
checksums and strict UTF-8, while Wayland paste waits dispatch pending
offers and invalidates restore on cancellation.AHK_CLIPBOARD_TIMEOUT_MS (default 2000) bounds reads;
regression with a slow-owner probe.wl-paste) —
matching upstream wlroots issue #295; environments that ship a sync agent
(clipboard managers) provide the bridge, and the port reads/writes
whichever server owns the data without inventing content.Meta.Selection's
owner-changed signal and broadcasts
ClipboardChanged(type) on the session bus; the runtime's
OnClipboardChange watch (which already uses XFixes on
X11/XWayland) arms a session-bus filter on Wayland and fires the callback
when the compositor's clipboard owner changes. VM-verified end to end:
wl-copy triggers the callback (Type 1). The sway/KDE
ext-data-control-v1 listener is the R2 follow-up.tools/linux/permissions/: udev rule, input group,
polkit action, install script — VM-verified).verify_report_numbers.sh.)What works where (verified environments in parentheses):
| Capability | X11 / XWayland (Xvfb suites, Xorg on Debian/Ubuntu) | GNOME 49 Wayland (Ubuntu 25.10) | wlroots Wayland, e.g. sway (headless suites) | KDE/other Wayland |
|---|---|---|---|---|
| Global hotkeys | Full: XGrabKey/XGrabButton,
~ passthrough, * wildcard, left/right
modifiers, key-up, a & b combos, BadAccess conflict
reporting | Exclusive only: 1::/^1::/
F12:: (zero-confirm, suppression, auto re-register after
reload) | Global hotkeys via Portal or the XWayland fallback | Portal (may require an app identity; see above) |
| Send / Mouse simulation | Full XTEST, Unicode keysym transmission | New (check0820): uinput virtual-keyboard
lane (needs /dev/uinput access, e.g. input
group) delivers keys/buttons/pointer on GNOME; non-ASCII via the
clipboard-paste fallback (better: waits for the target to consume,
restores an empty original to empty, AHK_WAYLAND_PASTE=0
disables it) or the virtual-keyboard protocol | Virtual keyboard/pointer protocols; non-ASCII via clipboard-paste fallback | Depends on compositor support for the virtual protocols (usually absent; uinput lane applies) |
| Hotstrings / InputHook | Full capture engine, Unicode trigger words, OnChar/OnKeyDown/OnKeyUp notifications | Not available (the capture engine requires the X11 grab machinery; use an XWayland/Xorg session) | ||
| Window/control automation (Win*, Control*, Gui) | Full (GTK3 Gui included) | Via XWayland fallback for X11 windows; native-Wayland apps are driven through the AT-SPI bus for text read/set and click (round-37) | ||
| Cross-app accessibility control automation (AT-SPI) | Via at-SPI too (any GTK/Qt/Electron app with accessibility on) | Yes (check0820, round-37): ControlGetText/
ControlSetText/ControlClick (read text,
editable-text set, Action.DoAction("click")) over the
org.a11y.atspi bus; GTK3 app verified end-to-end |
Via at-SPI bus (compositor-independent) | Via at-SPI bus (compositor-independent) |
Native input status and remaining environment gaps:
ahk-inputd.socket at
/run/ahk-inputd.sock as root:input 0660; the generic
installer offers --inputd-service. Systemd starts the hardened
root daemon only on the first authorized connection. It logs client
pid/uid/gid using SO_PEERCRED, drops crashed-client rules
fail-open, and exits five seconds after the final client so all grabs are
released while the socket remains ready for demand restart. Trusted users
must be added to group input explicitly and re-login; installers
never grant that keyboard-reading authority silently. The AppImage carries
the daemon but cannot install a host system service. The independent VM
oracle verifies real uinput distribution/suppression, SIGKILL restart,
stable socket inode, idle release and package removal.docs/IME-Integration.md.The Linux port is written and maintained by MonoEven.
This port is an independent implementation, but it stands on the work and support of the AutoHotkey, Linux and open-source communities:
Release artifacts and their evaluation:
.deb (amd64; arm64 builds the same way)
— shipped with every release, plus a CKSUMS.txt SHA-256
manifest. Release-tag packages use GitHub Artifact Attestations
(actions/attest@v4, Sigstore transparency log, SLSA Build L2)
as the primary provenance chain; verify with
gh attestation verify <file> --repo MonoEven/Autohotkey_Linux.
Packaging never generates an ephemeral OpenPGP key: without a configured,
fingerprint-pinned long-term key it emits UNSIGNED.txt.tools/linux/pack-appimage.sh
(appimagetool; icon handling and no-FUSE extraction fixed in linux.15).
Carries the interpreter, launcher and optional ahk-inputd binary;
a portable read-only image cannot install/enable the host systemd service.
GUI toolkits come from the host.tools/linux/pack-rpm.sh
(rpmbuild, private _dbpath, disabled debuginfo so no
eu-strip is needed). Suitable for Fedora/openSUSE
users.tools/linux/PKGBUILD
builds from the pinned current git source with a checked SHA-256 archive checksum; package name
autohotkey-linux.tools/linux/org.autohotkey.AHK.yml
is an experimental X11/Wayland + portal/a11y build and grants only
--device=input. It intentionally does not expose
/dev/uinput or claim native global suppression/injection; a
real host broker and sandbox consent matrix remain required..deb/RPM/PKGBUILD skeletons are the starting point the
distro packages would copy.aarch64); no ARM hardware is available
for CI today, so ARM binaries are untested artifacts (documented).git clone --branch linux-port https://github.com/MonoEven/Autohotkey_Linux.git cd Autohotkey_Linux cmake -S . -B build-core cmake --build build-core -j$(nproc) build-core/source/linux/core/ahk_core your-script.ahk
Prebuilt packages (tar.gz / .deb) and an installer are published with each GitHub Release: releases.
After installation, ahk --check reports the install method and
integrity plus the selected input backend, broker socket presence, and whether
/dev/uinput is writable. These are diagnostics only: the broker
still negotiates SUPPRESS/INJECT grants per client.
This port is based on AutoHotkey v2.0.26. The v1-to-v2 change documentation has been removed because v1 is not supported by this port.
Software License: GNU General Public License