This commit is contained in:
starlight 2025-01-23 21:49:40 +13:00
parent 8a5f321f37
commit 4d58c95c73
6 changed files with 491 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

11
Cargo.toml Normal file
View File

@ -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"

View File

@ -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

95
src/config.rs Normal file
View File

@ -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<String>,
// mouse = ["LeftButton", "RightButton"]
pub mouse: Vec<String>,
}
// [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<Self, ConfigError> {
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(())
}
}

318
src/listen.rs Normal file
View File

@ -0,0 +1,318 @@
use std::str::FromStr;
use inputbot::{KeybdKey, KeybdKey::*, MouseButton, MouseButton::*};
pub fn listen(keys: Vec<KeybdKey>, m_buttons: Vec<MouseButton>) {
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)")
}
}
}

27
src/main.rs Normal file
View File

@ -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<KeybdKey> = 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<MouseButton> = 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(())
}