#[cfg(not(target_os = "macos"))]
compile_error!("typechar only works on macOS — it types characters via CoreGraphics events");
use std::thread::sleep;
use std::time::Duration;
use core_graphics::event::{CGEvent, CGEventTapLocation};
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
use typechar::{Action, parse_args, utf16_chunks};
const MAX_UNITS_PER_EVENT: usize = 20;
unsafe extern "C" {
fn CGPreflightPostEventAccess() -> bool;
fn CGRequestPostEventAccess() -> bool;
}
const USAGE: &str = "\
typechar — type any Unicode string, regardless of keyboard layout
Usage:
typechar [OPTIONS] <string>
typechar [OPTIONS] --unicode <hex> [--unicode <hex>...]
Options:
-u, --unicode <hex> Codepoint to type, e.g. 20ac or U+20AC (repeatable)
-d, --delay <ms> Wait before typing (for apps that drop fast events)
-h, --help Show this help
-V, --version Show version
Examples:
typechar €
typechar --unicode 20ac
typechar --delay 100 '¯\\_(ツ)_/¯'";
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
match parse_args(&args) {
Ok(Action::Help) => println!("{USAGE}"),
Ok(Action::Version) => println!("typechar {}", env!("CARGO_PKG_VERSION")),
Ok(Action::Type { text, delay_ms }) => {
if delay_ms > 0 {
sleep(Duration::from_millis(delay_ms));
}
if let Err(msg) = type_text(&text) {
eprintln!("typechar: {msg}");
std::process::exit(1);
}
}
Err(msg) => {
eprintln!("typechar: {msg}\n\n{USAGE}");
std::process::exit(2);
}
}
}
fn type_text(text: &str) -> Result<(), String> {
if !unsafe { CGPreflightPostEventAccess() } {
unsafe { CGRequestPostEventAccess() };
return Err("no permission to post keyboard events.\n\
Grant Accessibility access to the app that runs typechar (your terminal, \
Karabiner, skhd, ...) in System Settings → Privacy & Security → Accessibility, \
then try again."
.into());
}
let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState)
.map_err(|_| "failed to create event source".to_string())?;
for chunk in utf16_chunks(text, MAX_UNITS_PER_EVENT) {
for key_down in [true, false] {
let event = CGEvent::new_keyboard_event(source.clone(), 0, key_down)
.map_err(|_| "failed to create keyboard event".to_string())?;
event.set_string_from_utf16_unchecked(&chunk);
event.post(CGEventTapLocation::HID);
}
sleep(Duration::from_millis(1));
}
Ok(())
}