Skip to content

Commit

Permalink
Optimize keyboard events
Browse files Browse the repository at this point in the history
  • Loading branch information
aurexav committed Jul 8, 2024
1 parent 2b8dc05 commit e57d86c
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 141 deletions.
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ eframe = { version = "0.28", features = ["persistence"] }
egui_extras = { version = "0.28", features = ["svg"] }
enigo = { version = "0.2" }
futures = { version = "0.3" }
global-hotkey = { version = "0.5", features = ["serde"] }
global-hotkey = { version = "0.5" }
parking_lot = { version = "0.12" }
reqwew = { version = "0.2" }
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -49,9 +49,6 @@ tracing-appender = { version = "0.2" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# llm_utils = { version = "0.0.6", optional = true }

[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
xkeysym = { version = "0.2" }

[target.'cfg(target_os = "macos")'.dependencies]
objc2-app-kit = { version = "0.2", features = ["NSApplication", "NSResponder", "NSRunningApplication", "NSWindow"] }
objc2-foundation = { version = "0.2" }
Expand Down
19 changes: 18 additions & 1 deletion src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@ use setting::Setting;

pub mod util;

// std
use std::fmt::{Debug, Formatter, Result as FmtResult};
// crates.io
use arboard::Clipboard;
// self
use crate::prelude::*;

#[derive(Debug)]
pub struct Components {
pub clipboard: Clipboard,
pub setting: Setting,
#[cfg(feature = "tokenizer")]
pub tokenizer: Tokenizer,
}
impl Components {
pub fn new() -> Result<Self> {
let clipboard = Clipboard::new()?;
let setting = Setting::load()?;

// TODO: https://github.com/emilk/egui/discussions/4670.
Expand All @@ -36,9 +41,21 @@ impl Components {
let tokenizer = Tokenizer::new(setting.ai.model.as_str());

Ok(Self {
clipboard,
setting,
#[cfg(feature = "tokenizer")]
tokenizer,
})
}
}
impl Debug for Components {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let mut s = f.debug_struct("Components");

s.field("clipboard", &"..").field("setting", &self.setting);
#[cfg(feature = "tokenizer")]
s.field("tokenizer", &self.tokenizer);

s.finish()
}
}
109 changes: 100 additions & 9 deletions src/component/keyboard.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// std
use std::str::FromStr;
// crates.io
use enigo::{Direction, Enigo, Key, Keyboard as _, Settings};
#[cfg(all(unix, not(target_os = "macos")))] use xkeysym::key::c;
// self
use crate::prelude::*;

Expand All @@ -12,14 +13,19 @@ impl Keyboard {
}

pub fn copy(&mut self) -> Result<()> {
self.0.key(Key::Meta, Direction::Press).map_err(EnigoError::Input)?;
// TODO: create a `CGKeyCode` table for macOS in `build.rs`.
#[cfg(target_os = "macos")]
self.0.key(Key::Other(0x08), Direction::Click).map_err(EnigoError::Input)?;
// TODO: Windows.
#[cfg(all(unix, not(target_os = "macos")))]
self.0.key(Key::Other(c), Direction::Click).map_err(EnigoError::Input)?;
self.0.key(Key::Meta, Direction::Release).map_err(EnigoError::Input)?;
let modifier = if cfg!(target_os = "macos") { Key::Meta } else { Key::Control };

self.0.key(modifier, Direction::Press).map_err(EnigoError::Input)?;
self.0.key(key_of('C')?, Direction::Click).map_err(EnigoError::Input)?;
self.0.key(modifier, Direction::Release).map_err(EnigoError::Input)?;

Ok(())
}

pub fn release_keys(&mut self, keys: Keys) -> Result<()> {
for k in keys.0 {
self.0.key(k, Direction::Release).map_err(EnigoError::Input)?;
}

Ok(())
}
Expand All @@ -28,3 +34,88 @@ impl Keyboard {
Ok(self.0.text(text).map_err(EnigoError::Input)?)
}
}

#[derive(Clone, Debug)]
pub struct Keys(pub Vec<Key>);
impl FromStr for Keys {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut keys = Vec::new();

for k in s.to_uppercase().split('+') {
let k = match k {
"CTRL" | "CONTROL" => Key::Control,
"SHIFT" => Key::Shift,
"ALT" => Key::Alt,
"COMMAND" | "META" | "SUPER" => Key::Meta,
k if k.len() == 1 => key_of(k.chars().next().expect("`k` must be `char`"))?,
k => return Err(Error::UnsupportedKey(k.to_owned())),
};

keys.push(k);
}

Ok(Self(keys))
}
}

