mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-06-19 12:13:32 +00:00
302f153b91
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>
726 lines
22 KiB
Rust
726 lines
22 KiB
Rust
use crate::{
|
|
types::{
|
|
audio_player::{FullState, PlayerState},
|
|
config::HotkeyConfig,
|
|
socket::{Request, Response},
|
|
},
|
|
utils::{
|
|
commands::parse_command,
|
|
daemon::get_audio_player,
|
|
pipewire::{get_all_devices, get_device},
|
|
},
|
|
};
|
|
use async_trait::async_trait;
|
|
use std::{collections::HashMap, path::PathBuf};
|
|
|
|
#[async_trait]
|
|
pub trait Executable {
|
|
async fn execute(&self) -> Response;
|
|
}
|
|
|
|
pub struct PingCommand {}
|
|
|
|
pub struct KillCommand {}
|
|
|
|
pub struct PauseCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct ResumeCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct TogglePauseCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct StopCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct IsPausedCommand {}
|
|
|
|
pub struct GetStateCommand {}
|
|
|
|
pub struct GetVolumeCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct SetVolumeCommand {
|
|
pub volume: Option<f32>,
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct GetPositionCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct SeekCommand {
|
|
pub position: Option<f32>,
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct GetDurationCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct PlayCommand {
|
|
pub file_path: Option<PathBuf>,
|
|
pub concurrent: Option<bool>,
|
|
}
|
|
|
|
pub struct GetTracksCommand {}
|
|
|
|
pub struct GetCurrentInputCommand {}
|
|
|
|
pub struct GetAllInputsCommand {}
|
|
|
|
pub struct SetCurrentInputCommand {
|
|
pub name: Option<String>,
|
|
}
|
|
|
|
pub struct SetLoopCommand {
|
|
pub enabled: Option<bool>,
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct ToggleLoopCommand {
|
|
pub id: Option<u32>,
|
|
}
|
|
|
|
pub struct GetDaemonVersionCommand {}
|
|
|
|
pub struct GetFullStateCommand {}
|
|
|
|
pub struct GetHotkeysCommand {}
|
|
|
|
pub struct SetHotkeyCommand {
|
|
pub slot: Option<String>,
|
|
pub file_path: Option<PathBuf>,
|
|
}
|
|
|
|
pub struct SetHotkeyActionCommand {
|
|
pub slot: Option<String>,
|
|
pub action: Option<Request>,
|
|
}
|
|
|
|
pub struct SetHotkeyKeyCommand {
|
|
pub slot: Option<String>,
|
|
pub key_chord: Option<String>,
|
|
}
|
|
|
|
pub struct SetHotkeyActionAndKeyCommand {
|
|
pub slot: Option<String>,
|
|
pub action: Option<Request>,
|
|
pub key_chord: Option<String>,
|
|
}
|
|
|
|
pub struct PlayHotkeyCommand {
|
|
pub slot: Option<String>,
|
|
}
|
|
|
|
pub struct ClearHotkeyCommand {
|
|
pub slot: Option<String>,
|
|
}
|
|
|
|
pub struct ClearHotkeyKeyCommand {
|
|
pub slot: Option<String>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for PingCommand {
|
|
async fn execute(&self) -> Response {
|
|
Response::new(true, "pong")
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for KillCommand {
|
|
async fn execute(&self) -> Response {
|
|
Response::new(true, "killed")
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for PauseCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
audio_player.pause(self.id);
|
|
Response::new(true, "Audio was paused")
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for ResumeCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
audio_player.resume(self.id);
|
|
Response::new(true, "Audio was resumed")
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for TogglePauseCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
|
|
if audio_player.get_state() == PlayerState::Stopped {
|
|
return Response::new(false, "Audio is not playing");
|
|
}
|
|
|
|
// This logic is a bit tricky with multiple tracks.
|
|
// If ID is provided, toggle that track.
|
|
// If not, toggle global pause state?
|
|
// For now, let's just use pause/resume based on global state if no ID.
|
|
|
|
if let Some(id) = self.id {
|
|
if let Some(track) = audio_player.tracks.get(&id) {
|
|
if track.sink.is_paused() {
|
|
audio_player.resume(Some(id));
|
|
Response::new(true, "Audio was resumed")
|
|
} else {
|
|
audio_player.pause(Some(id));
|
|
Response::new(true, "Audio was paused")
|
|
}
|
|
} else {
|
|
Response::new(false, "Track not found")
|
|
}
|
|
} else {
|
|
if audio_player.is_paused() {
|
|
audio_player.resume(None);
|
|
Response::new(true, "Audio was resumed")
|
|
} else {
|
|
audio_player.pause(None);
|
|
Response::new(true, "Audio was paused")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for StopCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
audio_player.stop(self.id);
|
|
Response::new(true, "Audio was stopped")
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for IsPausedCommand {
|
|
async fn execute(&self) -> Response {
|
|
let audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
let is_paused = audio_player.is_paused().to_string();
|
|
Response::new(true, is_paused)
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetStateCommand {
|
|
async fn execute(&self) -> Response {
|
|
let audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
let state = audio_player.get_state();
|
|
match serde_json::to_string(&state) {
|
|
Ok(json) => Response::new(true, json),
|
|
Err(err) => Response::new(false, format!("Failed to serialize state: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetVolumeCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
let volume = audio_player.get_volume(self.id);
|
|
|
|
if let Some(volume) = volume {
|
|
Response::new(true, volume.to_string())
|
|
} else {
|
|
Response::new(false, "Failed to get volume")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SetVolumeCommand {
|
|
async fn execute(&self) -> Response {
|
|
if let Some(volume) = self.volume {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
audio_player.set_volume(volume, self.id);
|
|
Response::new(true, format!("Audio volume was set to {}", volume))
|
|
} else {
|
|
Response::new(false, "Invalid volume value")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetPositionCommand {
|
|
async fn execute(&self) -> Response {
|
|
let audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
let position = audio_player.get_position(self.id);
|
|
Response::new(true, position.to_string())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SeekCommand {
|
|
async fn execute(&self) -> Response {
|
|
if let Some(position) = self.position {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
match audio_player.seek(position, self.id) {
|
|
Ok(_) => Response::new(true, format!("Audio position was set to {}", position)),
|
|
Err(err) => Response::new(false, err.to_string()),
|
|
}
|
|
} else {
|
|
Response::new(false, "Invalid position value")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetDurationCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
match audio_player.get_duration(self.id) {
|
|
Ok(duration) => Response::new(true, duration.to_string()),
|
|
Err(err) => Response::new(false, err.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for PlayCommand {
|
|
async fn execute(&self) -> Response {
|
|
if let Some(file_path) = &self.file_path {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
match audio_player
|
|
.play(file_path, self.concurrent.unwrap_or(false))
|
|
.await
|
|
{
|
|
Ok(id) => Response::new(true, id.to_string()),
|
|
Err(err) => Response::new(false, err.to_string()),
|
|
}
|
|
} else {
|
|
Response::new(false, "Invalid file path")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetTracksCommand {
|
|
async fn execute(&self) -> Response {
|
|
let audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
let tracks = audio_player.get_tracks();
|
|
match serde_json::to_string(&tracks) {
|
|
Ok(json) => Response::new(true, json),
|
|
Err(err) => Response::new(false, format!("Failed to serialize tracks: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetCurrentInputCommand {
|
|
async fn execute(&self) -> Response {
|
|
let audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
if let Some(input_device_name) = &audio_player.input_device_name {
|
|
if let Ok(input_device) = get_device(input_device_name).await {
|
|
Response::new(
|
|
true,
|
|
format!("{} - {}", input_device.name, input_device.nick),
|
|
)
|
|
} else {
|
|
Response::new(false, "Failed to get current input device")
|
|
}
|
|
} else {
|
|
Response::new(false, "No input device selected")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetAllInputsCommand {
|
|
async fn execute(&self) -> Response {
|
|
let (input_devices, _output_devices) = match get_all_devices().await {
|
|
Ok(devices) => devices,
|
|
Err(err) => return Response::new(false, format!("Failed to get devices: {}", err)),
|
|
};
|
|
let mut input_devices_strings = vec![];
|
|
for device in input_devices {
|
|
if device.name == "pwsp-virtual-mic" {
|
|
continue;
|
|
}
|
|
|
|
let string = format!("{} - {}", device.name, device.nick);
|
|
input_devices_strings.push(string);
|
|
}
|
|
let response_message = input_devices_strings.join("; ");
|
|
|
|
Response::new(true, response_message)
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SetCurrentInputCommand {
|
|
async fn execute(&self) -> Response {
|
|
if let Some(name) = &self.name {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
match audio_player.set_current_input_device(name).await {
|
|
Ok(_) => Response::new(true, "Input device was set"),
|
|
Err(err) => Response::new(false, err.to_string()),
|
|
}
|
|
} else {
|
|
Response::new(false, "Invalid index value")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SetLoopCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
|
|
match self.enabled {
|
|
Some(enabled) => {
|
|
audio_player.set_loop(enabled, self.id);
|
|
Response::new(true, format!("Loop was set to {}", enabled))
|
|
}
|
|
None => Response::new(false, "Invalid enabled value"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for ToggleLoopCommand {
|
|
async fn execute(&self) -> Response {
|
|
let mut audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
if let Some(id) = self.id {
|
|
if let Some(track) = audio_player.tracks.get_mut(&id) {
|
|
track.looped = !track.looped;
|
|
Response::new(true, format!("Loop was set to {}", track.looped))
|
|
} else {
|
|
Response::new(false, "Track not found")
|
|
}
|
|
} else {
|
|
// Toggle all?
|
|
for track in audio_player.tracks.values_mut() {
|
|
track.looped = !track.looped;
|
|
}
|
|
Response::new(true, "Loop toggled for all tracks")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetDaemonVersionCommand {
|
|
async fn execute(&self) -> Response {
|
|
Response::new(true, env!("CARGO_PKG_VERSION"))
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetFullStateCommand {
|
|
async fn execute(&self) -> Response {
|
|
let (input_devices, _output_devices) = match get_all_devices().await {
|
|
Ok(devices) => devices,
|
|
Err(err) => return Response::new(false, format!("Failed to get devices: {}", err)),
|
|
};
|
|
let mut all_inputs = HashMap::new();
|
|
let mut current_input_nick = String::new();
|
|
|
|
let audio_player = match get_audio_player().await {
|
|
Ok(player) => player.lock().await,
|
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
|
};
|
|
if let Some(current_input_name) = &audio_player.input_device_name {
|
|
for device in input_devices {
|
|
if device.name == "pwsp-virtual-mic" {
|
|
continue;
|
|
}
|
|
if device.name == *current_input_name {
|
|
current_input_nick = format!("{} - {}", device.name, device.nick);
|
|
}
|
|
|
|
all_inputs.insert(device.name, device.nick);
|
|
}
|
|
} else {
|
|
for device in input_devices {
|
|
if device.name == "pwsp-virtual-mic" {
|
|
continue;
|
|
}
|
|
|
|
all_inputs.insert(device.name, device.nick);
|
|
}
|
|
}
|
|
|
|
let full_state = FullState {
|
|
state: audio_player.get_state(),
|
|
tracks: audio_player.get_tracks(),
|
|
volume: audio_player.volume,
|
|
current_input: current_input_nick,
|
|
all_inputs,
|
|
};
|
|
|
|
match serde_json::to_string(&full_state) {
|
|
Ok(json) => Response::new(true, json),
|
|
Err(err) => Response::new(false, format!("Failed to serialize full state: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for GetHotkeysCommand {
|
|
async fn execute(&self) -> Response {
|
|
match HotkeyConfig::load() {
|
|
Ok(config) => match serde_json::to_string(&config) {
|
|
Ok(json) => Response::new(true, json),
|
|
Err(err) => Response::new(false, format!("Failed to serialize hotkeys: {}", err)),
|
|
},
|
|
Err(err) => Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SetHotkeyCommand {
|
|
async fn execute(&self) -> Response {
|
|
let Some(slot) = &self.slot else {
|
|
return Response::new(false, "Missing slot name");
|
|
};
|
|
let Some(file_path) = &self.file_path else {
|
|
return Response::new(false, "Missing file path");
|
|
};
|
|
|
|
let mut config = match HotkeyConfig::load() {
|
|
Ok(c) => c,
|
|
Err(err) => return Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
};
|
|
|
|
config.set_slot(
|
|
slot.clone(),
|
|
Request::play(&file_path.to_string_lossy(), false),
|
|
);
|
|
|
|
match config.save() {
|
|
Ok(_) => Response::new(true, format!("Hotkey slot '{}' set", slot)),
|
|
Err(err) => Response::new(false, format!("Failed to save hotkeys: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SetHotkeyActionCommand {
|
|
async fn execute(&self) -> Response {
|
|
let Some(slot) = &self.slot else {
|
|
return Response::new(false, "Missing slot name");
|
|
};
|
|
let Some(action) = &self.action else {
|
|
return Response::new(false, "Missing or invalid action");
|
|
};
|
|
|
|
let mut config = match HotkeyConfig::load() {
|
|
Ok(c) => c,
|
|
Err(err) => return Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
};
|
|
|
|
config.set_slot(slot.clone(), action.clone());
|
|
|
|
match config.save() {
|
|
Ok(_) => Response::new(true, format!("Hotkey slot '{}' set", slot)),
|
|
Err(err) => Response::new(false, format!("Failed to save hotkeys: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SetHotkeyKeyCommand {
|
|
async fn execute(&self) -> Response {
|
|
let Some(slot) = &self.slot else {
|
|
return Response::new(false, "Missing slot name");
|
|
};
|
|
let Some(key_chord) = &self.key_chord else {
|
|
return Response::new(false, "Missing key chord");
|
|
};
|
|
|
|
let mut config = match HotkeyConfig::load() {
|
|
Ok(c) => c,
|
|
Err(err) => return Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
};
|
|
|
|
if !config.set_key_chord(slot, Some(key_chord.clone())) {
|
|
return Response::new(false, format!("Slot '{}' not found", slot));
|
|
}
|
|
|
|
match config.save() {
|
|
Ok(_) => Response::new(
|
|
true,
|
|
format!("Key chord for slot '{}' set to '{}'", slot, key_chord),
|
|
),
|
|
Err(err) => Response::new(false, format!("Failed to save hotkeys: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for SetHotkeyActionAndKeyCommand {
|
|
async fn execute(&self) -> Response {
|
|
let Some(slot) = &self.slot else {
|
|
return Response::new(false, "Missing slot name");
|
|
};
|
|
let Some(action) = &self.action else {
|
|
return Response::new(false, "Missing or invalid action");
|
|
};
|
|
let Some(key_chord) = &self.key_chord else {
|
|
return Response::new(false, "Missing key chord");
|
|
};
|
|
|
|
let mut config = match HotkeyConfig::load() {
|
|
Ok(c) => c,
|
|
Err(err) => return Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
};
|
|
|
|
// Set the action and then the key chord
|
|
config.set_slot(slot.clone(), action.clone());
|
|
if !config.set_key_chord(slot, Some(key_chord.clone())) {
|
|
return Response::new(
|
|
false,
|
|
format!("Slot '{}' not found after setting action", slot),
|
|
);
|
|
}
|
|
|
|
match config.save() {
|
|
Ok(_) => Response::new(
|
|
true,
|
|
format!(
|
|
"Hotkey slot '{}' set with action and key chord '{}'",
|
|
slot, key_chord
|
|
),
|
|
),
|
|
Err(err) => Response::new(false, format!("Failed to save hotkeys: {}", err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for PlayHotkeyCommand {
|
|
async fn execute(&self) -> Response {
|
|
let Some(slot) = &self.slot else {
|
|
return Response::new(false, "Missing slot name");
|
|
};
|
|
|
|
let config = match HotkeyConfig::load() {
|
|
Ok(c) => c,
|
|
Err(err) => return Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
};
|
|
|
|
let Some(hotkey_slot) = config.find_slot(slot) else {
|
|
return Response::new(false, format!("Slot '{}' not found", slot));
|
|
};
|
|
|
|
let action = hotkey_slot.action.clone();
|
|
|
|
if let Some(cmd) = parse_command(&action) {
|
|
cmd.execute().await
|
|
} else {
|
|
Response::new(false, "Unknown command in hotkey slot")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for ClearHotkeyCommand {
|
|
async fn execute(&self) -> Response {
|
|
let Some(slot) = &self.slot else {
|
|
return Response::new(false, "Missing slot name");
|
|
};
|
|
|
|
let mut config = match HotkeyConfig::load() {
|
|
Ok(c) => c,
|
|
Err(err) => return Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
};
|
|
|
|
if config.remove_slot(slot) {
|
|
match config.save() {
|
|
Ok(_) => Response::new(true, format!("Hotkey slot '{}' cleared", slot)),
|
|
Err(err) => Response::new(false, format!("Failed to save hotkeys: {}", err)),
|
|
}
|
|
} else {
|
|
Response::new(false, format!("Slot '{}' not found", slot))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Executable for ClearHotkeyKeyCommand {
|
|
async fn execute(&self) -> Response {
|
|
let Some(slot) = &self.slot else {
|
|
return Response::new(false, "Missing slot name");
|
|
};
|
|
|
|
let mut config = match HotkeyConfig::load() {
|
|
Ok(c) => c,
|
|
Err(err) => return Response::new(false, format!("Failed to load hotkeys: {}", err)),
|
|
};
|
|
|
|
if !config.set_key_chord(slot, None) {
|
|
return Response::new(false, format!("Slot '{}' not found", slot));
|
|
}
|
|
|
|
match config.save() {
|
|
Ok(_) => Response::new(true, format!("Key chord for slot '{}' cleared", slot)),
|
|
Err(err) => Response::new(false, format!("Failed to save hotkeys: {}", err)),
|
|
}
|
|
}
|
|
}
|