Extract duplicated `pipewire::init()`, `MainLoopRc::new()`, and `ContextRc::new()` setup code from `pw_get_global_objects_thread`, `create_virtual_mic`, and `create_link` into a shared `setup_pipewire_context` helper in `src/utils/pipewire.rs`. Also ran codebase-wide linters to improve code quality.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Fix virtual mic audio linking by managing it in AudioPlayer lifecycle
- Moved `link_player_to_virtual_mic` to `src/utils/pipewire.rs` and updated it to return a termination sender.
- Added `player_link_sender` to `AudioPlayer` to manage the PipeWire link between the daemon and the virtual mic.
- Integrated linking logic into `AudioPlayer::play` and `AudioPlayer::update` to ensure the link is established when audio starts playing.
- Ensured the link is terminated in `AudioPlayer::drop_stream` when the audio sink is closed.
- Removed redundant and potentially failing startup linking loop from the daemon.
- Fixed log spam by ensuring `link_player` is only attempted when necessary and errors are handled gracefully.
- Maintained compatibility with stable Rust by avoiding unstable features.
Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>
* small refactor
* refactor
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Addresses a security vulnerability where the daemon or client could be
forced to allocate up to 10MB of memory per malformed socket message,
potentially leading to Out-Of-Memory (OOM) crashes.
Changes:
- Introduced a central `MAX_MESSAGE_SIZE` constant of 128KB in `src/types/socket.rs`.
- Enforced the 128KB limit on incoming requests in `src/bin/daemon.rs`.
- Enforced the 128KB limit on incoming responses in `src/utils/daemon.rs`.
- Preserved detailed `eprintln!` logging when messages are rejected.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* fix: drop audio stream when idle to allow system suspend
The daemon kept its ALSA playback stream open permanently, which
PipeWire reported as a running Stream/Output/Audio node even with
no tracks playing. This prevented desktop environments from detecting
idle state and entering suspend.
- Make the audio sink on-demand: created when playback starts,
dropped when all tracks finish
- Reduce player loop polling from 100ms to 2s when idle
- Throttle PipeWire device enumeration to every ~5s while playing
- Log only first and last link retry attempt instead of all 60
* feat: add hotkey system for playing individual sounds
Slot-based hotkey mappings stored in ~/.config/pwsp/hotkeys.json.
Daemon serves hotkey IPC commands for CLI/compositor bindings.
GUI supports focused hotkey triggers, a dedicated Hotkeys panel
with search and conflict detection, file badges, and a key chord
capture dialog. CLI gains play-hotkey, get hotkeys, set hotkey,
set hotkey-key, and clear-hotkey subcommands.
* feat: add global hotkey support via evdev
Listen for keyboard events directly from /dev/input using evdev,
enabling hotkeys to work system-wide regardless of window focus
or display server (X11, GNOME, KDE Plasma, Hyprland).
The daemon spawns async listeners for each keyboard device at
startup, tracks modifier state, and triggers playback when a
configured chord matches. Requires the user to be in the 'input'
group; logs a warning and continues without global hotkeys if
devices are inaccessible.
* various changes
* refactor: route hotkey mutations through daemon IPC
GUI no longer writes hotkey config directly to disk. Instead, all
mutations (set slot, set key chord, clear chord, remove slot) are
sent to the daemon via IPC, which persists the changes. The state
thread periodically syncs the hotkey config back from the daemon,
so CLI-made changes are reflected in the GUI.
New IPC commands: set_hotkey_action (arbitrary action per slot),
clear_hotkey_key (remove key chord without removing the slot).
Also removes unreachable capture overlay from draw_hotkeys().
* small refactor
---------
Co-authored-by: arabian <a.tevg@ya.ru>
At boot time, PipeWire takes some time to register the `pwsp-daemon` and `pwsp-virtual-mic` devices. Previously, the daemon's retry loop for `link_player_to_virtual_mic()` was synchronous and limited to 5 attempts (1.5 seconds total). This caused systemd autostarts to fail with a code 1 if the devices were not yet available.
This change replaces the synchronous wait with an asynchronous `tokio::spawn` task. It will retry the link attempt up to 60 times with a 1-second delay without blocking the startup of the rest of the daemon. This prevents it from exiting abruptly during autostart.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Directly attempt to remove the daemon socket file and handle NotFound errors
instead of checking for its existence first. This prevents a potential
race condition where the file could be replaced between the check and
the removal.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* 🧹 Refactor: Replace unsafe unwrap in get_audio_player
Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>
* 🧹 Refactor: Replace unsafe unwrap in get_audio_player
Resolved GitHub CI failure where a syntax error was introduced due to a bad automated merge with main. Rebased cleanly to ensure only the get_audio_player code health changes are included.
Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>
* Delete tests/perf_play.rs
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
The daemon was allocating memory based on an unverified length prefix
sent over the unauthenticated Unix socket, potentially allowing a malicious
client to cause an Out-Of-Memory panic (DoS). A 10 MB size limit has been
introduced.
Note: The previously reported `unwrap()` panic on invalid JSON payloads
was already fixed and replaced with a safe `match` block in a prior commit.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Replaced `.unwrap()` with proper error handling during JSON serialization in `GetStateCommand`, `GetTracksCommand`, and `GetFullStateCommand`.
- Added error handling for malformed client requests in the daemon's main loop.
- Ensured the daemon stays running even if serialization or deserialization fails.
- Handled potential errors from `get_all_devices()`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Replaced `.to_str().unwrap()` with `.to_string_lossy()` when converting
`PathBuf` to `String` to prevent potential crashes if the path contains
invalid Unicode. This change improves the robustness of both the CLI
and GUI components when handling file paths.
- Modified `src/bin/cli.rs` to safely handle `file_path`.
- Modified `src/gui/mod.rs` to safely handle `path` in `play_file`.
Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>