Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reverse switching with shift key #49

Merged
merged 1 commit into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 46 additions & 15 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::utils::{
};

use anyhow::{anyhow, Result};
use indexmap::IndexSet;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use windows::core::PCWSTR;
Expand Down Expand Up @@ -208,7 +209,7 @@ impl App {
if modifier == app.config.switch_apps_hotkey.get_modifier() {
if let Some(state) = app.switch_apps_state.take() {
if let Some((_, id)) = state.apps.get(state.index) {
set_foregound_window(*id)?;
set_foregound_window(*id);
}
for (hicon, _) in state.apps {
unsafe { DestroyIcon(hicon) };
Expand All @@ -220,11 +221,12 @@ impl App {
WM_USER_HOOTKEY => {
debug!("message WM_USER_HOOTKEY {}", wparam.0);
let app = get_app(hwnd)?;
let reverse = lparam.0 == 1;
let hotkey_id = wparam.0 as u32;
if hotkey_id == app.config.switch_windows_hotkey.id {
app.switch_windows()?;
app.switch_windows(reverse)?;
} else if hotkey_id == app.config.switch_apps_hotkey.id {
app.switch_apps()?;
app.switch_apps(reverse)?;
unsafe {
RedrawWindow(hwnd, None, HRGN::default(), RDW_ERASE | RDW_INVALIDATE)
};
Expand Down Expand Up @@ -263,8 +265,8 @@ impl App {
Ok(unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) })
}

pub fn switch_windows(&mut self) -> Result<bool> {
debug!("switch windows enter {:?}", self.switch_apps_state);
pub fn switch_windows(&mut self, reverse: bool) -> Result<bool> {
debug!("switch windows enter {:?}", self.switch_windows_state);
let windows = list_windows(false)?;
let foreground_window = get_foreground_window();
let foreground_pid = get_window_pid(foreground_window);
Expand All @@ -283,8 +285,9 @@ impl App {
let current_id = windows[0];
let mut index = 1;
let mut state_id = current_id;
let mut state_windows = vec![];
if windows_len > 2 {
if let Some((cache_module_path, cache_id, cache_index)) =
if let Some((cache_module_path, cache_id, cache_index, cache_windows)) =
self.switch_windows_state.cache.as_ref()
{
if cache_module_path == &module_path {
Expand All @@ -297,35 +300,63 @@ impl App {
}
}
} else {
index = (cache_index + 1).min(windows_len - 1);
state_id = *cache_id;
let mut windows_set: IndexSet<isize> =
windows.iter().map(|v| v.0).collect();
for id in cache_windows {
if windows_set.contains(id) {
state_windows.push(*id);
windows_set.remove(id);
}
}
state_windows.extend(windows_set);
index = if reverse {
if *cache_index == 0 {
windows_len - 1
} else {
cache_index - 1
}
} else if *cache_index >= windows_len - 1 {
0
} else {
cache_index + 1
};
}
}
}
}
if state_windows.is_empty() {
state_windows = windows.iter().map(|v| v.0).collect();
}
let hwnd = HWND(state_windows[index]);
self.switch_windows_state = SwitchWindowsState {
cache: Some((module_path, state_id, index)),
cache: Some((module_path, state_id, index, state_windows)),
modifier_released: false,
};
let hwnd = windows[index];
debug!(
"switch windows done {:?} {:?}",
hwnd, self.switch_windows_state
);
set_foregound_window(hwnd)?;
set_foregound_window(hwnd);

Ok(true)
}
}
}

fn switch_apps(&mut self) -> Result<()> {
fn switch_apps(&mut self, reverse: bool) -> Result<()> {
debug!("switch apps enter {:?}", self.switch_apps_state);
if let Some(state) = self.switch_apps_state.as_mut() {
if state.index + 1 < state.apps.len() {
state.index += 1;
} else {
if reverse {
if state.index == 0 {
state.index = state.apps.len() - 1;
} else {
state.index -= 1;
}
} else if state.index == state.apps.len() - 1 {
state.index = 0;
} else {
state.index += 1;
};
return Ok(());
}
Expand Down Expand Up @@ -466,7 +497,7 @@ fn get_app(hwnd: HWND) -> Result<&'static mut App> {

#[derive(Debug)]
struct SwitchWindowsState {
cache: Option<(String, HWND, usize)>,
cache: Option<(String, HWND, usize, Vec<isize>)>,
modifier_released: bool,
}

Expand Down
17 changes: 10 additions & 7 deletions src/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ use crate::{
};

use anyhow::{anyhow, Result};
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY;
use windows::Win32::UI::WindowsAndMessaging::{
CallNextHookEx, SendMessageW, SetWindowsHookExW, UnhookWindowsHookEx, HHOOK, KBDLLHOOKSTRUCT,
WH_KEYBOARD_LL,
};
use windows::Win32::{
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
UI::Input::KeyboardAndMouse::{VK_LSHIFT, VK_RSHIFT},
};

static mut KEYBOARD_STATE: Vec<HotKeyState> = vec![];
static mut WINDOW: HWND = HWND(0);
static mut IS_SHIFT_PRESSED: bool = false;

#[derive(Debug)]
pub struct KeyboardListener {
Expand All @@ -37,7 +41,6 @@ impl KeyboardListener {
.map(|hotkey| HotKeyState {
hotkey: (*hotkey).clone(),
is_modifier_pressed: false,
count_code_key_pressed: 0,
})
.collect()
}
Expand All @@ -59,7 +62,6 @@ impl Drop for KeyboardListener {
struct HotKeyState {
hotkey: Hotkey,
is_modifier_pressed: bool,
count_code_key_pressed: u32,
}

unsafe extern "system" fn keyboard_proc(code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
Expand All @@ -68,6 +70,9 @@ unsafe extern "system" fn keyboard_proc(code: i32, w_param: WPARAM, l_param: LPA
let vk_code = VIRTUAL_KEY(kbd_data.vkCode as _);
let mut is_modifier = false;
let is_key_pressed = || kbd_data.flags.0 & 128 == 0;
if [VK_LSHIFT, VK_RSHIFT].contains(&vk_code) {
IS_SHIFT_PRESSED = is_key_pressed();
}
for state in KEYBOARD_STATE.iter_mut() {
if state.hotkey.modifier.contains(&vk_code) {
is_modifier = true;
Expand All @@ -84,18 +89,16 @@ unsafe extern "system" fn keyboard_proc(code: i32, w_param: WPARAM, l_param: LPA
)
};
}
state.count_code_key_pressed = 0;
}
}
if !is_modifier {
for state in KEYBOARD_STATE.iter_mut() {
if vk_code.0 == state.hotkey.code && is_key_pressed() && state.is_modifier_pressed {
let count = state.count_code_key_pressed;
state.count_code_key_pressed += 1;
let id = state.hotkey.id;
if id != SWITCH_WINDOWS_HOTKEY_ID || !IS_FOREGROUND_IN_BLACKLIST {
let reverse = if IS_SHIFT_PRESSED { 1 } else { 0 };
unsafe {
SendMessageW(WINDOW, WM_USER_HOOTKEY, WPARAM(id as _), LPARAM(count as _))
SendMessageW(WINDOW, WM_USER_HOOTKEY, WPARAM(id as _), LPARAM(reverse))
};
return LRESULT(1);
}
Expand Down
5 changes: 2 additions & 3 deletions src/utils/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,13 @@ pub fn get_window_exe(hwnd: HWND) -> Option<String> {
module_path.split('\\').map(|v| v.to_string()).last()
}

pub fn set_foregound_window(hwnd: HWND) -> Result<()> {
pub fn set_foregound_window(hwnd: HWND) {
unsafe {
if is_iconic_window(hwnd) {
ShowWindow(hwnd, SW_RESTORE);
}
if hwnd == get_foreground_window() {
return Ok(());
return;
}
if SetForegroundWindow(hwnd).ok().is_err() {
AllocConsole();
Expand All @@ -212,7 +212,6 @@ pub fn set_foregound_window(hwnd: HWND) -> Result<()> {
SetForegroundWindow(hwnd);
}
};
Ok(())
}

pub fn get_foreground_window() -> HWND {
Expand Down