feat: implement Backspace-Replay pattern for perfect engine sync
- Add Engine::replay_keystrokes() — creates fresh engine and replays all keystrokes to compute correct screen output from scratch - Add Daemon::replay_and_inject() — tracks keystroke history and screen output, computes diff (backspaces + new text) on each keypress - Add Daemon::replay_backspace() — pops from history, replays, diffs - Handle flush chars (space, period, etc.) separately — commit word, type char, clear history - Add FocusIn/FocusOut detection for engine reset on focus loss - Add CPU pinning (P-cores 0-3) + nice(-10) priority boost - Clean up 9 dead code warnings (unused fields, constants, types) - Add replay_keystrokes tests for Telex, VNI, and backspace - 255 tests pass (was 252)
This commit is contained in:
parent
bb0847a38f
commit
3858aa955c
4 changed files with 338 additions and 39 deletions
|
|
@ -8,6 +8,31 @@ use std::time::Duration;
|
|||
|
||||
use vietc_engine::{Engine, EngineEvent, InputMethod};
|
||||
|
||||
/// Pin current thread to performance cores (0-3) and boost priority.
|
||||
/// Inspired by VMK's approach to minimize input latency on Intel hybrid CPUs.
|
||||
fn boost_thread_priority() {
|
||||
unsafe {
|
||||
// Set nice value to -10 (higher priority than normal)
|
||||
libc::setpriority(libc::PRIO_PROCESS, 0, -10);
|
||||
|
||||
// Try to pin to P-cores (cores 0-3 on Intel hybrid)
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let mut cpuset: libc::cpu_set_t = std::mem::zeroed();
|
||||
// Pin to cores 0-3 (P-cores on Intel 12th gen+)
|
||||
for i in 0..4 {
|
||||
libc::CPU_SET(i, &mut cpuset);
|
||||
}
|
||||
let ret = libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &cpuset);
|
||||
if ret == 0 {
|
||||
eprintln!("[vietc] Pinned to P-cores 0-3, nice=-10");
|
||||
} else {
|
||||
eprintln!("[vietc] CPU pinning failed ({}), nice=-10 still set", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod app_state;
|
||||
mod config;
|
||||
mod display;
|
||||
|
|
@ -84,6 +109,13 @@ struct Daemon {
|
|||
app_state: AppStateManager,
|
||||
engine_enabled: Arc<AtomicBool>,
|
||||
grab_enabled: bool,
|
||||
/// Backspace-Replay: all keystrokes in the current word being composed.
|
||||
/// On each keypress, we replay the entire history through a fresh engine
|
||||
/// to compute the correct screen output, eliminating state desync.
|
||||
keystroke_history: Vec<char>,
|
||||
/// What's currently displayed on screen for the current word.
|
||||
/// Used to calculate how many backspaces we need before retyping.
|
||||
screen_output: String,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
|
|
@ -120,6 +152,8 @@ impl Daemon {
|
|||
config_modified,
|
||||
app_state,
|
||||
engine_enabled,
|
||||
keystroke_history: Vec::new(),
|
||||
screen_output: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -307,6 +341,130 @@ impl Daemon {
|
|||
self.app_state.is_current_app_bypassed()
|
||||
}
|
||||
|
||||
/// Backspace-Replay: replay the entire keystroke history through a fresh
|
||||
/// engine, compute what should be on screen, and return the commands
|
||||
/// (backspaces to erase old + new text to type).
|
||||
fn replay_and_inject(&mut self, ch: char) -> Vec<OutputCommand> {
|
||||
let mut commands = Vec::new();
|
||||
|
||||
// Flush characters: commit current word, type the character, clear state
|
||||
if is_flush_char(ch) {
|
||||
if !self.screen_output.is_empty() {
|
||||
let backspaces = self.screen_output.chars().count();
|
||||
commands.push(OutputCommand::Backspace(backspaces));
|
||||
commands.push(OutputCommand::Type(self.screen_output.clone()));
|
||||
}
|
||||
// Type the flush character itself
|
||||
commands.push(OutputCommand::Type(ch.to_string()));
|
||||
self.keystroke_history.clear();
|
||||
self.screen_output.clear();
|
||||
return commands;
|
||||
}
|
||||
|
||||
// Add the new keystroke to history
|
||||
self.keystroke_history.push(ch);
|
||||
|
||||
// Replay through fresh engine
|
||||
let method = match self.config.input_method.as_str() {
|
||||
"vni" => InputMethod::Vni,
|
||||
_ => InputMethod::Telex,
|
||||
};
|
||||
let (new_output, did_flush) = Engine::replay_keystrokes(
|
||||
method,
|
||||
&self.config.macros,
|
||||
&self.keystroke_history,
|
||||
);
|
||||
|
||||
log_info(&format!(
|
||||
"[vietc] replay: history_len={} old_screen='{}' new_output='{}' flush={}",
|
||||
self.keystroke_history.len(),
|
||||
self.screen_output,
|
||||
new_output,
|
||||
did_flush
|
||||
));
|
||||
|
||||
if did_flush {
|
||||
// Engine flushed a word — commit it and clear state
|
||||
// The flush char (space/period/etc) was NOT in history, so we need to
|
||||
// type whatever was on screen + the flush char
|
||||
if !self.screen_output.is_empty() {
|
||||
let backspaces = self.screen_output.chars().count();
|
||||
commands.push(OutputCommand::Backspace(backspaces));
|
||||
commands.push(OutputCommand::Type(self.screen_output.clone()));
|
||||
}
|
||||
self.keystroke_history.clear();
|
||||
self.screen_output.clear();
|
||||
return commands;
|
||||
}
|
||||
|
||||
if new_output != self.screen_output {
|
||||
let backspaces = self.screen_output.chars().count();
|
||||
if backspaces > 0 {
|
||||
commands.push(OutputCommand::Backspace(backspaces));
|
||||
}
|
||||
if !new_output.is_empty() {
|
||||
commands.push(OutputCommand::Type(new_output.clone()));
|
||||
}
|
||||
self.screen_output = new_output;
|
||||
}
|
||||
|
||||
commands
|
||||
}
|
||||
|
||||
/// Backspace-Replay: pop from history, replay, and return commands to fix screen.
|
||||
fn replay_backspace(&mut self) -> Vec<OutputCommand> {
|
||||
let mut commands = Vec::new();
|
||||
|
||||
if self.keystroke_history.is_empty() {
|
||||
// Nothing in history — just forward the backspace
|
||||
commands.push(OutputCommand::Backspace(1));
|
||||
return commands;
|
||||
}
|
||||
|
||||
// Remove last keystroke from history
|
||||
self.keystroke_history.pop();
|
||||
|
||||
// Replay through fresh engine
|
||||
let method = match self.config.input_method.as_str() {
|
||||
"vni" => InputMethod::Vni,
|
||||
_ => InputMethod::Telex,
|
||||
};
|
||||
let (new_output, _) = if self.keystroke_history.is_empty() {
|
||||
(String::new(), false)
|
||||
} else {
|
||||
Engine::replay_keystrokes(
|
||||
method,
|
||||
&self.config.macros,
|
||||
&self.keystroke_history,
|
||||
)
|
||||
};
|
||||
|
||||
log_info(&format!(
|
||||
"[vietc] replay_backspace: history_len={} old_screen='{}' new_output='{}'",
|
||||
self.keystroke_history.len(),
|
||||
self.screen_output,
|
||||
new_output
|
||||
));
|
||||
|
||||
// Calculate diff
|
||||
let backspaces = self.screen_output.chars().count();
|
||||
if backspaces > 0 {
|
||||
commands.push(OutputCommand::Backspace(backspaces));
|
||||
}
|
||||
if !new_output.is_empty() {
|
||||
commands.push(OutputCommand::Type(new_output.clone()));
|
||||
}
|
||||
self.screen_output = new_output;
|
||||
|
||||
commands
|
||||
}
|
||||
|
||||
/// Reset the replay state (on flush, focus loss, modifier key, etc.)
|
||||
fn replay_reset(&mut self) {
|
||||
self.keystroke_history.clear();
|
||||
self.screen_output.clear();
|
||||
}
|
||||
|
||||
fn check_app_change_with(&mut self, new_class: String) {
|
||||
if let Some(should_enable) = self.app_state.update_with_app(new_class) {
|
||||
self.engine.set_enabled(should_enable);
|
||||
|
|
@ -321,6 +479,11 @@ enum OutputCommand {
|
|||
Backspace(usize),
|
||||
}
|
||||
|
||||
/// Characters that flush the current word and start a new one.
|
||||
fn is_flush_char(ch: char) -> bool {
|
||||
matches!(ch, ' ' | '.' | ',' | '!' | '?' | ';' | ':' | '\t' | '\n')
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config_path = config::find_config_path();
|
||||
let config = Config::load()?;
|
||||
|
|
@ -353,6 +516,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
));
|
||||
|
||||
// Boost thread priority for low-latency input (VMK technique)
|
||||
boost_thread_priority();
|
||||
|
||||
// Spawn background monitor for active window, config changes, and status changes
|
||||
let shared_active_window = Arc::new(Mutex::new(String::new()));
|
||||
let config_changed = Arc::new(AtomicBool::new(false));
|
||||
|
|
@ -560,7 +726,7 @@ fn run_with_x11(
|
|||
if active_window != last_active_window {
|
||||
log_info(&format!("[vietc] Window changed: '{}' -> '{}'", last_active_window, active_window));
|
||||
last_active_window = active_window.clone();
|
||||
daemon.engine.reset();
|
||||
daemon.replay_reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -569,9 +735,17 @@ fn run_with_x11(
|
|||
daemon.check_app_change_with(active_window);
|
||||
}
|
||||
|
||||
// Reset on focus loss (VMK technique)
|
||||
if capture.focus_lost {
|
||||
eprintln!("[vietc] Focus lost — resetting engine state");
|
||||
daemon.replay_reset();
|
||||
pressed_keys.clear();
|
||||
capture.focus_lost = false;
|
||||
}
|
||||
|
||||
while let Some(event) = capture.next_event() {
|
||||
if event.pressed {
|
||||
// Skip autorepeat — key is already tracked as held
|
||||
// Skip autorepeat
|
||||
if !pressed_keys.insert(event.keycode) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -580,54 +754,53 @@ fn run_with_x11(
|
|||
if let Some(' ') = event.ch {
|
||||
if (event.state & 4) != 0 {
|
||||
pressed_keys.remove(&event.keycode);
|
||||
daemon.replay_reset();
|
||||
daemon.toggle();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Modifier or non-character key → forward press only
|
||||
// Modifier or non-character key → forward press only, reset replay
|
||||
if capture.is_modifier_pressed(event.state) || event.ch.is_none() {
|
||||
daemon.engine.reset();
|
||||
daemon.replay_reset();
|
||||
capture.without_grab(|| {
|
||||
let _ = injector.send_key_event(event.keycode as u16, 1);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Character key
|
||||
// Character key — use Backspace-Replay
|
||||
if let Some(ch) = event.ch {
|
||||
match ch {
|
||||
'\x08' => {
|
||||
daemon.engine.process_key('\x08');
|
||||
// Backspace: replay pattern pops from history
|
||||
let commands = daemon.replay_backspace();
|
||||
pressed_keys.remove(&event.keycode);
|
||||
capture.without_grab(|| {
|
||||
let _ = injector.send_backspace();
|
||||
execute_commands(&*injector, &commands, true);
|
||||
});
|
||||
// Keep in pressed_keys so release is forwarded
|
||||
// If history is empty and commands only had a bare backspace,
|
||||
// we need to actually send it
|
||||
if daemon.keystroke_history.is_empty() && commands.is_empty() {
|
||||
capture.without_grab(|| {
|
||||
let _ = injector.send_backspace();
|
||||
});
|
||||
}
|
||||
}
|
||||
'\n' => {
|
||||
pressed_keys.remove(&event.keycode);
|
||||
daemon.engine.reset();
|
||||
daemon.replay_reset();
|
||||
capture.without_grab(|| {
|
||||
let _ = injector.send_key_event(event.keycode as u16, 1);
|
||||
let _ = injector.send_key_event(event.keycode as u16, 0);
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
let commands = daemon.process_key(ch);
|
||||
if !commands.is_empty() {
|
||||
// Engine consumed the key; remove from tracking
|
||||
pressed_keys.remove(&event.keycode);
|
||||
capture.without_grab(|| {
|
||||
execute_commands(&*injector, &commands, true);
|
||||
});
|
||||
} else {
|
||||
// Engine started composing; forward press+release immediately
|
||||
pressed_keys.remove(&event.keycode);
|
||||
capture.without_grab(|| {
|
||||
let _ = injector.send_key_event(event.keycode as u16, 1);
|
||||
let _ = injector.send_key_event(event.keycode as u16, 0);
|
||||
});
|
||||
}
|
||||
let commands = daemon.replay_and_inject(ch);
|
||||
pressed_keys.remove(&event.keycode);
|
||||
capture.without_grab(|| {
|
||||
execute_commands(&*injector, &commands, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,72 @@ impl Engine {
|
|||
event
|
||||
}
|
||||
|
||||
/// Replay a sequence of keystrokes through a fresh engine and return the
|
||||
/// final screen output. This is the core of the Backspace-Replay pattern:
|
||||
/// instead of tracking incremental state, we always recompute from scratch.
|
||||
/// Returns (output_on_screen, did_flush).
|
||||
/// `did_flush` means the engine processed a word boundary and the cursor
|
||||
/// is now at a clean position — caller should clear keystroke history.
|
||||
pub fn replay_keystrokes(
|
||||
method: InputMethod,
|
||||
macros: &std::collections::HashMap<String, String>,
|
||||
keystrokes: &[char],
|
||||
) -> (String, bool) {
|
||||
let mut engine = Engine::new(method);
|
||||
for (shortcut, expansion) in macros {
|
||||
engine.add_macro(shortcut.clone(), expansion.clone());
|
||||
}
|
||||
|
||||
let mut last_output = String::new();
|
||||
let mut did_flush = false;
|
||||
|
||||
for &ch in keystrokes {
|
||||
if let Some(event) = engine.process_key(ch) {
|
||||
match event {
|
||||
EngineEvent::Replace { insert, .. } => {
|
||||
last_output = insert;
|
||||
}
|
||||
EngineEvent::Flush(_word) => {
|
||||
// Word was flushed. The flush char is NOT part of the word.
|
||||
// The word is committed; clear tracking for current composing.
|
||||
last_output.clear();
|
||||
did_flush = true;
|
||||
}
|
||||
EngineEvent::Insert(text) => {
|
||||
last_output = text;
|
||||
}
|
||||
EngineEvent::UndoTones { restored, .. } => {
|
||||
last_output = restored;
|
||||
}
|
||||
EngineEvent::Paste(text) => {
|
||||
last_output = text;
|
||||
}
|
||||
EngineEvent::AutoRestore(word) => {
|
||||
last_output = word;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Key consumed but no screen change — buffer is building
|
||||
let buf = engine.buffer().to_string();
|
||||
if !buf.is_empty() {
|
||||
last_output = buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the engine has a buffer that hasn't been flushed, that's on screen
|
||||
let buf = engine.buffer().to_string();
|
||||
if !buf.is_empty() {
|
||||
last_output = buf;
|
||||
did_flush = false; // Still composing
|
||||
} else if did_flush {
|
||||
// After flush, nothing is on screen for the composing word
|
||||
last_output.clear();
|
||||
}
|
||||
|
||||
(last_output, did_flush)
|
||||
}
|
||||
|
||||
/// Update buffer with pasted text for subsequent edit operations (delete/backspace)
|
||||
pub fn update_with_pasted_text(&mut self, text: &str) {
|
||||
self.raw_buffer.clear();
|
||||
|
|
@ -497,4 +563,63 @@ mod tests {
|
|||
panic!("Expected Replace event, got {:?}", event2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replay_keystrokes_telex() {
|
||||
let macros = std::collections::HashMap::new();
|
||||
|
||||
// Replay "chao" -> should produce "chao" (no tone yet)
|
||||
let (output, flush) = Engine::replay_keystrokes(
|
||||
InputMethod::Telex,
|
||||
¯os,
|
||||
&['c', 'h', 'a', 'o'],
|
||||
);
|
||||
assert_eq!(output, "chao");
|
||||
assert!(!flush);
|
||||
|
||||
// Replay "chaos" -> s adds acute accent: "cháo"
|
||||
let (output, flush) = Engine::replay_keystrokes(
|
||||
InputMethod::Telex,
|
||||
¯os,
|
||||
&['c', 'h', 'a', 'o', 's'],
|
||||
);
|
||||
assert_eq!(output, "cháo");
|
||||
assert!(!flush);
|
||||
|
||||
// Replay "chaof" -> f adds grave accent: "chào"
|
||||
let (output, flush) = Engine::replay_keystrokes(
|
||||
InputMethod::Telex,
|
||||
¯os,
|
||||
&['c', 'h', 'a', 'o', 'f'],
|
||||
);
|
||||
assert_eq!(output, "chào");
|
||||
assert!(!flush);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replay_keystrokes_backspace() {
|
||||
let macros = std::collections::HashMap::new();
|
||||
|
||||
// Replay "chaos" then backspace -> engine pops 'o' from "cháo" → "chá"
|
||||
let (output, _) = Engine::replay_keystrokes(
|
||||
InputMethod::Telex,
|
||||
¯os,
|
||||
&['c', 'h', 'a', 'o', 's', '\x08'],
|
||||
);
|
||||
assert_eq!(output, "chá");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replay_keystrokes_vni() {
|
||||
let macros = std::collections::HashMap::new();
|
||||
|
||||
// VNI: "chao1" → acute accent on last vowel
|
||||
let (output, _) = Engine::replay_keystrokes(
|
||||
InputMethod::Vni,
|
||||
¯os,
|
||||
&['c', 'h', 'a', 'o', '1'],
|
||||
);
|
||||
// Verify it produces accented output (engine applies tone to last vowel)
|
||||
assert!(output.contains('á') || output.contains('ó'), "Expected toned output, got: {}", output);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ use std::ffi::{c_char, c_int, c_void};
|
|||
|
||||
type Display = c_void;
|
||||
type Window = u64;
|
||||
type XID = u64;
|
||||
type Time = u64;
|
||||
|
||||
// X11 event types
|
||||
const KEY_PRESS: c_int = 2;
|
||||
const KEY_RELEASE: c_int = 3;
|
||||
const FOCUS_IN: c_int = 9;
|
||||
const FOCUS_OUT: c_int = 10;
|
||||
|
||||
// X11 modifier masks
|
||||
const SHIFT_MASK: c_int = 1;
|
||||
const CONTROL_MASK: c_int = 4;
|
||||
const MOD1_MASK: c_int = 8; // Alt
|
||||
const MOD4_MASK: c_int = 64; // Super/Win
|
||||
|
|
@ -33,7 +33,6 @@ struct X11Lib {
|
|||
x_ungrab_keyboard: unsafe extern "C" fn(*mut Display, Time) -> c_int,
|
||||
x_next_event: unsafe extern "C" fn(*mut Display, *mut XEvent),
|
||||
x_lookup_string: unsafe extern "C" fn(*mut XKeyEvent, *mut c_char, c_int, *mut KeySym, *mut c_int) -> c_int,
|
||||
x_keysym_to_keycode: unsafe extern "C" fn(*mut Display, KeySym) -> u32,
|
||||
x_utf8_lookup_string: Option<unsafe extern "C" fn(*mut XKeyEvent, *mut c_char, c_int, *mut KeySym, *mut c_int) -> c_int>,
|
||||
x_flush: unsafe extern "C" fn(*mut Display) -> c_int,
|
||||
}
|
||||
|
|
@ -69,7 +68,6 @@ impl X11Lib {
|
|||
let x_ungrab_keyboard = sym!("XUngrabKeyboard");
|
||||
let x_next_event = sym!("XNextEvent");
|
||||
let x_lookup_string = sym!("XLookupString");
|
||||
let x_keysym_to_keycode = sym!("XKeysymToKeycode");
|
||||
let x_utf8_lookup_string = dlsym(handle, b"Xutf8LookupString\0".as_ptr() as *const c_char);
|
||||
let x_utf8_lookup_string = if x_utf8_lookup_string.is_null() {
|
||||
None
|
||||
|
|
@ -87,7 +85,6 @@ impl X11Lib {
|
|||
x_ungrab_keyboard,
|
||||
x_next_event,
|
||||
x_lookup_string,
|
||||
x_keysym_to_keycode,
|
||||
x_utf8_lookup_string,
|
||||
x_flush,
|
||||
})
|
||||
|
|
@ -149,7 +146,8 @@ pub struct X11Capture {
|
|||
display: *mut Display,
|
||||
root: Window,
|
||||
grabbed: bool,
|
||||
event_buf: Vec<u8>,
|
||||
/// Set to true when FocusOut is received — caller should reset engine state
|
||||
pub focus_lost: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for X11Capture {}
|
||||
|
|
@ -178,7 +176,7 @@ impl X11Capture {
|
|||
display,
|
||||
root,
|
||||
grabbed: false,
|
||||
event_buf: Vec::new(),
|
||||
focus_lost: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -225,6 +223,17 @@ impl X11Capture {
|
|||
}
|
||||
|
||||
let _type = event._type;
|
||||
|
||||
// Handle FocusIn/FocusOut — reset engine state when focus changes
|
||||
if _type == FOCUS_OUT {
|
||||
self.focus_lost = true;
|
||||
return self.next_event();
|
||||
}
|
||||
if _type == FOCUS_IN {
|
||||
self.focus_lost = false;
|
||||
return self.next_event();
|
||||
}
|
||||
|
||||
if _type != KEY_PRESS && _type != KEY_RELEASE {
|
||||
return self.next_event();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ extern "C" {
|
|||
const CURRENT_TIME: Time = 0;
|
||||
const PROP_MODE_REPLACE: c_int = 0;
|
||||
const NO_EVENT_MASK: i64 = 0;
|
||||
const INPUT_OUTPUT: c_int = 1;
|
||||
const COPY_FROM_PARENT: Window = 0;
|
||||
|
||||
const SELECTION_REQUEST: c_int = 30;
|
||||
|
|
@ -34,7 +33,6 @@ struct X11Lib {
|
|||
x_intern_atom: unsafe extern "C" fn(*mut Display, *const c_char, c_int) -> Atom,
|
||||
x_set_selection_owner: unsafe extern "C" fn(*mut Display, Atom, Window, Time) -> c_int,
|
||||
x_change_property: unsafe extern "C" fn(*mut Display, Window, Atom, Atom, c_int, c_int, *const c_void, c_int) -> c_int,
|
||||
x_get_selection_owner: unsafe extern "C" fn(*mut Display, Atom) -> Window,
|
||||
x_send_event: unsafe extern "C" fn(*mut Display, Window, c_int, i64, *const c_void) -> c_int,
|
||||
x_create_simple_window: unsafe extern "C" fn(*mut Display, Window, c_int, c_int, c_int, c_int, c_int, Atom, Atom) -> Window,
|
||||
x_map_window: unsafe extern "C" fn(*mut Display, Window) -> c_int,
|
||||
|
|
@ -90,7 +88,6 @@ impl X11Lib {
|
|||
let x_intern_atom = sym!(x11_handle, "XInternAtom");
|
||||
let x_set_selection_owner = sym!(x11_handle, "XSetSelectionOwner");
|
||||
let x_change_property = sym!(x11_handle, "XChangeProperty");
|
||||
let x_get_selection_owner = sym!(x11_handle, "XGetSelectionOwner");
|
||||
let x_send_event = sym!(x11_handle, "XSendEvent");
|
||||
let x_create_simple_window = sym!(x11_handle, "XCreateSimpleWindow");
|
||||
let x_map_window = sym!(x11_handle, "XMapWindow");
|
||||
|
|
@ -110,7 +107,6 @@ impl X11Lib {
|
|||
x_intern_atom,
|
||||
x_set_selection_owner,
|
||||
x_change_property,
|
||||
x_get_selection_owner,
|
||||
x_send_event,
|
||||
x_create_simple_window,
|
||||
x_map_window,
|
||||
|
|
@ -131,8 +127,6 @@ impl Drop for X11Lib {
|
|||
}
|
||||
}
|
||||
|
||||
const X11_KEYCODE_OFFSET: u32 = 8;
|
||||
|
||||
fn char_to_keycode(ch: char) -> Option<(u32, bool)> {
|
||||
match ch {
|
||||
'a' => Some((30, false)),
|
||||
|
|
@ -215,7 +209,6 @@ struct XEvent {
|
|||
pub struct X11Injector {
|
||||
lib: X11Lib,
|
||||
display: *mut Display,
|
||||
root: Window,
|
||||
clipboard_window: Window,
|
||||
atom_clipboard: Atom,
|
||||
atom_utf8: Atom,
|
||||
|
|
@ -251,7 +244,6 @@ impl X11Injector {
|
|||
Ok(Self {
|
||||
lib,
|
||||
display,
|
||||
root,
|
||||
clipboard_window,
|
||||
atom_clipboard,
|
||||
atom_utf8,
|
||||
|
|
|
|||
Loading…
Reference in a new issue