Skip to content

Commit

Permalink
Reuse the CGEvent between send keys
Browse files Browse the repository at this point in the history
Previously, we create a new CGEvent for every keystroke, this
can be a huge waste. This commit tries to cache the events
between sends to improve the typing performance.

Lot's of unsafe stuff here but that's the trade off, i guess.
  • Loading branch information
huytd committed Jan 14, 2023
1 parent f687504 commit 95fbc7e
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 14 deletions.
212 changes: 212 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Expand Up @@ -8,5 +8,8 @@ edition = "2021"
[dependencies]
core-foundation = "0.9.3"
core-graphics = "0.22.3"
env_logger = "0.10.0"
lazy_static = "1.4.0"
libc = "0.2.139"
log = "0.4.17"
vi = { git = "https://github.com/ZeroX-DG/vi-rs" }
6 changes: 4 additions & 2 deletions src/main.rs
@@ -1,8 +1,8 @@
mod platform;

use std::{cmp::Ordering, sync::Mutex};

use platform::{send_backspace, send_string, run_event_listener, KEY_ENTER, KEY_TAB, KEY_SPACE, KEY_ESCAPE, KEY_DELETE};
use log::debug;

static mut TYPING_BUF: Mutex<Vec<char>> = Mutex::new(vec![]);

Expand All @@ -25,8 +25,9 @@ fn event_handler(keycode: char, shift: bool) -> bool {
if ['a', 'e', 'o', 'd', 's', 't', 'j', 'f', 'x', 'r', 'w'].contains(&keycode) {
let ret = vi::telex::transform_buffer(typing_buf.as_slice());
if ret.chars().cmp(typing_buf.clone().into_iter()) != Ordering::Equal {
// println!("BUF {:?} - RET {:?}", typing_buf, ret);
debug!("BUF {:?} - RET {:?}", typing_buf, ret);
let backspace_count = typing_buf.len();
debug!(" DEL {} - SEND {}", backspace_count, ret);
_ = send_backspace(backspace_count);
_ = send_string(&ret);
*typing_buf = ret.chars().collect();
Expand All @@ -38,5 +39,6 @@ fn event_handler(keycode: char, shift: bool) -> bool {
}

fn main() {
env_logger::init();
run_event_listener(&event_handler);
}
33 changes: 21 additions & 12 deletions src/platform/macos.rs
@@ -1,8 +1,24 @@
use core_foundation::runloop::{CFRunLoop, kCFRunLoopCommonModes};
use core_graphics::{event::{EventField, CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType, KeyCode, CGKeyCode, CGEventFlags, CGEvent}, event_source::{CGEventSource, self}};

use lazy_static::lazy_static;
use super::{CallbackFn, KEY_ENTER, KEY_SPACE, KEY_TAB, KEY_DELETE, KEY_ESCAPE};

struct SharedBox<T>(T);
unsafe impl<T> Send for SharedBox<T> {}
unsafe impl<T> Sync for SharedBox<T> {}
impl<T> SharedBox<T> {
unsafe fn new(v: T) -> Self {
Self(v)
}
}

lazy_static! {
static ref EVENT_SOURCE: SharedBox<CGEventSource> = unsafe { SharedBox::new(CGEventSource::new(event_source::CGEventSourceStateID::Private).unwrap()) };
static ref BACKSPACE_DOWN: SharedBox<CGEvent> = unsafe { SharedBox::new(CGEvent::new_keyboard_event(EVENT_SOURCE.0.clone(), KeyCode::DELETE, true).unwrap()) };
static ref BACKSPACE_UP: SharedBox<CGEvent> = unsafe { SharedBox::new(CGEvent::new_keyboard_event(EVENT_SOURCE.0.clone(), KeyCode::DELETE, false).unwrap()) };
static ref SENDKEY: SharedBox<CGEvent> = unsafe { SharedBox::new(CGEvent::new_keyboard_event(EVENT_SOURCE.0.clone(), 0, true).unwrap()) };
}

// Modified from http://ritter.ist.psu.edu/projects/RUI/macosx/rui.c
fn get_char(keycode: CGKeyCode) -> Option<char> {
match keycode {
Expand Down Expand Up @@ -52,23 +68,16 @@ fn get_char(keycode: CGKeyCode) -> Option<char> {
}

pub fn send_backspace(count: usize) -> Result<(), ()> {
let source = CGEventSource::new(event_source::CGEventSourceStateID::Private)?;
let backspace_down = CGEvent::new_keyboard_event(source.clone(), KeyCode::DELETE, true)?;
let backspace_up = CGEvent::new_keyboard_event(source.clone(), KeyCode::DELETE, false)?;

for _ in 0..count {
backspace_down.post(CGEventTapLocation::HID);
backspace_up.post(CGEventTapLocation::HID);
BACKSPACE_DOWN.0.post(CGEventTapLocation::HID);
BACKSPACE_UP.0.post(CGEventTapLocation::HID);
}

Ok(())
}

pub fn send_string(string: &str) -> Result<(), ()> {
let source = CGEventSource::new(event_source::CGEventSourceStateID::Private)?;
let event = CGEvent::new_keyboard_event(source, 0, true)?;
event.set_string(string);
event.post(CGEventTapLocation::HID);
SENDKEY.0.set_string(string);
SENDKEY.0.post(CGEventTapLocation::HID);
Ok(())
}

Expand Down

0 comments on commit 95fbc7e

Please sign in to comment.