Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@ Run `voisu doctor` if the Trigger Key does not respond — it reports a portal
without a usable GlobalShortcuts interface, and adds `--verbose` for the full
reasoning behind each check.

### Ubuntu GNOME troubleshooting

- **Voisu is not running after login.** The daemon waits up to 20 seconds for
GNOME to export `WAYLAND_DISPLAY`/`DISPLAY`. If `voisu service status` still
shows inactive or failed, run `voisu service restart` once the desktop is up,
and check `journalctl --user -u voisu.service`.
- **`Paste action` and `Paste backend` show SKIP.** Those checks are
Hyprland-only; SKIP is expected on GNOME.
- **`Focus guard none` shows WARN.** Expected on GNOME, where Voisu cannot
verify which window has focus. In `type` mode (`voisu delivery type`) Delivery
pastes into whichever window has focus. In `guarded` mode
(`voisu delivery guarded`) it cannot verify focus, so it leaves the Transcript
on the clipboard instead of pasting.
- **The Overlay is the GNOME Shell extension `overlay@voisu.app`.**
`voisu-overlay.service` (the GTK observer) being inactive is expected there.
- **Direct Delivery puts the Transcript on the clipboard and asks GNOME to
paste it.** It goes through the RemoteDesktop portal and libei. Unless the
compositor's input seat offers text input, Voisu submits a Ctrl+V keystroke, so
a window that pastes with a different shortcut (a terminal using
Ctrl+Shift+V) receives nothing; paste manually, the Transcript is on the
clipboard.
- **Reset Voisu's RemoteDesktop grant.** This clears only the grant Voisu saved
itself. GNOME may ask again when the daemon restarts or at the next Delivery,
and a permission GNOME remembers on its own is not cleared by this.

```sh
rm ~/.local/state/voisu/remote-desktop.restore-token
systemctl --user restart voisu.service
```

## Command reference

`voisu` controls the daemon (`voisu-daemon`). All history and diagnostics stay
Expand Down
11 changes: 11 additions & 0 deletions crates/voisu-app/src/bin/voisu-daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ fn main() {
}
_ => {}
}
// Under systemd the unit carries no display start condition (a skip is never
// retried), so wait for the session display here, before any
// display-dependent setup: exit 75 to be respawned with the manager's
// environment once it arrives, or 78 (not restarted) if it never does.
let systemd_owned = matches!(
std::env::args().skip(1).collect::<Vec<_>>().as_slice(),
[argument] if argument == "--systemd"
);
if let Some(code) = voisu_app::system::startup_display_gate(systemd_owned) {
std::process::exit(code);
}
voisu_app::system::install_crypto_provider();
let mut builder = tokio::runtime::Builder::new_multi_thread();
builder.enable_all();
Expand Down
10 changes: 7 additions & 3 deletions crates/voisu-app/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1145,17 +1145,21 @@ fn service_unit(executable: &Path) -> Result<String, String> {
concat!(
"[Unit]\n",
"Description=Voisu dictation daemon\n",
"After=wayland-session-waitenv.service dbus.socket pipewire.service\n",
// Ordered after the target because it helps, but GNOME imports the
// display variables on its own schedule, and a ConditionEnvironment
// skip is never retried. The daemon waits for the display itself
// under --systemd: exit 75 (arrived late) is respawned by
// Restart=on-failure, exit 78 (never arrived) is not restarted.
"After=graphical-session.target wayland-session-waitenv.service dbus.socket pipewire.service\n",
"Wants=dbus.socket pipewire.service\n",
"PartOf=graphical-session.target\n",
"ConditionEnvironment=|WAYLAND_DISPLAY\n",
"ConditionEnvironment=|DISPLAY\n",
"StartLimitIntervalSec=30s\n",
"StartLimitBurst=3\n\n",
"[Service]\n",
"Type=simple\n",
"ExecStart={} --systemd\n",
"Restart=on-failure\n",
"RestartPreventExitStatus=78\n",
"RestartSec=2s\n",
// Graceful shutdown stops an active Recording, processes it to
// completion, joins the actor, and drains retained provider cleanup;
Expand Down
Loading
Loading