rust-switcher 1.0.13

Windows keyboard layout switcher and text conversion utility
Documentation
use windows::Win32::Foundation::HWND;

use crate::{
    config,
    platform::win::{
        keyboard::{
            HookDecision,
            capture::{push_chord_capture, store_captured_hotkey},
            main_hwnd,
            mods::{chord_from_vk, update_mods_down_press},
            now_tick_ms,
            sequence::try_match_any_sequence,
        },
        with_state_mut,
    },
};

pub(crate) fn handle_keydown(vk: u32, is_mod: bool) -> windows::core::Result<HookDecision> {
    update_mods_down_press(vk);

    let Some(hwnd) = main_hwnd() else {
        return Ok(HookDecision::Pass);
    };

    let now_ms = now_tick_ms();

    with_state_mut(hwnd, |state| {
        handle_keydown_in_state(hwnd, state, vk, is_mod, now_ms)
    })
    .unwrap_or(Ok(HookDecision::Pass))
}

pub(crate) fn handle_keydown_in_state(
    hwnd: HWND,
    state: &mut crate::app::AppState,
    vk: u32,
    is_mod: bool,
    now_ms: u64,
) -> windows::core::Result<HookDecision> {
    let chord = chord_from_vk(vk);

    if state.hotkey_capture.active {
        return handle_keydown_capture(hwnd, state, vk, chord, is_mod, now_ms);
    }

    handle_keydown_runtime(hwnd, state, chord, is_mod, now_ms)
}

pub(crate) fn handle_keydown_capture(
    hwnd: HWND,
    state: &mut crate::app::AppState,
    vk: u32,
    chord: config::HotkeyChord,
    is_mod: bool,
    now_ms: u64,
) -> windows::core::Result<HookDecision> {
    if matches!(vk, 0x0D | 0x1B) {
        crate::platform::win::stop_hotkey_capture_ui(hwnd, state);
        return Ok(HookDecision::Swallow);
    }

    let Some(slot) = state.hotkey_capture.slot else {
        return Ok(HookDecision::Pass);
    };

    if is_mod {
        crate::platform::win::touch_hotkey_settings_control(hwnd, state);
        state.hotkey_capture.pending_mods = chord.mods;
        state.hotkey_capture.pending_mods_vks = chord.mods_vks;
        state.hotkey_capture.pending_mods_valid = true;
        state.hotkey_capture.saw_non_mod = false;
        return Ok(HookDecision::Swallow);
    }

    state.hotkey_capture.saw_non_mod = true;
    state.hotkey_capture.pending_mods_valid = false;
    crate::platform::win::touch_hotkey_settings_control(hwnd, state);

    let prev = state.hotkey_sequence_values.get(slot);
    let seq = push_chord_capture(
        prev,
        chord,
        now_ms,
        &mut state.hotkey_capture.last_input_tick_ms,
    );

    store_captured_hotkey(state, slot, chord, seq)?;
    Ok(HookDecision::Swallow)
}

pub(crate) fn handle_keydown_runtime(
    hwnd: HWND,
    state: &mut crate::app::AppState,
    chord: config::HotkeyChord,
    is_mod: bool,
    now_ms: u64,
) -> windows::core::Result<HookDecision> {
    if is_mod {
        state.runtime_chord_capture.pending_mods = chord.mods;
        state.runtime_chord_capture.pending_mods_vks = chord.mods_vks;
        state.runtime_chord_capture.pending_mods_valid = true;
        state.runtime_chord_capture.saw_non_mod = false;
        return Ok(HookDecision::Pass);
    }

    state.runtime_chord_capture.saw_non_mod = true;
    state.runtime_chord_capture.pending_mods_valid = false;

    let matched = try_match_any_sequence(hwnd, state, chord, now_ms)?;
    Ok(if matched {
        HookDecision::Swallow
    } else {
        HookDecision::Pass
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{app::HotkeySlot, config};

    fn chord() -> config::HotkeyChord {
        config::HotkeyChord {
            mods: 0,
            mods_vks: 0,
            vk: Some(u32::from(b'A')),
        }
    }

    #[test]
    fn enter_stops_hotkey_capture() {
        let mut state = crate::app::AppState::default();
        state.hotkey_capture.start(HotkeySlot::LastWord);

        let decision =
            handle_keydown_capture(HWND::default(), &mut state, 0x0D, chord(), false, 100)
                .expect("capture should succeed");

        assert_eq!(decision, HookDecision::Swallow);
        assert!(!state.hotkey_capture.active);
        assert_eq!(state.hotkey_capture.slot, None);
    }

    #[test]
    fn escape_stops_hotkey_capture() {
        let mut state = crate::app::AppState::default();
        state.hotkey_capture.start(HotkeySlot::LastSequence);

        let decision =
            handle_keydown_capture(HWND::default(), &mut state, 0x1B, chord(), false, 100)
                .expect("capture should succeed");

        assert_eq!(decision, HookDecision::Swallow);
        assert!(!state.hotkey_capture.active);
        assert_eq!(state.hotkey_capture.slot, None);
    }
}