Aartiq
Aartiq™
Download

Linux Integration

Aartiq for Linux

GNOME and KDE Plasma

Linux Integration

Implemented in src/lib/linux-integration.js. Twelve actions, an eleven-channel bridge, and three ways of writing .desktop files. Four actions reach a real system API. Eight send on an IPC channel that no renderer subscribes to, and four of the eleven bridge methods ask for a channel that has no handler at all.

Scheme registered, never delivered
notify-send and kdialog
espeak out, no dictation in

aartiq:// links are discarded on Linux

The module registers the scheme at startup via registerLinuxProtocol(), and main.js wires up the eleven IPC handlers behind process.platform === 'linux'. Both of those work.

What does not exist is the delivery path. handleLinuxURLScheme is the function that would receive an activated link, and it is imported by main.js and never called. There is no second-instance handler and nothing reads process.argv for a URL, which is how XDG delivers a protocol activation.

The actions are reachable from inside the app through electronAPI.linux.executeAction, and from nowhere else. The Windows page documents the identical defect, and the deep-link reference explains why macOS is the only platform where a link does anything.

Actions

All twelve entries from the actionHandlers table. The reach column is derived by a test from whether the handler's channel has a subscriber, so it cannot drift from the source.

4
reach a system API
8
send into nothing
0
reachable from a link
0
bridge channels registered twice
aartiq://open-app?appName=…
Does the work

Runs gtk-launch on GNOME and KDE, falling back to kioclient, and xdg-open everywhere else. Because it is exec'd as a string, the appName parameter is interpolated into a shell command line.

Parameters readappName
aartiq://volume?level (0-100)=…
Does the work

pactl on GNOME, qdbus against org.kde.KMix on KDE, and an explicit error on any other desktop. Note that the desktop is matched by exact string equality against a lowercased XDG_CURRENT_DESKTOP, so Ubuntu's usual "ubuntu:GNOME" does not match "gnome" and silently takes the unsupported branch.

Parameters readlevel (0-100)
aartiq://voice?text=…
Does the work

When speak is not the string "false" it calls speakText, which runs espeak. This is output only; there is no dictation path. startVoiceRecognition returns success: false and points at whisper.cpp.

Parameters readtext, speak
aartiq://notify?title=…
Does the work

notify-send on GNOME, kdialog --passivepopup on KDE, and notify-send with a five-second timeout everywhere else. The title and message are interpolated into the notify-send command string.

Parameters readtitle, message, icon
aartiq://chat?message=…
Sends on a channel with no listener

Sends ai:chat-message. Nothing subscribes to that channel.

Parameters readmessage
aartiq://navigate?url=…
Sends on a channel with no listener

Sends browser:navigate. Nothing subscribes to that channel.

Parameters readurl
aartiq://search?query=…
Sends on a channel with no listener

Sends ai:search. Nothing subscribes to that channel.

Parameters readquery
aartiq://create-pdf?content=…
Sends on a channel with no listener

Sends ai:create-pdf. Nothing subscribes to that channel.

Parameters readcontent, title
aartiq://run-command?command=…
Sends on a channel with no listener

Sends ai:run-command and returns "Command queued". Nothing subscribes to that channel, so nothing runs. There is no confirmation gate on this path, unlike the Windows one.

Parameters readcommand
aartiq://screenshot?mode (default fullscreen)=…
Sends on a channel with no listener

Sends ai:screenshot. Nothing subscribes to that channel. The previous version of this page claimed capture via scrot or import; the module invokes no screenshot program at all.

Parameters readmode (default fullscreen), path
aartiq://schedule?task=…
Sends on a channel with no listener

Sends ai:schedule. Nothing subscribes to that channel.

Parameters readtask, cron
aartiq://ask-ai?prompt=…
Sends on a channel with no listener

Sends ai:ask-ai. Nothing subscribes to that channel.

Parameters readprompt, speak

Bridge API

The preload bridge calls 11 channels and main.js registers 11, so every method has a handler and every name matches. The Windows bridge, likewise, matches on all nine of its names. That part is wired correctly. What follows is not.

ChannelArgumentsHandler
linux:execute-actionaction, paramsRuns any action in the table above
linux:desktop:get(none)Current desktop, from XDG_CURRENT_DESKTOP
linux:notifytitle, body, optionsDesktop notification
linux:voice:listenparamsDictation — always returns success: false
linux:voice:speaktext, paramsSpeech synthesis through espeak
linux:voice:get-voices(none)espeak --voices
linux:generate-urlaction, paramsBuilds an aartiq:// URL
linux:create-shortcutname, action, paramsWrites a .desktop file into userData
linux:install-gnome-shortcutname, action, paramsWrites into ~/.local/share/applications
linux:create-launcher(none)Writes aartiq.desktop into userData
linux:register-protocol(none)Registers aartiq:// for this build

