aethermap-gui 1.5.0

GUI client for aethermap input remapper
Documentation
use crate::gui::{Message, State};
use crate::views::hotkeys::HotkeyBinding;
use crate::views::hotkeys::HotkeyBindingsView;
use aethermap_common::ipc_client::IpcClient;
use aethermap_common::HotkeyBinding as CommonHotkeyBinding;
use aethermap_common::{Request, Response};
use iced::Command;

pub fn show(state: &mut State, device_id: String) -> Command<Message> {
    state.hotkey_view = Some(HotkeyBindingsView {
        device_id: device_id.clone(),
        bindings: Vec::new(),
        editing_binding: None,
        new_modifiers: Vec::new(),
        new_key: String::new(),
        new_profile_name: String::new(),
        new_layer_id: String::new(),
    });
    let device_id_clone = device_id.clone();
    Command::perform(async move { device_id_clone }, |id| {
        Message::LoadHotkeyBindings(id)
    })
}

pub fn close(state: &mut State) -> Command<Message> {
    state.hotkey_view = None;
    Command::none()
}

pub fn load(state: &State, device_id: String) -> Command<Message> {
    let socket_path = state.socket_path.clone();
    Command::perform(
        async move {
            let client = IpcClient::with_socket_path(&socket_path);
            let request = Request::ListHotkeys { device_id };
            match client.send(&request).await {
                Ok(Response::HotkeyList { bindings, .. }) => Ok(bindings
                    .into_iter()
                    .map(|b| HotkeyBinding {
                        modifiers: b.modifiers,
                        key: b.key,
                        profile_name: b.profile_name,
                        device_id: b.device_id,
                        layer_id: b.layer_id,
                    })
                    .collect()),
                Ok(Response::Error(msg)) => Err(msg),
                Err(e) => Err(format!("IPC error: {}", e)),
                _ => Err("Unexpected response".to_string()),
            }
        },
        Message::HotkeyBindingsLoaded,
    )
}

pub fn loaded(state: &mut State, bindings: Vec<HotkeyBinding>) -> Command<Message> {
    if let Some(view) = &mut state.hotkey_view {
        view.bindings = bindings;
    }
    Command::none()
}

pub fn load_error(state: &mut State, error: String) -> Command<Message> {
    state.add_notification(&format!("Failed to load hotkey bindings: {}", error), true);
    Command::none()
}

pub fn edit(state: &mut State, index: usize) -> Command<Message> {
    if let Some(view) = &state.hotkey_view {
        if let Some(binding) = view.bindings.get(index) {
            state.hotkey_view = Some(HotkeyBindingsView {
                device_id: view.device_id.clone(),
                bindings: view.bindings.clone(),
                editing_binding: Some(index),
                new_modifiers: binding.modifiers.clone(),
                new_key: binding.key.clone(),
                new_profile_name: binding.profile_name.clone(),
                new_layer_id: binding
                    .layer_id
                    .map(|id| id.to_string())
                    .unwrap_or_default(),
            });
        }
    }
    Command::none()
}

pub fn toggle_modifier(state: &mut State, modifier: String) -> Command<Message> {
    if let Some(view) = state.hotkey_view.as_mut() {
        if view.new_modifiers.contains(&modifier) {
            view.new_modifiers.retain(|m| m != &modifier);
        } else {
            view.new_modifiers.push(modifier);
        }
    }
    Command::none()
}

pub fn key_changed(state: &mut State, value: String) -> Command<Message> {
    if let Some(view) = state.hotkey_view.as_mut() {
        view.new_key = value;
    }
    Command::none()
}

pub fn profile_name_changed(state: &mut State, value: String) -> Command<Message> {
    if let Some(view) = state.hotkey_view.as_mut() {
        view.new_profile_name = value;
    }
    Command::none()
}

pub fn layer_id_changed(state: &mut State, value: String) -> Command<Message> {
    if let Some(view) = state.hotkey_view.as_mut() {
        view.new_layer_id = value;
    }
    Command::none()
}

pub fn save(state: &mut State) -> Command<Message> {
    if let Some(view) = &state.hotkey_view {
        let device_id = view.device_id.clone();
        let binding = CommonHotkeyBinding {
            modifiers: view.new_modifiers.clone(),
            key: view.new_key.clone(),
            profile_name: view.new_profile_name.clone(),
            device_id: Some(view.device_id.clone()),
            layer_id: if view.new_layer_id.is_empty() {
                None
            } else {
                view.new_layer_id.parse().ok()
            },
        };
        let socket_path = state.socket_path.clone();

        if let Some(local_view) = &state.hotkey_view {
            let gui_binding = HotkeyBinding {
                modifiers: binding.modifiers.clone(),
                key: binding.key.clone(),
                profile_name: binding.profile_name.clone(),
                device_id: binding.device_id.clone(),
                layer_id: binding.layer_id,
            };
            let mut updated_view = local_view.clone();
            if let Some(editing) = local_view.editing_binding {
                if editing < local_view.bindings.len() {
                    updated_view.bindings[editing] = gui_binding;
                }
            } else {
                updated_view.bindings.push(gui_binding);
            }
            updated_view.editing_binding = None;
            updated_view.new_modifiers = Vec::new();
            updated_view.new_key = String::new();
            updated_view.new_profile_name = String::new();
            updated_view.new_layer_id = String::new();
            state.hotkey_view = Some(updated_view);
        }

        return Command::perform(
            async move {
                let client = IpcClient::with_socket_path(&socket_path);
                let request = Request::RegisterHotkey { device_id, binding };
                match client.send(&request).await {
                    Ok(Response::HotkeyRegistered { .. }) => Ok(()),
                    Ok(Response::Error(msg)) => Err(msg),
                    Err(e) => Err(format!("IPC error: {}", e)),
                    _ => Err("Unexpected response".to_string()),
                }
            },
            |result| match result {
                Ok(()) => Message::ShowNotification("Hotkey saved".to_string(), false),
                Err(e) => Message::ShowNotification(format!("Failed to save hotkey: {}", e), true),
            },
        );
    }
    Command::none()
}

pub fn delete(state: &mut State, index: usize) -> Command<Message> {
    if let Some(view) = &state.hotkey_view {
        if index < view.bindings.len() {
            let device_id = view.device_id.clone();
            let binding = view.bindings[index].clone();
            let socket_path = state.socket_path.clone();

            let updated_bindings = view
                .bindings
                .iter()
                .enumerate()
                .filter(|(i, _)| *i != index)
                .map(|(_, b)| b.clone())
                .collect();

            return Command::perform(
                async move {
                    let client = IpcClient::with_socket_path(&socket_path);
                    let request = Request::RemoveHotkey {
                        device_id,
                        key: binding.key.clone(),
                        modifiers: binding.modifiers.clone(),
                    };
                    match client.send(&request).await {
                        Ok(Response::HotkeyRemoved { .. }) => Ok(()),
                        Ok(Response::Error(msg)) => Err(msg),
                        Err(e) => Err(format!("IPC error: {}", e)),
                        _ => Err("Unexpected response".to_string()),
                    }
                },
                move |result| match result {
                    Ok(_) => Message::HotkeyBindingsUpdated(updated_bindings),
                    Err(err_msg) => Message::ShowNotification(
                        format!("Failed to delete hotkey: {}", err_msg),
                        true,
                    ),
                },
            );
        }
    }
    Command::none()
}

pub fn bindings_updated(state: &mut State, bindings: Vec<HotkeyBinding>) -> Command<Message> {
    if let Some(view) = &mut state.hotkey_view {
        view.bindings = bindings;
    }
    state.add_notification("Hotkey deleted", false);
    Command::none()
}