From 4d58c95c737f7273135bd4ca4451b354a03cc89f Mon Sep 17 00:00:00 2001 From: starlight Date: Thu, 23 Jan 2025 21:49:40 +1300 Subject: [PATCH] yay --- .gitignore | 2 + Cargo.toml | 11 ++ README.md | 38 ++++++ src/config.rs | 95 +++++++++++++++ src/listen.rs | 318 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 27 +++++ 6 files changed, 491 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/config.rs create mode 100644 src/listen.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f51b22e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "keydisplay" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +toml = "0.8" +dirs = "5.0" +inputbot = "0.6.0" +thiserror = "2.0.11" diff --git a/README.md b/README.md index cb48910..47cd9e0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ # keydisplay +note: you will need to add yourself to the input group or run as root if you intend to use wayland +you can do it like this + +`doas usermod -aG input $USER` + +## config.rs +- use serde with a config struct + +- impl with defaults and read function + +### read: +- get the current users config folder + +- make a folder if the "keydisplay" folder doesnt exist + +- make a keydisplay/config.toml if it doesnt exist + +- give it 600 permissions + +config: +- theme = {catppuccin_mocha, grey, night, day} +- [listen] +- - keys = "z, x" +- - mouse = "m1, m2" +- [display] +- - mouse = "key" + +## listen.rs +- use inputbot to listen for inputs https://github.com/obv-mikhail/InputBot + +- keys you can use are here https://docs.rs/inputbot/latest/src/inputbot/public.rs.html#300-352 + +## main.rs +- read config + +- start listener on keys from config + +- load gui using theme diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..b8b577f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,95 @@ +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("Failed to read config file: {0}")] + FileRead(#[from] std::io::Error), + #[error("Failed to parse config file: {0}")] + Parse(#[from] toml::ser::Error), + #[error("Failed to parse config file: {0}")] + ParseDeserialise(#[from] toml::de::Error), + #[error("Failed to write config file: {0}")] + FileWrite(std::io::Error), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + // theme = grey / night / day / catppuccin_mocha + pub theme: String, + pub listen: ListenConfig, + pub display: DisplayConfig, +} + +// [listen] +#[derive(Debug, Serialize, Deserialize)] +pub struct ListenConfig { + // keys = ["KeyZ", "KeyX"] + pub keys: Vec, + // mouse = ["LeftButton", "RightButton"] + pub mouse: Vec, +} + +// [display] +#[derive(Debug, Serialize, Deserialize)] +pub struct DisplayConfig { + pub mouse: String, +} + +impl Config { + // Provide default values + pub fn default() -> Self { + Self { + theme: "grey".to_string(), + listen: ListenConfig { + keys: vec!["ZKey".to_string(), "XKey".to_string()], + mouse: vec!["LeftButton".to_string(), "RightButton".to_string()], + }, + display: DisplayConfig { + mouse: "key".to_string(), + }, + } + } + + // Load config from a file, creating it with defaults if it doesn't exist + pub fn load_or_create(path: PathBuf) -> Result { + let full_path = path.join("config.toml"); + + if !full_path.exists() { + println!("Creating default config"); + let config = Self::default(); + + fs::create_dir_all(&path)?; + + config.save(&full_path)?; + return Ok(config); + } + + println!("Loading default config from {full_path:?}"); + let content = fs::read_to_string(&full_path)?; + let config: Config = toml::from_str(&content)?; + Ok(config) + } + + // Save config to a file + pub fn save(&self, path: &PathBuf) -> Result<(), ConfigError> { + let content = toml::to_string_pretty(&self).map_err(|e| ConfigError::Parse(e))?; + + fs::write(path, content).map_err(ConfigError::FileWrite)?; + + // Ensure proper permissions on Unix-like systems + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(path) + .map_err(ConfigError::FileWrite)? + .permissions(); + perms.set_mode(0o600); // Read/write for owner only + fs::set_permissions(path, perms).map_err(ConfigError::FileWrite)?; + } + + Ok(()) + } +} diff --git a/src/listen.rs b/src/listen.rs new file mode 100644 index 0000000..a21ca25 --- /dev/null +++ b/src/listen.rs @@ -0,0 +1,318 @@ +use std::str::FromStr; + +use inputbot::{KeybdKey, KeybdKey::*, MouseButton, MouseButton::*}; + +pub fn listen(keys: Vec, m_buttons: Vec) { + for key in keys { + key.bind(move || { + recieve_key_press(key); + while key.is_pressed() { + // theres probably a better way to do this + } + recieve_key_release(key); + }) + } + + for button in m_buttons { + button.bind(move || { + recieve_mouse_press(button); + while button.is_pressed() {} + recieve_mouse_release(button); + }) + } + + MiddleButton.bind(|| { + println!("aaa"); + }); + + inputbot::handle_input_events(); +} + +// move me into gui.rs later +fn recieve_key_press(key: KeybdKey) { + println!("pressed {}", string_from_keybdkey(key)); +} + +fn recieve_key_release(key: KeybdKey) { + println!("released {}", string_from_keybdkey(key)); +} + +fn recieve_mouse_press(button: MouseButton) { + println!("pressed {}", string_from_mousebutton(button)); +} + +fn recieve_mouse_release(button: MouseButton) { + println!("released {}", string_from_mousebutton(button)); +} + +pub fn keybdkey_from_config(key: &str) -> KeybdKey { + match key { + "BackspaceKey" => BackspaceKey, + "TabKey" => TabKey, + "EnterKey" => EnterKey, + "EscapeKey" => EscapeKey, + "SpaceKey" => EscapeKey, + "PageUpKey" => PageUpKey, + "PageDownKey" => PageDownKey, + "EndKey" => EndKey, + "HomeKey" => HomeKey, + "LeftKey" => LeftKey, + "UpKey" => UpKey, + "RightKey" => RightKey, + "DownKey" => DownKey, + "InsertKey" => InsertKey, + "DeleteKey" => DeleteKey, + "Numrow0Key" => Numrow0Key, + "Numrow1Key" => Numrow1Key, + "Numrow2Key" => Numrow2Key, + "Numrow3Key" => Numrow3Key, + "Numrow4Key" => Numrow4Key, + "Numrow5Key" => Numrow5Key, + "Numrow6Key" => Numrow6Key, + "Numrow7Key" => Numrow7Key, + "Numrow8Key" => Numrow8Key, + "Numrow9Key" => Numrow9Key, + "AKey" => AKey, + "BKey" => BKey, + "CKey" => CKey, + "DKey" => DKey, + "EKey" => EKey, + "FKey" => FKey, + "GKey" => GKey, + "HKey" => HKey, + "IKey" => IKey, + "JKey" => JKey, + "KKey" => KKey, + "LKey" => LKey, + "MKey" => MKey, + "NKey" => NKey, + "OKey" => OKey, + "PKey" => PKey, + "QKey" => QKey, + "RKey" => RKey, + "SKey" => SKey, + "TKey" => TKey, + "UKey" => UKey, + "VKey" => VKey, + "WKey" => WKey, + "XKey" => XKey, + "YKey" => YKey, + "ZKey" => ZKey, + "LSuper" => LSuper, + "RSuper" => RSuper, + "Numpad0Key" => Numpad0Key, + "Numpad1Key" => Numpad1Key, + "Numpad2Key" => Numpad2Key, + "Numpad3Key" => Numpad3Key, + "Numpad4Key" => Numpad4Key, + "Numpad5Key" => Numpad5Key, + "Numpad6Key" => Numpad6Key, + "Numpad7Key" => Numpad7Key, + "Numpad8Key" => Numpad8Key, + "Numpad9Key" => Numpad9Key, + "F1Key" => F1Key, + "F2Key" => F2Key, + "F3Key" => F3Key, + "F4Key" => F4Key, + "F5Key" => F5Key, + "F6Key" => F6Key, + "F7Key" => F7Key, + "F8Key" => F8Key, + "F9Key" => F9Key, + "F10Key" => F10Key, + "F11Key" => F11Key, + "F12Key" => F12Key, + "F13Key" => F13Key, + "F14Key" => F14Key, + "F15Key" => F15Key, + "F16Key" => F16Key, + "F17Key" => F17Key, + "F18Key" => F18Key, + "F19Key" => F19Key, + "F20Key" => F20Key, + "F21Key" => F21Key, + "F22Key" => F22Key, + "F23Key" => F23Key, + "F24Key" => F24Key, + "NumLockKey" => NumLockKey, + "ScrollLockKey" => ScrollLockKey, + "CapsLockKey" => CapsLockKey, + "LShiftKey" => LShiftKey, + "RShiftKey" => RShiftKey, + "LControlKey" => LControlKey, + "RControlKey" => RControlKey, + "LAltKey" => LAltKey, + "RAltKey" => RAltKey, + + "BrowserBackKey" => BrowserBackKey, + "BrowserForwardKey" => BrowserForwardKey, + "BrowserRefreshKey" => BrowserRefreshKey, + + "VolumeMuteKey" => VolumeMuteKey, + "VolumeDownKey" => VolumeDownKey, + "VolumeUpKey" => VolumeUpKey, + + "MediaNextTrackKey" => MediaNextTrackKey, + "MediaPrevTrackKey" => MediaPrevTrackKey, + "MediaStopKey" => MediaStopKey, + "MediaPlayPauseKey" => MediaPlayPauseKey, + + "BackquoteKey" => BackquoteKey, + "SlashKey" => SlashKey, + "BackslashKey" => BackslashKey, + "CommaKey" => CommaKey, + "PeriodKey" => PeriodKey, + "MinusKey" => MinusKey, + "QuoteKey" => QuoteKey, + "SemicolonKey" => SemicolonKey, + "LBracketKey" => LBracketKey, + "RBracketKey" => RBracketKey, + "EqualKey" => EqualKey, + _ => panic!("invalid keycode!"), + } +} + +fn string_from_keybdkey(key: KeybdKey) -> String { + match key { + BackspaceKey => String::from("๐Ÿก"), + TabKey => String::from("โ‡Œ"), + EnterKey => String::from("โฎ "), + EscapeKey => String::from("ESC"), + SpaceKey => String::from(""), + PageUpKey => String::from("PGUP"), + PageDownKey => String::from("PGDN"), + EndKey => String::from("END"), + HomeKey => String::from("HOME"), + LeftKey => String::from("โฏ‡"), + UpKey => String::from("โฏ…"), + RightKey => String::from("โฏˆ"), + DownKey => String::from("โฏ†"), + InsertKey => String::from("INS"), + DeleteKey => String::from("DEL"), + Numrow0Key => String::from("0"), + Numrow1Key => String::from("1"), + Numrow2Key => String::from("2"), + Numrow3Key => String::from("3"), + Numrow4Key => String::from("4"), + Numrow5Key => String::from("5"), + Numrow6Key => String::from("6"), + Numrow7Key => String::from("7"), + Numrow8Key => String::from("8"), + Numrow9Key => String::from("9"), + AKey => String::from("A"), + BKey => String::from("B"), + CKey => String::from("C"), + DKey => String::from("D"), + EKey => String::from("E"), + FKey => String::from("F"), + GKey => String::from("G"), + HKey => String::from("H"), + IKey => String::from("I"), + JKey => String::from("J"), + KKey => String::from("K"), + LKey => String::from("L"), + MKey => String::from("M"), + NKey => String::from("N"), + OKey => String::from("O"), + PKey => String::from("P"), + QKey => String::from("Q"), + RKey => String::from("R"), + SKey => String::from("S"), + TKey => String::from("T"), + UKey => String::from("U"), + VKey => String::from("V"), + WKey => String::from("W"), + XKey => String::from("X"), + YKey => String::from("Y"), + ZKey => String::from("Z"), + LSuper => String::from("SUPER"), + RSuper => String::from("SUPER"), + Numpad0Key => String::from("0"), + Numpad1Key => String::from("1"), + Numpad2Key => String::from("2"), + Numpad3Key => String::from("3"), + Numpad4Key => String::from("4"), + Numpad5Key => String::from("5"), + Numpad6Key => String::from("6"), + Numpad7Key => String::from("7"), + Numpad8Key => String::from("8"), + Numpad9Key => String::from("9"), + F1Key => String::from("F1"), + F2Key => String::from("F2"), + F3Key => String::from("F3"), + F4Key => String::from("F4"), + F5Key => String::from("F5"), + F6Key => String::from("F6"), + F7Key => String::from("F7"), + F8Key => String::from("F8"), + F9Key => String::from("F9"), + F10Key => String::from("F10"), + F11Key => String::from("F11"), + F12Key => String::from("F12"), + F13Key => String::from("F13"), + F14Key => String::from("F14"), + F15Key => String::from("F15"), + F16Key => String::from("F16"), + F17Key => String::from("F17"), + F18Key => String::from("F18"), + F19Key => String::from("F19"), + F20Key => String::from("F20"), + F21Key => String::from("F21"), + F22Key => String::from("F22"), + F23Key => String::from("F23"), + F24Key => String::from("F24"), + NumLockKey => String::from("Num\nLock"), + ScrollLockKey => String::from("Scroll\nLock"), + CapsLockKey => String::from("CAPS"), + LShiftKey => String::from("SHIFT"), + RShiftKey => String::from("SHIFT"), + LControlKey => String::from("CTRL"), + RControlKey => String::from("CTRL"), + LAltKey => String::from("ALT"), + RAltKey => String::from("ALT"), + BrowserBackKey => String::from("๐Ÿกจ"), + BrowserForwardKey => String::from("๐Ÿกช"), + BrowserRefreshKey => String::from("โŸณ"), + VolumeMuteKey => String::from("๐Ÿ”Š"), + VolumeDownKey => String::from("๐Ÿ”‰"), + VolumeUpKey => String::from("๐Ÿ”ˆ+"), + MediaNextTrackKey => String::from("โญ"), + MediaPrevTrackKey => String::from("โฎ"), + MediaStopKey => String::from("โน "), + MediaPlayPauseKey => String::from("โต"), + BackquoteKey => String::from("`"), + SlashKey => String::from("/"), + BackslashKey => String::from("\\"), + CommaKey => String::from(","), + PeriodKey => String::from("."), + MinusKey => String::from("-"), + QuoteKey => String::from("\""), + SemicolonKey => String::from(";"), + LBracketKey => String::from("("), + RBracketKey => String::from("),"), + EqualKey => String::from("="), + _ => panic!("invalid keycode!"), + } +} + +pub fn mousebutton_from_config(button: &str) -> MouseButton { + match button { + "LeftButton" => LeftButton, + "MiddleButton" => MiddleButton, + "RightButton" => RightButton, + _ => { + panic!("invalid mouse button! (InputBot hasn't implemented it or your config is wrong)") + } + } +} + +fn string_from_mousebutton(button: MouseButton) -> String { + match button { + LeftButton => String::from("M1"), + MiddleButton => String::from("M3"), + RightButton => String::from("M2"), + _ => { + panic!("invalid mouse button! (InputBot hasn't implemented it or your config is wrong)") + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7924037 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,27 @@ +use inputbot::{get_keybd_key, KeybdKey, KeybdKeyIter, MouseButton}; + +mod config; +mod listen; + +fn main() -> Result<(), config::ConfigError> { + println!("Hello, world!"); + let config_dir = dirs::config_dir().expect("Config directory not found"); + let keydisplay_dir = config_dir.join("keydisplay"); + + let config = config::Config::load_or_create(keydisplay_dir)?; + + let mut config_to_keys: Vec = Vec::new(); + for key in config.listen.keys { + // rust is magic ๐Ÿ˜ ๐Ÿฆ€ + config_to_keys.push(listen::keybdkey_from_config(&key)); + } + + let mut config_to_mouse: Vec = Vec::new(); + for button in config.listen.mouse { + config_to_mouse.push(listen::mousebutton_from_config(&button)); + } + + listen::listen(config_to_keys, config_to_mouse); + + Ok(()) +}