fn key_of(key: char) -> Result<Key> {
// TODO: create a `CGKeyCode` table for macOS in `build.rs`.
// Currently, we only support limited keys on macOS.
#[cfg(target_os = "macos")]
let k = Key::Other(match key {
'A' => 0,
'S' => 1,
'D' => 2,
'F' => 3,
'H' => 4,
'G' => 5,
'Z' => 6,
'X' => 7,
'C' => 8,
'V' => 9,
'B' => 11,
'Q' => 12,
'W' => 13,
'E' => 14,
'R' => 15,
'Y' => 16,
'T' => 17,
'1' => 18,
'2' => 19,
'3' => 20,
'4' => 21,
'6' => 22,
'5' => 23,
'=' => 24,
'9' => 25,
'7' => 26,
'-' => 27,
'8' => 28,
'0' => 29,
']' => 30,
'O' => 31,
'U' => 32,
'[' => 33,
'I' => 34,
'P' => 35,
'L' => 37,
'J' => 38,
'\'' => 39,
'K' => 40,
';' => 41,
'\\' => 42,
',' => 43,
'/' => 44,
'N' => 45,
'M' => 46,
'.' => 47,
'`' => 50,
_ => return Err(Error::UnsupportedKey(key.to_string())),
});
#[cfg(not(target_os = "macos"))]
let k = Key::Unicode(key);

Ok(k)
}
26 changes: 14 additions & 12 deletions src/component/setting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::{borrow::Cow, fs, path::PathBuf};
// crates.io
use app_dirs2::AppDataType;
use async_openai::config::OPENAI_API_BASE;
use global_hotkey::hotkey::{Code, HotKey, Modifiers};
use serde::{Deserialize, Serialize};
// self
use super::openai::Model;
Expand Down Expand Up @@ -101,7 +100,8 @@ impl Default for Rewrite {
fn default() -> Self {
Self {
prompt: "As language professor, assist me in refining this text. \
Amend any grammatical errors and enhance the language to sound more like a native speaker.\
Amend any grammatical errors, \
enhance the language to sound more like a native speaker and keep the origin format. \
Just provide the refined text only, without any other things:"
.into(),
}
Expand All @@ -127,8 +127,10 @@ impl Translation {
impl Default for Translation {
fn default() -> Self {
Self {
prompt: "As a language professor, amend any grammatical errors and enhance the language to sound more like a native speaker. \
Provide the translated text only, without any other things:".into(),
prompt: "As a language professor, amend any grammatical errors, \
enhance the language to sound more like a native speaker and keep the origin format. \
Provide the translated text only, without any other things:"
.into(),
a: Language::ZhCn,
b: Language::EnGb,
}
Expand All @@ -147,18 +149,18 @@ pub enum Language {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Hotkeys {
pub rewrite: HotKey,
pub rewrite_directly: HotKey,
pub translate: HotKey,
pub translate_directly: HotKey,
pub rewrite: String,
pub rewrite_directly: String,
pub translate: String,
pub translate_directly: String,
}
impl Default for Hotkeys {
fn default() -> Self {
Self {
rewrite: HotKey::new(Some(Modifiers::CONTROL), Code::KeyT),
rewrite_directly: HotKey::new(Some(Modifiers::CONTROL), Code::KeyY),
translate: HotKey::new(Some(Modifiers::CONTROL), Code::KeyU),
translate_directly: HotKey::new(Some(Modifiers::CONTROL), Code::KeyI),
rewrite: "Ctrl+Y".into(),
rewrite_directly: "Ctrl+U".into(),
translate: "Ctrl+I".into(),
translate_directly: "Ctrl+O".into(),
}
}
}
14 changes: 12 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ pub enum Error {
#[error(transparent)]
Eframe(#[from] eframe::Error),
#[error(transparent)]
GlobalHotKey(#[from] global_hotkey::Error),
#[error(transparent)]
OpenAi(#[from] async_openai::error::OpenAIError),
#[error(transparent)]
Reqwew(#[from] reqwew::error::Error),
Expand All @@ -22,6 +20,10 @@ pub enum Error {

#[error(transparent)]
Enigo(#[from] EnigoError),
#[error(transparent)]
GlobalHotKey(#[from] GlobalHotKeyError),
#[error("unsupported key: {0}")]
UnsupportedKey(String)
}

#[derive(Debug, thiserror::Error)]
Expand All @@ -31,3 +33,11 @@ pub enum EnigoError {
#[error(transparent)]
NewCon(#[from] enigo::NewConError),
}

#[derive(Debug, thiserror::Error)]
pub enum GlobalHotKeyError {
#[error(transparent)]
Main(#[from] global_hotkey::Error),
#[error(transparent)]
Parse(#[from] global_hotkey::hotkey::HotKeyParseError),
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod state;
mod ui;

mod prelude {
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T, E = Error> = std::result::Result<T, E>;

pub use crate::error::*;
}
Expand Down
13 changes: 3 additions & 10 deletions src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,10 @@ impl Services {
let rt = Runtime::new()?;
let quoter = Quoter::new(&rt, state.chat.quote.clone());
let is_chatting = Arc::new(AtomicBool::new(false));
let chat = Chat::new(
keyboard.clone(),
&rt,
is_chatting.clone(),
components.setting.ai.clone(),
components.setting.chat.clone(),
state.chat.input.clone(),
state.chat.output.clone(),
);
let chat =
Chat::new(keyboard.clone(), &rt, is_chatting.clone(), &components.setting, &state.chat);
let hotkey =
Hotkey::new(ctx, keyboard.clone(), &rt, &components.setting.hotkeys, chat.tx.clone())?;
Hotkey::new(ctx, keyboard.clone(), &components.setting.hotkeys, chat.tx.clone())?;

Ok(Self { keyboard, rt: Some(rt), quoter, is_chatting, chat, hotkey })
}
Expand Down
Loading

0 comments on commit e57d86c

Please sign in to comment.