titan2-touchpadd/src/main.rs
Peter Cai e15517aaf9 Emit simulated keyboard events from the keyboard uinput
Android doesn't like a mouse that also acts as a keyboard
2025-11-16 19:15:03 -05:00

188 lines
5.5 KiB
Rust

use std::{
os::unix::fs::FileTypeExt,
sync::{Arc, Mutex},
};
use evdev::{AttributeSet, Device, KeyCode, RelativeAxisCode, uinput};
use eyre::{OptionExt, eyre};
use tracing::{error, info, level_filters::LevelFilter, warn};
use tracing_logcat::{LogcatMakeWriter, LogcatTag};
use tracing_subscriber::{EnvFilter, fmt::format::Format};
use crate::constants::NUMERIC_KEYCODES;
mod constants;
mod evloop;
mod gesture;
mod keyboard;
mod state;
fn main() -> eyre::Result<()> {
let base_subscriber = tracing_subscriber::fmt().with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
);
if let Ok(o) = std::env::var("LOGCAT_OUTPUT")
&& o == "true"
{
let tag = LogcatTag::Fixed(env!("CARGO_PKG_NAME").to_owned());
let writer = LogcatMakeWriter::new(tag).expect("Failed to initialize logcat writer");
base_subscriber
.with_writer(writer)
.event_format(Format::default().with_level(false).without_time())
.with_ansi(false)
.init();
} else {
base_subscriber.init();
}
info!("Detecting Titan 2's touchpad input...");
let (Some(mut touchpad_dev), Some(mut keyboard_dev)) = find_touchpad_and_keyboard_dev()? else {
error!("No touchpad or keyboard devices found, exitting");
return Err(eyre!("No touchpad device found"));
};
info!("Creating virtual mouse input...");
let uinput_axes = {
let mut axes = AttributeSet::new();
axes.insert(RelativeAxisCode::REL_X);
axes.insert(RelativeAxisCode::REL_Y);
axes.insert(RelativeAxisCode::REL_WHEEL_HI_RES);
axes
};
let uinput_keys = {
let mut keys = AttributeSet::new();
keys.insert(KeyCode::BTN_LEFT);
keys.insert(KeyCode::BTN_RIGHT);
keys
};
let mut uinput_dev = uinput::VirtualDevice::builder()?
.name("titan2-virtual-mouse")
.with_relative_axes(&uinput_axes)?
.with_keys(&uinput_keys)?
.build()?;
info!(
"Virtual mouse input created at {}",
uinput_dev
.get_syspath()?
.to_str()
.ok_or_eyre("can't decode pathbuf")?
);
// Features that remap / simulate keyboard input are guarded behind a feature gate
let keyboard_features_enabled =
matches!(std::env::var("KEYBOARD_FEATURES"), Ok(v) if v == "true");
let keyboard_uinput_dev = if keyboard_features_enabled {
let mut dev = uinput::VirtualDevice::builder()?
.name("TitanKey") // For the keyboard we're really just adding minor features, so we reuse the official device name
.with_relative_axes(&uinput_axes)?
.with_keys(&{
let mut keys = AttributeSet::new();
for key in keyboard_dev.supported_keys().unwrap() {
keys.insert(key);
}
// These additional keys respond to touchpad gestures, but have to live on the keyboard
// in order not to mess up Android's key charracter maps.
keys.insert(KeyCode::KEY_LEFT);
keys.insert(KeyCode::KEY_RIGHT);
keys.insert(KeyCode::KEY_UP);
keys.insert(KeyCode::KEY_DOWN);
// Top-row virtual numeric keys
for i in NUMERIC_KEYCODES {
keys.insert(i);
}
keys
})?
.build()?;
info!(
"Virtual keyboard created at {}",
dev.get_syspath()?
.to_str()
.ok_or_eyre("can't decode pathbuf")?
);
Some(Arc::new(Mutex::new(dev)))
} else {
None
};
if let Err(e) = touchpad_dev.grab() {
warn!(
"Unable to grab touchpad device, continuing but there might be conflicts with system gestures: {e:?}"
);
}
if keyboard_uinput_dev.is_some()
&& let Err(e) = keyboard_dev.grab()
{
return Err(eyre!(
"Unable to grab the keyboard device when keyboard features are enabled: {e:?}"
));
}
evloop::run_evloop(
keyboard_features_enabled,
touchpad_dev,
keyboard_dev,
uinput_dev,
keyboard_uinput_dev,
)
}
fn find_touchpad_and_keyboard_dev() -> eyre::Result<(Option<Device>, Option<Device>)> {
let mut touchpad_dev = None;
let mut keyboard_dev = None;
for ent in std::fs::read_dir("/dev/input")? {
let Ok(ent) = ent else {
continue;
};
let Ok(file_type) = ent.file_type() else {
continue;
};
if !file_type.is_char_device() {
continue;
}
let Ok(filename) = ent.file_name().into_string() else {
continue;
};
if !filename.starts_with("event") {
continue;
}
info!("Checking device /dev/input/{filename}");
let Ok(dev) = Device::open(ent.path()) else {
warn!("Unable to open device /dev/input/{filename}, skipping");
continue;
};
if let Some(name) = dev.name() {
if name == "touchPad" {
info!("Found touch pad device at /dev/input/{filename}");
touchpad_dev = Some(dev);
} else if name == "TitanKey" {
info!("Found keyboard device at /dev/input/{filename}");
keyboard_dev = Some(dev);
}
}
}
Ok((touchpad_dev, keyboard_dev))
}