From 6fae6f6ce70dce180e3072890895ca6a104b2d77 Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 11:39:07 +0400 Subject: [PATCH 1/9] feat: run redirects from --mouse and --keyboard without the menu Starting the tool from a shortcut or a script had no way to switch a redirect on: the only path was the interactive menu, which needs a person at the keyboard. This adds --mouse and --keyboard so the redirect that is wanted can be named up front. With either flag the program starts as usual, switches on exactly what was asked for, and prints one green line saying what is running instead of drawing the menu. Both flags together turn on both. Closing the window or pressing Ctrl+C still switches everything back and ends the program, on the same console control handler the menu already relies on, so the flag mode needs no teardown of its own. An unrecognised argument is refused with its own message and exit code rather than silently opening the menu, so a misspelt flag cannot look like it did nothing. --- src/app/cli.rs | 144 +++++++++++++++++++++++++++++++++++++++++++++++++ src/app/mod.rs | 124 +++++++++++++++++++++--------------------- src/error.rs | 6 +++ src/main.rs | 6 +++ 4 files changed, 219 insertions(+), 61 deletions(-) create mode 100644 src/app/cli.rs diff --git a/src/app/cli.rs b/src/app/cli.rs new file mode 100644 index 0000000..7accfe4 --- /dev/null +++ b/src/app/cli.rs @@ -0,0 +1,144 @@ +//! Reading the redirects asked for on the command line. +//! +//! With no arguments `InputRedirect` shows its menu as it always has. Given +//! `--mouse`, `--keyboard`, or both, it switches those redirects on at once +//! and stays out of the way: no menu is drawn, one green line says what is +//! running, and closing the window or pressing Ctrl+C switches everything +//! back, exactly as the menu does. + +use crate::error::{Error, Result}; + +const MOUSE_FLAG: &str = "--mouse"; +const KEYBOARD_FLAG: &str = "--keyboard"; + +/// Which redirects the command line switched on. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Requested { + pub mouse: bool, + pub keyboard: bool, +} + +impl Requested { + /// Whether the command line asked for anything at all. `false` is the + /// signal to fall back to the interactive menu. + #[must_use] + pub fn any(self) -> bool { + self.mouse || self.keyboard + } + + /// The single line the flag mode prints in place of the menu, naming + /// exactly which redirects are running. + #[must_use] + pub fn active_message(self) -> &'static str { + match (self.mouse, self.keyboard) { + (true, true) => "Mouse and keyboard redirect active", + (true, false) => "Mouse redirect active", + (false, true) => "Keyboard redirect active", + // The flag mode runs only when something was asked for, so this + // arm is never reached; it keeps the match total without a panic. + (false, false) => "Nothing is redirected", + } + } +} + +/// Reads the requested redirects from the arguments, with the program name +/// already removed. +/// +/// An unrecognised argument is refused rather than ignored: a misspelt +/// `--mouse` that quietly opened the menu instead would look like the flag +/// does nothing. +pub fn parse(arguments: I) -> Result +where + I: IntoIterator, +{ + let mut requested = Requested::default(); + + for argument in arguments { + match argument.as_str() { + MOUSE_FLAG => requested.mouse = true, + KEYBOARD_FLAG => requested.keyboard = true, + other => return Err(Error::Usage(format!("unknown argument {other:?}"))), + } + } + + Ok(requested) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn parse_args(arguments: &[&str]) -> Result { + parse(arguments.iter().copied().map(String::from)) + } + + #[test] + fn no_arguments_means_the_menu() { + let requested = parse_args(&[]).unwrap(); + + assert!(!requested.any()); + assert!(!requested.mouse); + assert!(!requested.keyboard); + } + + #[test] + fn the_mouse_flag_switches_the_mouse_on_and_leaves_the_keyboard_alone() { + let requested = parse_args(&["--mouse"]).unwrap(); + + assert!(requested.mouse); + assert!(!requested.keyboard); + } + + #[test] + fn the_keyboard_flag_switches_the_keyboard_on_and_leaves_the_mouse_alone() { + let requested = parse_args(&["--keyboard"]).unwrap(); + + assert!(requested.keyboard); + assert!(!requested.mouse); + } + + #[test] + fn the_two_flags_together_switch_both_on() { + let requested = parse_args(&["--mouse", "--keyboard"]).unwrap(); + + assert!(requested.mouse); + assert!(requested.keyboard); + } + + #[test] + fn the_order_of_the_flags_does_not_matter() { + let one_way = parse_args(&["--mouse", "--keyboard"]).unwrap(); + let other_way = parse_args(&["--keyboard", "--mouse"]).unwrap(); + + assert_eq!(one_way, other_way); + } + + #[test] + fn a_flag_given_twice_is_still_just_on() { + let requested = parse_args(&["--mouse", "--mouse"]).unwrap(); + + assert!(requested.mouse); + assert!(!requested.keyboard); + } + + #[test] + fn an_unknown_argument_is_refused() { + assert!(parse_args(&["--trackball"]).is_err()); + } + + #[test] + fn an_unknown_argument_after_a_good_one_is_still_refused() { + assert!(parse_args(&["--mouse", "--trackball"]).is_err()); + } + + #[test] + fn the_active_line_names_exactly_what_is_running() { + let mouse = parse_args(&["--mouse"]).unwrap(); + let keyboard = parse_args(&["--keyboard"]).unwrap(); + let both = parse_args(&["--mouse", "--keyboard"]).unwrap(); + + assert_eq!(mouse.active_message(), "Mouse redirect active"); + assert_eq!(keyboard.active_message(), "Keyboard redirect active"); + assert_eq!(both.active_message(), "Mouse and keyboard redirect active"); + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 3b56cac..5479b37 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,6 +1,7 @@ //! The program as the user experiences it: a screen, a menu and a loop. mod actions; +mod cli; mod exit; mod instance; @@ -40,7 +41,14 @@ impl App { } /// Brings the driver up and then serves the menu until the user leaves. + /// + /// With `--mouse` or `--keyboard` on the command line it switches those + /// redirects on instead and waits without a menu; see `run_headless`. pub fn run(mut self) -> Result { + // Refused before anything is claimed or installed, so a misspelt flag + // fails on the spot rather than half-starting the program. + let requested = cli::parse(std::env::args().skip(1))?; + // Held for the whole session: dropping it is what lets the next copy // of the program start. let _only_copy = instance::SingleInstance::claim().ok_or(Error::AlreadyRunning)?; @@ -52,10 +60,9 @@ impl App { exit::watch_for_close(); // A restart is only really owed while the driver is half-removed. If it - // is installed and answering, the flag is stale - a build that wrote it - // non-volatile would leave it set past the reboot that should have - // cleared it - and offering a restart that cannot help, every start - // from now on, is the one outcome reboot.rs set out to avoid. + // is installed and answering, the flag is stale, and offering a restart + // that cannot help on every start from now on is the one outcome the + // pending flag is meant to avoid. if driver::is_restart_pending() { if driver::is_running() { driver::clear_restart_pending(); @@ -66,12 +73,23 @@ impl App { self.start()?; + // The command line, not the menu, is driving: switch on what it asked + // for and wait it out. + if requested.any() { + return Ok(self.run_headless(requested)); + } + + self.screen.say( + Tone::Muted, + "Nothing is redirected yet. Press 1 or 2 to start.", + ); + loop { self.redraw(); match ui::wait_for_command(ui::TICK_MS) { // Nothing was pressed: the loop comes back only to refresh the - // counters, which is what makes the screen feel alive. + // counters, which is what keeps the screen feeling alive. MenuKey::Tick => {} MenuKey::Unknown => self .screen @@ -85,6 +103,32 @@ impl App { } } + /// Switches on the redirects named on the command line, says which ones in + /// one green line, and then waits without drawing a menu or reading a key. + /// + /// The only ways out are the window closing and Ctrl+C. Windows delivers + /// both to the console control handler set up in `exit`, which switches + /// the redirects back and ends the process - the same path closing the + /// menu takes, so there is nothing to tear down here. + fn run_headless(&self, requested: cli::Requested) -> Outcome { + if let Some(engine) = self.engine.as_ref() { + if requested.mouse { + engine.set_mouse(true); + } + if requested.keyboard { + engine.set_keyboard(true); + } + } + + self.screen.report(Tone::Done, requested.active_message()); + + // Park rather than spin: there is nothing to do until the control + // handler ends the process, and a spurious wake just parks again. + loop { + std::thread::park(); + } + } + /// Runs one menu entry. Returns an outcome when the program has to close. fn carry_out(&mut self, command: Command) -> Option { match command { @@ -121,69 +165,27 @@ impl App { self.screen.report(Tone::Done, "Ready"); sleep(SETTLE); - self.screen.say( - Tone::Muted, - "Nothing is redirected yet. Press 1 or 2 to start.", - ); Ok(()) } - /// The last screen: says what was switched back before the window closes. - fn shut_down(&mut self) -> Outcome { - self.screen.begin_screen(); - self.screen.report(Tone::Working, "Shutting down"); - - if let Some(engine) = self.engine.take() { - engine.stop(); - } - self.driver = None; - - self.screen - .report(Tone::Done, "Your keyboard and mouse are back to normal"); - self.screen.blank(); - - Outcome::Finished - } - - pub(super) fn redraw(&mut self) { - let dashboard = self.dashboard(); - self.screen.draw(dashboard); + fn redraw(&self) { + self.screen.draw(self.dashboard()); } - pub(super) fn dashboard(&self) -> Dashboard { - let status = self.driver.as_ref().map(|driver| driver.status()); - let counters = self.engine.as_ref().map(Engine::stats).unwrap_or_default(); + fn dashboard(&self) -> Dashboard { + let engine = self.engine.as_ref(); + let stats = engine.map(Engine::stats).unwrap_or_default(); + let driver = self.driver.as_ref(); + let devices = driver.map(|driver| driver.devices()).unwrap_or_default(); Dashboard { - mouse_redirect: self.engine.as_ref().is_some_and(Engine::is_mouse_enabled), - keyboard_redirect: self - .engine - .as_ref() - .is_some_and(Engine::is_keyboard_enabled), - driver_connected: status.is_some_and(|status| status.connected), - virtual_keyboard: status.is_some_and(|status| status.virtual_keyboard), - virtual_mouse: status.is_some_and(|status| status.virtual_mouse), - keystrokes: counters.keystrokes, - clicks: counters.clicks, + mouse_redirect: engine.is_some_and(Engine::is_mouse_enabled), + keyboard_redirect: engine.is_some_and(Engine::is_keyboard_enabled), + driver_connected: driver.is_some_and(|driver| driver.is_connected()), + virtual_keyboard: devices.keyboard, + virtual_mouse: devices.mouse, + keystrokes: stats.keystrokes, + clicks: stats.clicks, } } } - -impl Default for App { - fn default() -> Self { - Self::new() - } -} - -impl Drop for App { - fn drop(&mut self) { - // Quitting from the menu and panicking both end up here. Closing the - // window does not - it goes straight to the same cleanup instead. - if let Some(engine) = self.engine.take() { - engine.stop(); - } - - exit::clean_up(); - self.driver = None; - } -} diff --git a/src/error.rs b/src/error.rs index cc382e2..e3e280b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,4 +49,10 @@ pub enum Error { #[error("the input hooks could not be installed: {0}")] Hook(String), + + /// An argument on the command line was not understood. Kept apart from the + /// driver failures because nothing was attempted: the program refuses the + /// line before it touches the console or the driver. + #[error("{0}")] + Usage(String), } diff --git a/src/main.rs b/src/main.rs index 9cb4548..4c39ddf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ const EXIT_NOT_ELEVATED: u8 = 1; const EXIT_FAILED: u8 = 2; const EXIT_RESTART_REQUIRED: u8 = 3; const EXIT_ALREADY_RUNNING: u8 = 4; +const EXIT_USAGE: u8 = 5; fn main() -> ExitCode { match App::new().run() { @@ -43,6 +44,11 @@ fn give_up(error: &Error) -> ExitCode { eprintln!("Switch to that window, or close it before starting again."); EXIT_ALREADY_RUNNING } + Error::Usage(message) => { + eprintln!("InputRedirect: {message}."); + eprintln!("Pass --mouse, --keyboard, both, or nothing for the menu."); + EXIT_USAGE + } Error::RestartRequired(reason) => { eprintln!("InputRedirect cannot start yet: {reason}."); eprintln!("Restart the computer, then start InputRedirect again."); From ea73e027d3323870051cc7c7b78071bbee0c4f45 Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 12:00:35 +0400 Subject: [PATCH 2/9] fix: restore untouched menu methods in app::run rewrite The first commit rewrote the whole of app/mod.rs and, in doing so, dropped shut_down, the Default and Drop impls, and rebuilt the dashboard against driver methods that do not exist. Restore the original file verbatim and keep only the three intended additions: the cli module, the command-line branch in run, and run_headless. --- src/app/mod.rs | 112 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 5479b37..ac52f10 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -60,9 +60,10 @@ impl App { exit::watch_for_close(); // A restart is only really owed while the driver is half-removed. If it - // is installed and answering, the flag is stale, and offering a restart - // that cannot help on every start from now on is the one outcome the - // pending flag is meant to avoid. + // is installed and answering, the flag is stale - a build that wrote it + // non-volatile would leave it set past the reboot that should have + // cleared it - and offering a restart that cannot help, every start + // from now on, is the one outcome reboot.rs set out to avoid. if driver::is_restart_pending() { if driver::is_running() { driver::clear_restart_pending(); @@ -79,17 +80,12 @@ impl App { return Ok(self.run_headless(requested)); } - self.screen.say( - Tone::Muted, - "Nothing is redirected yet. Press 1 or 2 to start.", - ); - loop { self.redraw(); match ui::wait_for_command(ui::TICK_MS) { // Nothing was pressed: the loop comes back only to refresh the - // counters, which is what keeps the screen feeling alive. + // counters, which is what makes the screen feel alive. MenuKey::Tick => {} MenuKey::Unknown => self .screen @@ -103,6 +99,20 @@ impl App { } } + /// Runs one menu entry. Returns an outcome when the program has to close. + fn carry_out(&mut self, command: Command) -> Option { + match command { + Command::ToggleMouse => self.toggle_mouse(), + Command::ToggleKeyboard => self.toggle_keyboard(), + Command::StopEverything => self.stop_everything(), + Command::RecreateDevices => self.recreate_devices(), + Command::RemoveDriver => return self.remove_driver(), + Command::Quit => return Some(self.shut_down()), + } + + None + } + /// Switches on the redirects named on the command line, says which ones in /// one green line, and then waits without drawing a menu or reading a key. /// @@ -129,20 +139,6 @@ impl App { } } - /// Runs one menu entry. Returns an outcome when the program has to close. - fn carry_out(&mut self, command: Command) -> Option { - match command { - Command::ToggleMouse => self.toggle_mouse(), - Command::ToggleKeyboard => self.toggle_keyboard(), - Command::StopEverything => self.stop_everything(), - Command::RecreateDevices => self.recreate_devices(), - Command::RemoveDriver => return self.remove_driver(), - Command::Quit => return Some(self.shut_down()), - } - - None - } - fn start(&mut self) -> Result<()> { self.screen.banner(); @@ -165,27 +161,69 @@ impl App { self.screen.report(Tone::Done, "Ready"); sleep(SETTLE); + self.screen.say( + Tone::Muted, + "Nothing is redirected yet. Press 1 or 2 to start.", + ); Ok(()) } - fn redraw(&self) { - self.screen.draw(self.dashboard()); + /// The last screen: says what was switched back before the window closes. + fn shut_down(&mut self) -> Outcome { + self.screen.begin_screen(); + self.screen.report(Tone::Working, "Shutting down"); + + if let Some(engine) = self.engine.take() { + engine.stop(); + } + self.driver = None; + + self.screen + .report(Tone::Done, "Your keyboard and mouse are back to normal"); + self.screen.blank(); + + Outcome::Finished + } + + pub(super) fn redraw(&mut self) { + let dashboard = self.dashboard(); + self.screen.draw(dashboard); } - fn dashboard(&self) -> Dashboard { - let engine = self.engine.as_ref(); - let stats = engine.map(Engine::stats).unwrap_or_default(); - let driver = self.driver.as_ref(); - let devices = driver.map(|driver| driver.devices()).unwrap_or_default(); + pub(super) fn dashboard(&self) -> Dashboard { + let status = self.driver.as_ref().map(|driver| driver.status()); + let counters = self.engine.as_ref().map(Engine::stats).unwrap_or_default(); Dashboard { - mouse_redirect: engine.is_some_and(Engine::is_mouse_enabled), - keyboard_redirect: engine.is_some_and(Engine::is_keyboard_enabled), - driver_connected: driver.is_some_and(|driver| driver.is_connected()), - virtual_keyboard: devices.keyboard, - virtual_mouse: devices.mouse, - keystrokes: stats.keystrokes, - clicks: stats.clicks, + mouse_redirect: self.engine.as_ref().is_some_and(Engine::is_mouse_enabled), + keyboard_redirect: self + .engine + .as_ref() + .is_some_and(Engine::is_keyboard_enabled), + driver_connected: status.is_some_and(|status| status.connected), + virtual_keyboard: status.is_some_and(|status| status.virtual_keyboard), + virtual_mouse: status.is_some_and(|status| status.virtual_mouse), + keystrokes: counters.keystrokes, + clicks: counters.clicks, + } + } +} + +impl Default for App { + fn default() -> Self { + Self::new() + } +} + +impl Drop for App { + fn drop(&mut self) { + // Quitting from the menu and panicking both end up here. Closing the + // window does not - it goes straight to the same cleanup instead. + if let Some(engine) = self.engine.take() { + engine.stop(); } + + exit::clean_up(); + self.driver = None; } } From 77780082a5b464218e9b6eb0c00172ba0924d8ca Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 12:36:05 +0400 Subject: [PATCH 3/9] feat: add -m/-k short flags and --help/-h Many command-line tools take a short spelling of every long flag and a --help that lists them, and people reach for both by habit. Without them -m or -h just fell into the "unknown argument" path, which is a confusing way to greet someone typing what they expect to work. -m and -k are now accepted as exact synonyms for --mouse and --keyboard, and --help / -h prints the list of flags and exits. Help is answered before the console is prepared, the single-instance lock is taken or the driver is touched, so it is plain text and costs nothing; it wins over a redirect given on the same line, since someone reading the flags did not mean to start one. wait_before_the_window_closes keeps the text on screen for a double-click launch and returns at once from a shell. parse now returns a Request enum (Menu / Redirect / Help) rather than a bare Requested, so "show the menu", "run these redirects" and "print help" are distinct outcomes the caller matches on instead of inferring. --- src/app/cli.rs | 158 +++++++++++++++++++++++++++++++++++-------------- src/app/mod.rs | 28 +++++++-- 2 files changed, 134 insertions(+), 52 deletions(-) diff --git a/src/app/cli.rs b/src/app/cli.rs index 7accfe4..8daadad 100644 --- a/src/app/cli.rs +++ b/src/app/cli.rs @@ -1,15 +1,46 @@ -//! Reading the redirects asked for on the command line. +//! Reading what the command line asks for. //! //! With no arguments `InputRedirect` shows its menu as it always has. Given -//! `--mouse`, `--keyboard`, or both, it switches those redirects on at once -//! and stays out of the way: no menu is drawn, one green line says what is -//! running, and closing the window or pressing Ctrl+C switches everything -//! back, exactly as the menu does. +//! `--mouse` / `-m`, `--keyboard` / `-k`, or both, it switches those redirects +//! on at once and stays out of the way: no menu is drawn, one green line says +//! what is running, and closing the window or pressing Ctrl+C switches +//! everything back, exactly as the menu does. `--help` / `-h` lists the flags +//! and exits. use crate::error::{Error, Result}; -const MOUSE_FLAG: &str = "--mouse"; -const KEYBOARD_FLAG: &str = "--keyboard"; +const MOUSE_FLAGS: [&str; 2] = ["--mouse", "-m"]; +const KEYBOARD_FLAGS: [&str; 2] = ["--keyboard", "-k"]; +const HELP_FLAGS: [&str; 2] = ["--help", "-h"]; + +/// The usage text `--help` prints. It is shown before the console is prepared +/// for drawing, so it carries no colour or glyphs: just the flags and what +/// each one does. +pub const HELP: &str = "\ +InputRedirect - send your typing and your clicks through the real signed driver. + +Usage: + InputRedirect [options] + +Options: + -m, --mouse redirect the mouse buttons + -k, --keyboard redirect the keyboard + -h, --help show this help and exit + +With no options the interactive menu opens. Given --mouse, --keyboard, or both, +those redirects switch on with no menu - one line says what is running. Closing +the window or pressing Ctrl+C switches everything back and ends the program."; + +/// What the command line asked the program to do. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Request { + /// No redirect flags were given: open the interactive menu. + Menu, + /// Switch on exactly these redirects and skip the menu. + Redirect(Requested), + /// Print the usage text and leave, without touching anything else. + Help, +} /// Which redirects the command line switched on. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -19,13 +50,6 @@ pub struct Requested { } impl Requested { - /// Whether the command line asked for anything at all. `false` is the - /// signal to fall back to the interactive menu. - #[must_use] - pub fn any(self) -> bool { - self.mouse || self.keyboard - } - /// The single line the flag mode prints in place of the menu, naming /// exactly which redirects are running. #[must_use] @@ -34,75 +58,103 @@ impl Requested { (true, true) => "Mouse and keyboard redirect active", (true, false) => "Mouse redirect active", (false, true) => "Keyboard redirect active", - // The flag mode runs only when something was asked for, so this - // arm is never reached; it keeps the match total without a panic. + // `parse` returns `Request::Menu` when nothing was asked for, so + // this arm is never reached; it keeps the match total, no panic. (false, false) => "Nothing is redirected", } } } -/// Reads the requested redirects from the arguments, with the program name -/// already removed. +/// Reads what the arguments ask for, with the program name already removed. /// /// An unrecognised argument is refused rather than ignored: a misspelt /// `--mouse` that quietly opened the menu instead would look like the flag -/// does nothing. -pub fn parse(arguments: I) -> Result +/// does nothing. `--help` wins over any redirect alongside it, since someone +/// asking to read the flags did not mean to start one. +pub fn parse(arguments: I) -> Result where I: IntoIterator, { let mut requested = Requested::default(); for argument in arguments { - match argument.as_str() { - MOUSE_FLAG => requested.mouse = true, - KEYBOARD_FLAG => requested.keyboard = true, - other => return Err(Error::Usage(format!("unknown argument {other:?}"))), + let argument = argument.as_str(); + + if HELP_FLAGS.contains(&argument) { + return Ok(Request::Help); + } else if MOUSE_FLAGS.contains(&argument) { + requested.mouse = true; + } else if KEYBOARD_FLAGS.contains(&argument) { + requested.keyboard = true; + } else { + return Err(Error::Usage(format!("unknown argument {argument:?}"))); } } - Ok(requested) + if requested.mouse || requested.keyboard { + Ok(Request::Redirect(requested)) + } else { + Ok(Request::Menu) + } } #[cfg(test)] mod tests { use super::*; - fn parse_args(arguments: &[&str]) -> Result { + fn parse_args(arguments: &[&str]) -> Result { parse(arguments.iter().copied().map(String::from)) } + fn requested(mouse: bool, keyboard: bool) -> Requested { + Requested { mouse, keyboard } + } + + fn redirect(mouse: bool, keyboard: bool) -> Request { + Request::Redirect(requested(mouse, keyboard)) + } + #[test] fn no_arguments_means_the_menu() { - let requested = parse_args(&[]).unwrap(); - - assert!(!requested.any()); - assert!(!requested.mouse); - assert!(!requested.keyboard); + assert_eq!(parse_args(&[]).unwrap(), Request::Menu); } #[test] fn the_mouse_flag_switches_the_mouse_on_and_leaves_the_keyboard_alone() { - let requested = parse_args(&["--mouse"]).unwrap(); + let request = parse_args(&["--mouse"]).unwrap(); - assert!(requested.mouse); - assert!(!requested.keyboard); + assert_eq!(request, redirect(true, false)); } #[test] fn the_keyboard_flag_switches_the_keyboard_on_and_leaves_the_mouse_alone() { - let requested = parse_args(&["--keyboard"]).unwrap(); + let request = parse_args(&["--keyboard"]).unwrap(); - assert!(requested.keyboard); - assert!(!requested.mouse); + assert_eq!(request, redirect(false, true)); + } + + #[test] + fn the_short_mouse_flag_means_the_same_as_the_long_one() { + assert_eq!(parse_args(&["-m"]).unwrap(), redirect(true, false)); + } + + #[test] + fn the_short_keyboard_flag_means_the_same_as_the_long_one() { + assert_eq!(parse_args(&["-k"]).unwrap(), redirect(false, true)); } #[test] fn the_two_flags_together_switch_both_on() { - let requested = parse_args(&["--mouse", "--keyboard"]).unwrap(); + let request = parse_args(&["--mouse", "--keyboard"]).unwrap(); - assert!(requested.mouse); - assert!(requested.keyboard); + assert_eq!(request, redirect(true, true)); + } + + #[test] + fn the_short_flags_can_be_combined_too() { + let request = parse_args(&["-m", "-k"]).unwrap(); + + assert_eq!(request, redirect(true, true)); } #[test] @@ -115,10 +167,24 @@ mod tests { #[test] fn a_flag_given_twice_is_still_just_on() { - let requested = parse_args(&["--mouse", "--mouse"]).unwrap(); + let request = parse_args(&["--mouse", "-m"]).unwrap(); + + assert_eq!(request, redirect(true, false)); + } + + #[test] + fn either_spelling_of_help_asks_for_help() { + assert_eq!(parse_args(&["--help"]).unwrap(), Request::Help); + assert_eq!(parse_args(&["-h"]).unwrap(), Request::Help); + } + + #[test] + fn help_wins_over_a_redirect_alongside_it() { + let after_mouse = parse_args(&["--mouse", "--help"]).unwrap(); + let before_keyboard = parse_args(&["-h", "--keyboard"]).unwrap(); - assert!(requested.mouse); - assert!(!requested.keyboard); + assert_eq!(after_mouse, Request::Help); + assert_eq!(before_keyboard, Request::Help); } #[test] @@ -133,9 +199,9 @@ mod tests { #[test] fn the_active_line_names_exactly_what_is_running() { - let mouse = parse_args(&["--mouse"]).unwrap(); - let keyboard = parse_args(&["--keyboard"]).unwrap(); - let both = parse_args(&["--mouse", "--keyboard"]).unwrap(); + let mouse = requested(true, false); + let keyboard = requested(false, true); + let both = requested(true, true); assert_eq!(mouse.active_message(), "Mouse redirect active"); assert_eq!(keyboard.active_message(), "Keyboard redirect active"); diff --git a/src/app/mod.rs b/src/app/mod.rs index ac52f10..90c9308 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -42,12 +42,19 @@ impl App { /// Brings the driver up and then serves the menu until the user leaves. /// - /// With `--mouse` or `--keyboard` on the command line it switches those - /// redirects on instead and waits without a menu; see `run_headless`. + /// `--help` / `-h` prints the list of flags and exits before anything is + /// claimed. `--mouse` / `-m` or `--keyboard` / `-k` switch those redirects + /// on and wait without a menu instead; see `run_headless`. pub fn run(mut self) -> Result { - // Refused before anything is claimed or installed, so a misspelt flag - // fails on the spot rather than half-starting the program. - let requested = cli::parse(std::env::args().skip(1))?; + // Read before anything is claimed or installed, so a misspelt flag + // fails on the spot and `--help` answers without a driver. + let request = cli::parse(std::env::args().skip(1))?; + + // Help touches nothing else: it is printed and the program leaves. + if request == cli::Request::Help { + show_help(); + return Ok(Outcome::Finished); + } // Held for the whole session: dropping it is what lets the next copy // of the program start. @@ -76,7 +83,7 @@ impl App { // The command line, not the menu, is driving: switch on what it asked // for and wait it out. - if requested.any() { + if let cli::Request::Redirect(requested) = request { return Ok(self.run_headless(requested)); } @@ -227,3 +234,12 @@ impl Drop for App { self.driver = None; } } + +/// Prints the command-line help and holds the window open long enough to read +/// it. Like the startup errors, help is shown before the console is prepared, +/// so it is plain text; `wait_before_the_window_closes` only pauses when the +/// program owns the window, so `--help` from a shell returns at once. +fn show_help() { + println!("{}", cli::HELP); + ui::wait_before_the_window_closes(); +} From bea8b9d0b2f6b571aefee044ed62c6dd08dd0e67 Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 12:36:54 +0400 Subject: [PATCH 4/9] fix: print --help and exit without waiting for a key Help was pausing on "Press any key to close this window", which is menu behaviour: a command-line tool asked for its usage prints it and returns at once. The pause only made sense for a startup error a double-click user would otherwise never see; help is something they typed, so it just prints and the program leaves. --- src/app/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 90c9308..0a9f0a9 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -235,11 +235,9 @@ impl Drop for App { } } -/// Prints the command-line help and holds the window open long enough to read -/// it. Like the startup errors, help is shown before the console is prepared, -/// so it is plain text; `wait_before_the_window_closes` only pauses when the -/// program owns the window, so `--help` from a shell returns at once. +/// Prints the command-line help and returns. Help is shown before the console +/// is prepared for drawing, so it is plain text, and it does not wait for a +/// key: a command-line tool that was asked for its usage prints it and exits. fn show_help() { println!("{}", cli::HELP); - ui::wait_before_the_window_closes(); } From b00142b0343e3aa5bdec9f48afbc6a189402806c Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 12:49:01 +0400 Subject: [PATCH 5/9] docs: trim the --help text The line describing what --mouse and --keyboard do to the menu repeated what the options table already says. Help stays to the point: what the flags are, and how the program ends. --- src/app/cli.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/cli.rs b/src/app/cli.rs index 8daadad..ae52f3e 100644 --- a/src/app/cli.rs +++ b/src/app/cli.rs @@ -27,9 +27,8 @@ Options: -k, --keyboard redirect the keyboard -h, --help show this help and exit -With no options the interactive menu opens. Given --mouse, --keyboard, or both, -those redirects switch on with no menu - one line says what is running. Closing -the window or pressing Ctrl+C switches everything back and ends the program."; +With no options the interactive menu opens. Closing the window or pressing +Ctrl+C switches everything back and ends the program."; /// What the command line asked the program to do. #[derive(Clone, Copy, Debug, PartialEq, Eq)] From 87950a55de33fc71deb212825735ccb8fd3aaae1 Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 12:52:22 +0400 Subject: [PATCH 6/9] feat: set the active-redirect line apart and trim its help entry In flag mode the line naming what is running sat flush against the setup lines that scrolled past, so the one line worth reading did not stand out. A blank line before it gives it room, the same way the menu spaces its blocks. The --help entry said "show this help and exit"; that a command-line tool returns after printing its usage is a given, so the entry is just "show this help". --- src/app/cli.rs | 2 +- src/app/mod.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/cli.rs b/src/app/cli.rs index ae52f3e..a093d7a 100644 --- a/src/app/cli.rs +++ b/src/app/cli.rs @@ -25,7 +25,7 @@ Usage: Options: -m, --mouse redirect the mouse buttons -k, --keyboard redirect the keyboard - -h, --help show this help and exit + -h, --help show this help With no options the interactive menu opens. Closing the window or pressing Ctrl+C switches everything back and ends the program."; diff --git a/src/app/mod.rs b/src/app/mod.rs index 0a9f0a9..f1fd98e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -137,6 +137,9 @@ impl App { } } + // A blank line sets this apart from the setup lines that scrolled + // past, so the one line worth reading stands on its own. + self.screen.blank(); self.screen.report(Tone::Done, requested.active_message()); // Park rather than spin: there is nothing to do until the control From e8fa05bb581906d6e38a7d4ac88c719949404b88 Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 13:16:41 +0400 Subject: [PATCH 7/9] feat: add --remove-driver flag and rename the menu key to R The driver-removal action was called "remove" everywhere in the code and the README, but the menu key was D, the odd one out. This settles on "remove": the menu key is now R, and a matching -r / --remove-driver flag runs the very same flow from the command line - it confirms, removes the driver package and offers the restart, then ends. Help lists the new flag, README gains a Command line section describing all of them, and the Quick Start key table shows R. --- README.md | 27 ++++++++++++++++++++-- src/app/cli.rs | 60 ++++++++++++++++++++++++++++++++++++++++-------- src/app/mod.rs | 15 +++++++++--- src/ui/prompt.rs | 4 ++-- src/ui/screen.rs | 4 ++-- 5 files changed, 91 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 5425a54..0dac727 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,38 @@ Run `InputRedirect.exe` as administrator and choose an option: | `2` | Redirect keyboard | | `3` | Stop everything | | `4` | Re-create the virtual devices | -| `D` | Remove the driver | +| `R` | Remove the driver | | `Q` | Quit | `1` and `2` are switches - press again to turn them off. Closing the window turns the redirect off. -After `D` the machine has to restart before the driver is fully gone; the tool +After `R` the machine has to restart before the driver is fully gone; the tool offers it and reminds you on the next start if you skipped it. +## Command line + +You do not have to open the menu. Started from a terminal, InputRedirect can +switch a redirect on straight away - handy for shortcuts, scheduled tasks and +scripts: + +``` +Usage: + InputRedirect [options] + +Options: + -m, --mouse redirect the mouse buttons + -k, --keyboard redirect the keyboard + -r, --remove-driver remove the driver + -h, --help show this help +``` + +With `--mouse`, `--keyboard`, or both (short `-m` / `-k`), the tool starts as +usual, switches on what you asked for and prints one line saying what is +running - no menu. Closing the window or pressing Ctrl+C switches everything +back, exactly as the menu does. `--remove-driver` (short `-r`) runs the same +removal as the `R` key. + ## Building ```sh diff --git a/src/app/cli.rs b/src/app/cli.rs index a093d7a..8fdc8e6 100644 --- a/src/app/cli.rs +++ b/src/app/cli.rs @@ -4,13 +4,15 @@ //! `--mouse` / `-m`, `--keyboard` / `-k`, or both, it switches those redirects //! on at once and stays out of the way: no menu is drawn, one green line says //! what is running, and closing the window or pressing Ctrl+C switches -//! everything back, exactly as the menu does. `--help` / `-h` lists the flags -//! and exits. +//! everything back, exactly as the menu does. `--remove-driver` / `-r` takes +//! the driver back out - the same flow the menu's `R` runs - and `--help` / +//! `-h` lists the flags and exits. use crate::error::{Error, Result}; const MOUSE_FLAGS: [&str; 2] = ["--mouse", "-m"]; const KEYBOARD_FLAGS: [&str; 2] = ["--keyboard", "-k"]; +const REMOVE_FLAGS: [&str; 2] = ["--remove-driver", "-r"]; const HELP_FLAGS: [&str; 2] = ["--help", "-h"]; /// The usage text `--help` prints. It is shown before the console is prepared @@ -23,9 +25,10 @@ Usage: InputRedirect [options] Options: - -m, --mouse redirect the mouse buttons - -k, --keyboard redirect the keyboard - -h, --help show this help + -m, --mouse redirect the mouse buttons + -k, --keyboard redirect the keyboard + -r, --remove-driver remove the driver + -h, --help show this help With no options the interactive menu opens. Closing the window or pressing Ctrl+C switches everything back and ends the program."; @@ -33,10 +36,12 @@ Ctrl+C switches everything back and ends the program."; /// What the command line asked the program to do. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Request { - /// No redirect flags were given: open the interactive menu. + /// No flags were given: open the interactive menu. Menu, /// Switch on exactly these redirects and skip the menu. Redirect(Requested), + /// Take the driver back out of Windows, then end. + RemoveDriver, /// Print the usage text and leave, without touching anything else. Help, } @@ -68,19 +73,25 @@ impl Requested { /// /// An unrecognised argument is refused rather than ignored: a misspelt /// `--mouse` that quietly opened the menu instead would look like the flag -/// does nothing. `--help` wins over any redirect alongside it, since someone -/// asking to read the flags did not mean to start one. +/// does nothing. When more than one thing is asked for, the request that does +/// the least damage if the rest were a mistake wins: `--help` only prints, so +/// it comes first, and `--remove-driver` ends the program, so it comes before +/// the redirects it would otherwise switch on only to tear straight down. pub fn parse(arguments: I) -> Result where I: IntoIterator, { let mut requested = Requested::default(); + let mut help = false; + let mut remove = false; for argument in arguments { let argument = argument.as_str(); if HELP_FLAGS.contains(&argument) { - return Ok(Request::Help); + help = true; + } else if REMOVE_FLAGS.contains(&argument) { + remove = true; } else if MOUSE_FLAGS.contains(&argument) { requested.mouse = true; } else if KEYBOARD_FLAGS.contains(&argument) { @@ -90,7 +101,11 @@ where } } - if requested.mouse || requested.keyboard { + if help { + Ok(Request::Help) + } else if remove { + Ok(Request::RemoveDriver) + } else if requested.mouse || requested.keyboard { Ok(Request::Redirect(requested)) } else { Ok(Request::Menu) @@ -177,6 +192,15 @@ mod tests { assert_eq!(parse_args(&["-h"]).unwrap(), Request::Help); } + #[test] + fn either_spelling_of_remove_asks_to_remove_the_driver() { + let long = parse_args(&["--remove-driver"]).unwrap(); + let short = parse_args(&["-r"]).unwrap(); + + assert_eq!(long, Request::RemoveDriver); + assert_eq!(short, Request::RemoveDriver); + } + #[test] fn help_wins_over_a_redirect_alongside_it() { let after_mouse = parse_args(&["--mouse", "--help"]).unwrap(); @@ -186,6 +210,22 @@ mod tests { assert_eq!(before_keyboard, Request::Help); } + #[test] + fn remove_wins_over_a_redirect_alongside_it() { + let with_mouse = parse_args(&["--mouse", "-r"]).unwrap(); + let with_keyboard = parse_args(&["-r", "--keyboard"]).unwrap(); + + assert_eq!(with_mouse, Request::RemoveDriver); + assert_eq!(with_keyboard, Request::RemoveDriver); + } + + #[test] + fn help_wins_over_removing_the_driver_too() { + let request = parse_args(&["--remove-driver", "--help"]).unwrap(); + + assert_eq!(request, Request::Help); + } + #[test] fn an_unknown_argument_is_refused() { assert!(parse_args(&["--trackball"]).is_err()); diff --git a/src/app/mod.rs b/src/app/mod.rs index f1fd98e..9bda0db 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -43,8 +43,10 @@ impl App { /// Brings the driver up and then serves the menu until the user leaves. /// /// `--help` / `-h` prints the list of flags and exits before anything is - /// claimed. `--mouse` / `-m` or `--keyboard` / `-k` switch those redirects - /// on and wait without a menu instead; see `run_headless`. + /// claimed. `--remove-driver` / `-r` brings the driver up only to take it + /// straight back out, the same flow the menu's `R` runs. `--mouse` / `-m` + /// or `--keyboard` / `-k` switch those redirects on and wait without a + /// menu instead; see `run_headless`. pub fn run(mut self) -> Result { // Read before anything is claimed or installed, so a misspelt flag // fails on the spot and `--help` answers without a driver. @@ -81,6 +83,13 @@ impl App { self.start()?; + // Asked to remove the driver from the command line: run the very flow + // the menu's R does - it confirms, removes, and offers the restart - + // then ends, rather than dropping into the menu afterwards. + if request == cli::Request::RemoveDriver { + return Ok(self.remove_driver().unwrap_or(Outcome::Finished)); + } + // The command line, not the menu, is driving: switch on what it asked // for and wait it out. if let cli::Request::Redirect(requested) = request { @@ -96,7 +105,7 @@ impl App { MenuKey::Tick => {} MenuKey::Unknown => self .screen - .say(Tone::Warning, "Unknown key. Use 1, 2, 3, 4, D or Q."), + .say(Tone::Warning, "Unknown key. Use 1, 2, 3, 4, R or Q."), MenuKey::Chosen(command) => { if let Some(outcome) = self.carry_out(command) { return Ok(outcome); diff --git a/src/ui/prompt.rs b/src/ui/prompt.rs index 37eba6f..47f7ebe 100644 --- a/src/ui/prompt.rs +++ b/src/ui/prompt.rs @@ -123,7 +123,7 @@ fn command_for(scan_code: u16) -> Option { 0x03 | 0x50 => Command::ToggleKeyboard, // 2 0x04 | 0x51 => Command::StopEverything, // 3 0x05 | 0x4B => Command::RecreateDevices, // 4 - 0x20 => Command::RemoveDriver, // d + 0x13 => Command::RemoveDriver, // r 0x10 | 0x01 => Command::Quit, // q, escape _ => return None, }; @@ -218,7 +218,7 @@ mod tests { assert_eq!(command_for(0x03), Some(Command::ToggleKeyboard)); assert_eq!(command_for(0x04), Some(Command::StopEverything)); assert_eq!(command_for(0x05), Some(Command::RecreateDevices)); - assert_eq!(command_for(0x20), Some(Command::RemoveDriver)); + assert_eq!(command_for(0x13), Some(Command::RemoveDriver)); assert_eq!(command_for(0x10), Some(Command::Quit)); } diff --git a/src/ui/screen.rs b/src/ui/screen.rs index b9b1286..02ee07d 100644 --- a/src/ui/screen.rs +++ b/src/ui/screen.rs @@ -209,7 +209,7 @@ fn menu(dashboard: Dashboard) -> String { )); text.push_str(&theme::action('3', "stop everything")); text.push_str(&theme::action('4', "re-create virtual devices")); - text.push_str(&theme::action('D', "remove driver")); + text.push_str(&theme::action('R', "remove driver")); text.push_str(&theme::action('Q', "quit")); text.push_str(theme::BLANK); @@ -275,7 +275,7 @@ mod tests { ("[2]", "redirect keyboard"), ("[3]", "stop everything"), ("[4]", "re-create virtual devices"), - ("[D]", "remove driver"), + ("[R]", "remove driver"), ("[Q]", "quit"), ] { assert!(line_with(&frame, key).contains(label), "{key}"); From 5951df340213fe230c9183cdea7f6a7991f8777a Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 13:36:10 +0400 Subject: [PATCH 8/9] fix: keep driver removal from installing anything --- src/app/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 2 +- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 9bda0db..b2ec53b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -9,6 +9,8 @@ use std::sync::Arc; use std::thread::sleep; use std::time::Duration; +use windows_registry::LOCAL_MACHINE; + use crate::driver::{self, Driver, Step}; use crate::error::{Error, Result}; use crate::redirect::Engine; @@ -17,6 +19,9 @@ use crate::ui::{self, Command, Dashboard, MenuKey, Screen, Tone}; /// Long enough for the setup lines to be read before the screen takes over. const SETTLE: Duration = Duration::from_millis(600); +/// The service whose presence means the driver package is installed. +const DRIVER_SERVICE_KEY: &str = r"SYSTEM\CurrentControlSet\Services\logi_joy_bus_enum"; + /// Why the program stopped. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Outcome { @@ -43,10 +48,10 @@ impl App { /// Brings the driver up and then serves the menu until the user leaves. /// /// `--help` / `-h` prints the list of flags and exits before anything is - /// claimed. `--remove-driver` / `-r` brings the driver up only to take it - /// straight back out, the same flow the menu's `R` runs. `--mouse` / `-m` - /// or `--keyboard` / `-k` switch those redirects on and wait without a - /// menu instead; see `run_headless`. + /// claimed. `--remove-driver` / `-r` removes an installed driver without + /// installing one when there is nothing to remove. `--mouse` / `-m` or + /// `--keyboard` / `-k` switch those redirects on and wait without a menu + /// instead; see `run_headless`. pub fn run(mut self) -> Result { // Read before anything is claimed or installed, so a misspelt flag // fails on the spot and `--help` answers without a driver. @@ -62,6 +67,21 @@ impl App { // of the program start. let _only_copy = instance::SingleInstance::claim().ok_or(Error::AlreadyRunning)?; + let restart_pending = driver::is_restart_pending(); + + // Driver::connect installs a missing package. A removal request must + // never create the very thing it was asked to take away, so answer the + // no-op before preparing the console or calling start. A pending + // restart is different: removal already happened and still has to be + // finished, so the existing restart screen below takes precedence. + if request == cli::Request::RemoveDriver + && !restart_pending + && !driver_is_installed() + { + println!("InputRedirect: no driver is installed, so there is nothing to remove."); + return Ok(Outcome::Finished); + } + ui::claim_console(); // From here on the window can be closed at any moment, and the @@ -73,7 +93,7 @@ impl App { // non-volatile would leave it set past the reboot that should have // cleared it - and offering a restart that cannot help, every start // from now on, is the one outcome reboot.rs set out to avoid. - if driver::is_restart_pending() { + if restart_pending { if driver::is_running() { driver::clear_restart_pending(); } else { @@ -87,7 +107,14 @@ impl App { // the menu's R does - it confirms, removes, and offers the restart - // then ends, rather than dropping into the menu afterwards. if request == cli::Request::RemoveDriver { - return Ok(self.remove_driver().unwrap_or(Outcome::Finished)); + if let Some(outcome) = self.remove_driver() { + return Ok(outcome); + } + + // In menu mode `say` is rendered by the next redraw. There is no + // next redraw here, so report the declined operation directly. + self.screen.report(Tone::Muted, "Nothing was removed"); + return Ok(Outcome::Finished); } // The command line, not the menu, is driving: switch on what it asked @@ -247,6 +274,15 @@ impl Drop for App { } } +/// Whether the driver package exists without starting or installing it. +fn driver_is_installed() -> bool { + LOCAL_MACHINE + .options() + .read() + .open(DRIVER_SERVICE_KEY) + .is_ok() +} + /// Prints the command-line help and returns. Help is shown before the console /// is prepared for drawing, so it is plain text, and it does not wait for a /// key: a command-line tool that was asked for its usage prints it and exits. diff --git a/src/main.rs b/src/main.rs index 4c39ddf..fb7ce61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,7 +46,7 @@ fn give_up(error: &Error) -> ExitCode { } Error::Usage(message) => { eprintln!("InputRedirect: {message}."); - eprintln!("Pass --mouse, --keyboard, both, or nothing for the menu."); + eprintln!("Run InputRedirect --help for usage."); EXIT_USAGE } Error::RestartRequired(reason) => { From 8b7ea2a97a9b9c3e6e0439e815b7ae6793c94ca2 Mon Sep 17 00:00:00 2001 From: kerogenesis Date: Thu, 30 Jul 2026 13:39:16 +0400 Subject: [PATCH 9/9] fix: apply rustfmt and add the touchpad note --- README.md | 2 ++ src/app/mod.rs | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0dac727..669a1de 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ from a real Logitech device. One executable, nothing to install: the signed driver files are embedded in it and deployed on first start. +This is really useful when anti-cheat software decides to block your laptop's touchpad for some reason lol. + ## Requirements - Windows 10 or 11 diff --git a/src/app/mod.rs b/src/app/mod.rs index b2ec53b..6cbfc54 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -74,10 +74,7 @@ impl App { // no-op before preparing the console or calling start. A pending // restart is different: removal already happened and still has to be // finished, so the existing restart screen below takes precedence. - if request == cli::Request::RemoveDriver - && !restart_pending - && !driver_is_installed() - { + if request == cli::Request::RemoveDriver && !restart_pending && !driver_is_installed() { println!("InputRedirect: no driver is installed, so there is nothing to remove."); return Ok(Outcome::Finished); }