The Linux startup crash is fixed

This page used to report that the app could not start on Linux at all. setupLinuxIPCHandlers() registered five linux: channels that main.js then registered again at module scope. Electron's ipcMain.handle throws on a second registration, the call was not wrapped, and it ran at module top level — so on Linux the main process stopped before the window was created. macOS and Windows were unaffected, because the setup call sits inside a process.platform === 'linux' guard, which is why it survived as long as it did.

Each channel is now registered exactly once. The five main.js copies were kept rather than the module's, and that choice matters: they carry a process.platform !== 'linux' check that answers { error: 'Not Linux' } on the other two platforms. Removing those instead would have left every call to them rejecting with No handler registered instead of returning an error.

A test calls the real setup function against an Electron stub whose ipcMain.handle throws on a duplicate, exactly as the real one does, and requires the registration not to throw. That test could not exist before: the module named a parameter interface, which is a reserved word in strict mode, so Jest could not load the file at all.

Registered twice — now none
none
Registered once, never invoked
linux:get-desktop
linux:shortcut-action
linux:speak
linux:get-voices
linux:start-voice

The orphan list is a separate finding and the fix did not change it: those five channels are still registered by the module and still invoked by nothing in the repository. Fixing the crash removed the duplication, not the dead channels.

Calling an action from inside the app

Subject to the caveat above, this is the path that works. It goes through the preload bridge, so it inherits the same dead-channel problem for the eight actions that only send.

// The one path that works on Linux
await window.electronAPI.linux.executeAction('notify', {
  title: 'Build finished',
  message: 'The app is ready',
});
// -> { success: true }  and a real notification appears

await window.electronAPI.linux.executeAction('chat', { message: 'hello' });
// -> { success: true, message: 'Message sent to AI' }  …and nothing happens

// Synthesis works; dictation does not.
await window.electronAPI.linux.voice.speak('Done');
await window.electronAPI.linux.voice.listen();
// -> { success: false, message: '... whisper.cpp ...' }

Desktop entries

Three bridge methods write .desktop files. Each has a problem that stops the file it writes from doing anything, which is worth knowing before treating this as a working feature.

createLinuxShortcut writes an invalid Exec line

The generated .desktop file sets Exec to the aartiq:// URL itself. The desktop entry specification requires Exec to name an executable, so a launcher created this way will not run. The file is written into the app's userData directory rather than a directory the desktop shell searches, which compounds it.

installGNOMEShortcut assumes an aartiq binary is on PATH

It writes Exec=aartiq "<url>", but no binary of that name is installed by the project. It also accepts desktop === 'ubuntu' as a synonym for GNOME, which is the opposite of the exact-match rule the volume and notify handlers use.

createDesktopLauncher registers the scheme where nothing reads it

It is the only launcher that sets Exec correctly and includes MimeType=x-scheme-handler/aartiq, but it writes into userData instead of ~/.local/share/applications, so the desktop never discovers the association.

Claims removed from this page

  • —System tray support, for both GNOME and KDE. There is no tray code in this module.
  • —KRunner integration. Nothing in the repository references KRunner.
  • —GNOME and Plasma global keybindings. The module registers no global shortcut; keybinding registration lives in main.js and is not part of this integration.
  • —Screenshot capture via scrot or import. The screenshot action sends an IPC message and runs no capture program.
  • —pocketsphinx as an offline speech-to-text dependency, and web-based STT via the AI backend as an implemented path. startVoiceRecognition returns success: false and names whisper.cpp in its message.
  • —Voice input, or dictating messages to AI. There is no dictation path on Linux.
  • —"Choose from 80+ eSpeak voices" as a product feature. espeak is real and the voice list is read from espeak --voices, but the number was asserted rather than measured and depends entirely on the installed voice package.
  • —The screenshot row was missing from the action table on the old page even though screenshot is a real action, and the table omitted the voice action.
  • —Global Shortcuts as a feature of desktop integration, which implies a system-wide hotkey that does not exist.

Quick Reference

Does something

aartiq://open-app
aartiq://volume
aartiq://voice
aartiq://notify

Sends into nothing

aartiq://chat
aartiq://navigate
aartiq://search
aartiq://create-pdf
aartiq://run-command
aartiq://screenshot
aartiq://schedule
aartiq://ask-ai