From 0992f3b8635e3b67e1a984b1424aa71206735ef1 Mon Sep 17 00:00:00 2001 From: Jelle Spreeuwenberg Date: Wed, 9 Sep 2026 20:39:08 +0200 Subject: [PATCH] feat: manage Handy preferences and startup declaratively --- docs/constellation-work.md | 57 ++++++++++--------------- docs/constellation.md | 4 +- modules/capabilities/handy.nix | 46 ++++++++++++++++++++ modules/capabilities/niri/_bindings.nix | 4 +- modules/capabilities/niri/default.nix | 8 ---- modules/profiles/workstation-base.nix | 1 + 6 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 modules/capabilities/handy.nix diff --git a/docs/constellation-work.md b/docs/constellation-work.md index b4cf018..9a59a44 100644 --- a/docs/constellation-work.md +++ b/docs/constellation-work.md @@ -119,40 +119,29 @@ reports are not a fresh comparison with this baseline. ## Open choices and next discussion Dictation: plain local transcription is selected; rambling cleanup is deferred. -The shared Niri module installs Handy and starts it hidden with the desktop. -Super+D toggles recording; Super+Shift+D cancels. Commands were verified against -the pinned Handy 0.9.1 source. No microphone recording or model download was run. -After user activation, launch Handy from Walker, download/select Parakeet V3, -keep automatic language detection and disable push-to-talk for toggle operation. -In Advanced, choose paste method None and clipboard handling Copy to Clipboard. -Keep post-processing disabled. Settings/models remain writable app-owned state. -Start Handy manually for the current session; desktop autostart applies on login. -Try an English and a Dutch recording, paste into Codex with Ctrl+Shift+V, then -submit manually. User feedback needed: recognition accuracy, delay and shortcut -delivery. Automatic paste can follow if wanted after checking Niri compatibility. -Verification: formatting, flake evaluation, Zenith and Polaris no-link system -builds, and whitespace checks passed. No system activation or interactive -dictation was performed; recognition quality and latency remain unmeasured. -First-use diagnosis: Handy was set to Nemotron, Direct Input, Don't Modify -Clipboard and push-to-talk. One recording transcribed but output used X11 input -simulation; other recordings returned zero usable samples. Default digital mic -is unmuted. Set None + Copy to Clipboard and retry a full sentence; microphone -selection/voice detection needs further checking if History stays empty. -Niri Super+D invokes a forced toggle, independent of Handy's push-to-talk setting; -Handy's Ctrl+Space global binding is not the Niri binding. Keep the user's minimal -overlay. Noctalia's native microphone filter suppresses all microphone activity -popups/indicators, while camera and screen-sharing indications remain enabled. -The Noctalia change passed formatting, flake evaluation and both workstation -builds; user activation and the clipboard retry remain pending. -Automatic paste: Niri now includes wtype, which Handy natively uses to deliver -Wayland paste shortcuts. Set Paste Method to Ctrl+Shift+V for Kitty, keep automatic -submission off, and restart Handy after activation. Interactive delivery remains -unverified. Handy 0.9.1 supports Overlay Style Live with streaming models; the -selected Nemotron Streaming 3.5 is a suitable candidate. Minimal hides live text. -This supersedes the initial Parakeet V3/clipboard-only recommendation when live -feedback and automatic paste are wanted; no custom input script is needed. -The wtype addition passed formatting, flake evaluation, both workstation builds -and whitespace checking. Activation and the interactive paste trial are pending. +Handy preferences are Nix-owned in `modules/capabilities/handy.nix`: Nemotron +Streaming 3.5, automatic language detection, default microphone, Live overlay at +the bottom, Ctrl+Shift+V paste through wtype, preserved clipboard, manual submission +and no cloud processing. Other preferences use pinned app defaults. A user service +copies the baseline into Handy's writable store on each start, allowing migrations +while resetting GUI edits. Models/recordings stay outside Nix; the selected model +is already downloaded on Zenith. Other hosts still need model provisioning. +Super+Shift+D toggles; Super+Ctrl+D cancels. Super+D push-to-talk awaits the user's +decision about a keyboard listener: Niri 26.04 has no key-release bindings. +Noctalia suppresses microphone activity popups/indicators; camera and screen-sharing +indications remain. Automatic paste and live capture need interactive checks. + +Hang investigation: stopping live Vulkan transcription never completed; cancel +returned the UI to idle but subsequent requests reported pipeline busy. Handy was +no longer running when inspected; no crash cause is established. The same saved +1.56-second recording completed in CPU batch mode (135 ms inference, 729 ms load) +and Vulkan batch mode (156 ms inference, 420 ms load). This checks model inference, +not live capture/stop/cancel. No new audio was recorded. Switching to CPU is not a +confirmed fix. After activation, retry live recording and stopping, and inspect +the service logs if it hangs. +Verification: formatting, flake evaluation, both workstation builds and whitespace +checks passed. Handy loaded the generated settings in an isolated data directory +without settings parse errors. System activation remains user-owned and pending. The focus is the broader environment, not further API/database polish. Candidate areas remain places/ownership, navigation/interaction, human/agent collaboration, diff --git a/docs/constellation.md b/docs/constellation.md index 8b5a00c..0bcb619 100644 --- a/docs/constellation.md +++ b/docs/constellation.md @@ -29,8 +29,8 @@ use a flake to declare its tools without Nix owning the surrounding workspace. interaction with useful connections to the rest of the environment. - Use Handy for local plain dictation, mainly English with occasional Dutch. Use native Wayland automatic paste and optional live text with a streaming model. - Niri owns recording shortcuts; - model downloads and microphone preferences remain application-owned. Cleanup + Nix owns Handy preferences and desktop integration; the user service restores + settings on each start. Model downloads and recordings remain application data. Cleanup through Codex, a cloud API or a local language model is deferred. - Outlook is acceptable as an application, but its interaction and integration do not fit as naturally. Accessible mail for tools and agents matters separately diff --git a/modules/capabilities/handy.nix b/modules/capabilities/handy.nix new file mode 100644 index 0000000..1b3614a --- /dev/null +++ b/modules/capabilities/handy.nix @@ -0,0 +1,46 @@ +{ + flake.modules.homeManager.handy = + { config, pkgs, ... }: + let + settings = (pkgs.formats.json { }).generate "handy-settings.json" { + settings = { + settings_schema_version = 1; + selected_model = "handy-computer/nemotron-3.5-asr-streaming-0.6b-gguf/nemotron-3.5-asr-streaming-0.6b-Q8_0.gguf"; + selected_language = "auto"; + translate_to_english = false; + selected_microphone = null; + always_on_microphone = false; + push_to_talk = true; + paste_method = "ctrl_shift_v"; + typing_tool = "wtype"; + clipboard_handling = "dont_modify"; + auto_submit = false; + overlay_style = "live"; + overlay_position = "bottom"; + post_process_enabled = false; + start_hidden = true; + autostart_enabled = false; + update_checks_enabled = false; + show_whats_new_on_update = false; + onboarding_completed = true; + }; + }; + in + { + home.packages = [ pkgs.handy ]; + + systemd.user.services.handy = { + Unit = { + Description = "Handy dictation"; + PartOf = [ "graphical-session.target" ]; + After = [ "graphical-session.target" ]; + }; + Install.WantedBy = [ "graphical-session.target" ]; + Service = { + # Handy writes migrations and runtime preferences: restore the Nix baseline on start. + ExecStartPre = "${pkgs.coreutils}/bin/install -Dm600 ${settings} ${config.xdg.dataHome}/com.pais.handy/settings_store.json"; + ExecStart = "${pkgs.handy}/bin/handy --start-hidden"; + }; + }; + }; +} diff --git a/modules/capabilities/niri/_bindings.nix b/modules/capabilities/niri/_bindings.nix index 97b1205..ef8f539 100644 --- a/modules/capabilities/niri/_bindings.nix +++ b/modules/capabilities/niri/_bindings.nix @@ -36,7 +36,7 @@ in }; # Dictation - "Mod+D" = { + "Mod+Shift+D" = { spawn = [ handy "--toggle-transcription" @@ -44,7 +44,7 @@ in _props.repeat = false; _props.hotkey-overlay-title = "Toggle Dictation"; }; - "Mod+Shift+D" = { + "Mod+Ctrl+D" = { spawn = [ handy "--cancel" diff --git a/modules/capabilities/niri/default.nix b/modules/capabilities/niri/default.nix index 39a497d..dc18274 100644 --- a/modules/capabilities/niri/default.nix +++ b/modules/capabilities/niri/default.nix @@ -41,19 +41,11 @@ in playerctl brightnessctl wireplumber - handy wtype ] ++ [ swapMonitorWorkspaces ]; }; - desktop.autostart = [ - [ - (lib.getExe pkgs.handy) - "--start-hidden" - ] - ]; - dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark"; wayland.windowManager.niri = { diff --git a/modules/profiles/workstation-base.nix b/modules/profiles/workstation-base.nix index dddbe3d..9bfbb86 100644 --- a/modules/profiles/workstation-base.nix +++ b/modules/profiles/workstation-base.nix @@ -43,6 +43,7 @@ in imports = [ homeModules.passwords homeModules.clipboard + homeModules.handy homeModules.desktop-commands homeModules.dev-tools homeModules.xdg