From c2cc52e3915b1aa8900e123c3a0c5709137f0c8e Mon Sep 17 00:00:00 2001 From: Nicole Tietz-Sokolskaya Date: Thu, 6 Mar 2025 23:02:06 -0500 Subject: [PATCH] get it typing! --- src/bin/main.rs | 77 ++++++++++++++++----------- src/typing.rs | 138 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 137 insertions(+), 78 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 64d44df..e6a5045 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -4,12 +4,14 @@ use std::{ }; use anyhow::{anyhow, Result}; -use enigo::{Enigo, Key, Keyboard, Settings}; +use enigo::{Direction, Enigo, Key, Keyboard, Settings}; use midi_keys::{ log::load_raw_log, midi::daemon::Category, parser::parse_message, - typing::{base_values, encode, midi_to_scale_degree, TypingState}, + typing::{ + base_values, encode, midi_to_scale_degree, Keystroke, Modifier, Special, TypingState, + }, ui::{display_state_daemon, DisplayQueues, DisplayState}, }; use midir::{MidiInput, MidiInputPort}; @@ -37,12 +39,17 @@ fn main() { println!("started daemon"); for c in "hello, world".chars() { - let v = encode(c).unwrap(); - println!("{c}: {:?} - {:?}", v, base_values(v, 7, 3)); + let v = encode(Keystroke::Char(c)).unwrap(); + println!("{c}: {:?} - {:?}", v, base_values(v, 7, 2)); } + for c in "Hello, WORLD!".chars() { - let v = encode(c).unwrap(); - println!("{c}: {:?} - {:?}", v, base_values(v, 7, 3)); + if c.is_uppercase() { + let v = encode(Keystroke::Modifier(Modifier::Shift)).unwrap(); + println!("{c}: {:?} - {:?}", v, base_values(v, 7, 2)); + } + let v = encode(Keystroke::Char(c.to_ascii_lowercase())).unwrap(); + println!("{c}: {:?} - {:?}", v, base_values(v, 7, 2)); } //std::thread::spawn(move || loop { @@ -57,6 +64,7 @@ fn main() { let mut enigo = Enigo::new(&Settings::default()).unwrap(); let mut current_note = None; + let mut shift = false; loop { match ws_recv.recv() { @@ -87,7 +95,7 @@ fn main() { None => { current_note = None; continue; - }, + } }; steps += 1; @@ -96,17 +104,33 @@ fn main() { println!("here: {steps}, {scale_degree}, {current_note:?}, {m:?}"); if steps % 2 == 0 { - if velocity > 20 { - state.set_bits(64); - } - let (b, c) = state.emit(); println!("got {b}, {c:?}"); if let Some(c) = c { - enigo.text(&format!("{c}")).unwrap(); - } else if b == 120 { - enigo.key(Key::Backspace, enigo::Direction::Click).unwrap(); + match c { + Keystroke::Char(c) => { + if shift { + println!("well that's a shifty character"); + let shifted_c = c.to_ascii_uppercase(); + enigo.text(&format!("{shifted_c}")).unwrap(); + shift = false; + } else { + println!("no shifty characters here"); + enigo.text(&format!("{c}")).unwrap(); + } + } + Keystroke::Modifier(Modifier::Shift) => { + println!("SHIFTY BEHAVIOR"); + shift = !shift; + } + Keystroke::Special(Special::CapsLock) => { + enigo.key(Key::CapsLock, Direction::Click).unwrap() + } + Keystroke::Special(Special::Backspace) => { + enigo.key(Key::Backspace, Direction::Click).unwrap() + } + }; } } } @@ -116,24 +140,6 @@ fn main() { } else if let Some(_) = note_off { current_note = None; } - - //if let Some((note, velocity)) = m.note_on_values() { - // if let Some(degree) = midi_to_scale_degree(60, note) { - // steps += 1; - // println!("got degree: {degree}, steps: {steps}, state: {state:?}"); - // state.enter(degree); - - // if steps % 3 == 0 { - // let (b, c) = state.emit(); - // println!("got {b}, {c:?}"); - // if let Some(c) = c { - // enigo.text(&format!("{c}")).unwrap(); - // } else if b == 255 { - // enigo.key(Key::Backspace, enigo::Direction::Click).unwrap(); - // } - // } - // } - //} } Err(err) => println!("err(ws): {err:?}"), } @@ -149,6 +155,13 @@ fn main() { midi_keys::ui::run(state); } +pub fn press_modifier(keyboard: &mut Enigo, modifier: &Modifier, direction: Direction) { + match modifier { + Modifier::Shift => keyboard.key(Key::Shift, direction), + } + .unwrap(); +} + pub fn run() -> Result<()> { let midi_in = MidiInput::new("keyboard")?; diff --git a/src/typing.rs b/src/typing.rs index b5e02b8..02ceb8b 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,28 +1,31 @@ #[derive(Debug, Copy, Clone)] pub struct TypingState { - base: u8, - current: u8, + base: u32, + current: u32, } impl TypingState { - pub fn new(base: u8) -> TypingState { + pub fn new(base: u32) -> TypingState { TypingState { base, current: 0 } } /// Add the value into the current accumulator. pub fn enter(&mut self, value: u8) { - self.current = self.current.saturating_mul(self.base).saturating_add(value); + self.current = self + .current + .saturating_mul(self.base) + .saturating_add(value.into()); } - pub fn set_bits(&mut self, mask: u8) { + pub fn set_bits(&mut self, mask: u32) { self.current |= mask; } - /// Decode the current accumulator as a character, or None if it's invalid. + /// Decode the current accumulator as a keystroke, or None if it's invalid. /// This resets the accumulator to 0! - pub fn emit(&mut self) -> (u8, Option) { + pub fn emit(&mut self) -> (u32, Option) { let x = self.current; - let c = decode_alt(x); + let c = decode(x); self.current = 0; @@ -31,10 +34,11 @@ impl TypingState { } /// Chunks a u8 into a sequence of values in a given base. -pub fn base_values(mut x: u8, base: u8, len: usize) -> Vec { +pub fn base_values(mut x: u32, base: u32, len: usize) -> Vec { let mut vals = vec![]; for _ in 0..len { - vals.push(x % base); + let digit = x % (base as u32); + vals.push(digit as u8); x = x / base; } vals.reverse(); @@ -58,10 +62,66 @@ pub fn midi_to_scale_degree(root: u8, note: u8) -> Option { }) } +#[derive(Debug, PartialEq, Eq)] +pub enum Keystroke { + Char(char), + Modifier(Modifier), + Special(Special), +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Modifier { + Shift, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Special { + CapsLock, + Backspace, +} + /// Converts a character to its assigned number for use in text entry. This is -/// stores them as u8 to keep things simple and fixed length. If we want to -/// support the full Unicode space, we can have expand this to u32. -pub fn encode(c: char) -> Option { +/// stores them as u32 to keep things simple and fixed length. If we want to +/// support the full Unicode space, we must expand this to u32. +pub fn encode(key: Keystroke) -> Option { + Some(match key { + Keystroke::Modifier(Modifier::Shift) => 0, + Keystroke::Special(Special::CapsLock) => 1, + Keystroke::Special(Special::Backspace) => 2, + + Keystroke::Char(c @ 'a'..='z') => c as u32 - 'a' as u32 + 3, + Keystroke::Char(c @ '0'..='9') => c as u32 - '0' as u32 + 29, + Keystroke::Char('.') => 39, + Keystroke::Char(',') => 40, + Keystroke::Char('!') => 41, + Keystroke::Char(' ') => 42, + Keystroke::Char('\n') => 43, + + _ => return None, + }) +} + +/// Converts a number to its corresponding character for use in text entry. +/// If a character isn't implemented, it will return None. +pub fn decode(x: u32) -> Option { + Some(match x { + 0 => Keystroke::Modifier(Modifier::Shift), + 1 => Keystroke::Special(Special::CapsLock), + 2 => Keystroke::Special(Special::Backspace), + + 3..29 => Keystroke::Char(char::from_u32(x - 3 + ('a' as u32))?), + 29..39 => Keystroke::Char(char::from_u32(x - 29 + ('0' as u32))?), + 39 => Keystroke::Char('.'), + 40 => Keystroke::Char(','), + 41 => Keystroke::Char('!'), + 42 => Keystroke::Char(' '), + 43 => Keystroke::Char('\n'), + + _ => return None, + }) +} + +pub fn encode_old(c: char) -> Option { Some(match c { 'a'..='z' => c as u8 - 'a' as u8, 'A'..='Z' => c as u8 - 'A' as u8 + 26, @@ -76,7 +136,7 @@ pub fn encode(c: char) -> Option { /// Converts a number to its corresponding character for use in text entry. /// If a character isn't implemented, it will return None. -pub fn decode(x: u8) -> Option { +pub fn decode_old(x: u8) -> Option { let c = match x { 0..=25 => 'a' as u8 + x, 26..=51 => 'A' as u8 + x - 26, @@ -107,58 +167,44 @@ mod tests { #[test] fn encodes_lowercase() { - assert_eq!(encode('a'), Some(0)); - assert_eq!(encode('b'), Some(1)); - assert_eq!(encode('z'), Some(25)); - } - - #[test] - fn encodes_uppercase() { - assert_eq!(encode('A'), Some(26)); - assert_eq!(encode('B'), Some(27)); - assert_eq!(encode('Z'), Some(51)); + assert_eq!(encode(Keystroke::Char('a')), Some(3)); + assert_eq!(encode(Keystroke::Char('b')), Some(4)); + assert_eq!(encode(Keystroke::Char('z')), Some(28)); } #[test] fn encodes_numbers() { - assert_eq!(encode('0'), Some(52)); - assert_eq!(encode('1'), Some(53)); - assert_eq!(encode('9'), Some(61)); + assert_eq!(encode(Keystroke::Char('0')), Some(29)); + assert_eq!(encode(Keystroke::Char('1')), Some(30)); + assert_eq!(encode(Keystroke::Char('9')), Some(38)); } #[test] fn encodes_punctuation() { - assert_eq!(encode('.'), Some(62)); - assert_eq!(encode(','), Some(63)); - assert_eq!(encode('!'), Some(64)); + assert_eq!(encode(Keystroke::Char('.')), Some(39)); + assert_eq!(encode(Keystroke::Char(',')), Some(40)); + assert_eq!(encode(Keystroke::Char('!')), Some(41)); } #[test] fn decodes_lowercase() { - assert_eq!(decode(0), Some('a')); - assert_eq!(decode(1), Some('b')); - assert_eq!(decode(25), Some('z')); - } - - #[test] - fn decodes_uppercase() { - assert_eq!(decode(26), Some('A')); - assert_eq!(decode(27), Some('B')); - assert_eq!(decode(51), Some('Z')); + assert_eq!(decode(3), Some(Keystroke::Char('a'))); + assert_eq!(decode(4), Some(Keystroke::Char('b'))); + assert_eq!(decode(28), Some(Keystroke::Char('z'))); } #[test] fn decodes_numbers() { - assert_eq!(decode(52), Some('0')); - assert_eq!(decode(53), Some('1')); - assert_eq!(decode(61), Some('9')); + assert_eq!(decode(29), Some(Keystroke::Char('0'))); + assert_eq!(decode(30), Some(Keystroke::Char('1'))); + assert_eq!(decode(38), Some(Keystroke::Char('9'))); } #[test] fn decodes_punctuation() { - assert_eq!(decode(62), Some('.')); - assert_eq!(decode(63), Some(',')); - assert_eq!(decode(64), Some('!')); + assert_eq!(decode(39), Some(Keystroke::Char('.'))); + assert_eq!(decode(40), Some(Keystroke::Char(','))); + assert_eq!(decode(41), Some(Keystroke::Char('!'))); } #[test]