feat(teipe): initial commit

This commit is contained in:
2024-08-18 17:43:59 +02:00
commit 7f2dac5a61
30 changed files with 3390 additions and 0 deletions

16
libeirs/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "libeirs"
version = "0.1.0"
edition = "2021"
[build-dependencies]
bindgen.workspace = true
anyhow.workspace = true
[dependencies]
tokio = { workspace = true, features = ["rt", "net"] }
tokio-stream = { workspace = true }
log = { workspace = true, features = ["kv"] }
memmap2 = "0.9.4"
nix = { version = "0.29", features = ["poll"] }
anyhow.workspace = true

25
libeirs/build.rs Normal file
View File

@@ -0,0 +1,25 @@
use std::{env, path::PathBuf};
fn main() -> anyhow::Result<()> {
let bindings = bindgen::builder()
.header("/usr/include/libei-1.0/libei.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.allowlist_item("ei_.*")
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
})
.no_debug("ei_event_type") // Implemented manually to use Display
.bitfield_enum("ei_device_capability")
.generate()?;
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let out_file = out_path.join("libei.rs");
bindings
.write_to_file(out_file)
.expect("Couldn't write bindings!");
println!("cargo:rustc-link-lib=ei");
Ok(())
}

129
libeirs/src/device.rs Normal file
View File

@@ -0,0 +1,129 @@
use std::{borrow::Borrow, ffi::CStr, fmt::Display, ops::Deref, ptr::NonNull};
use crate::{eirc::Ei, ffi::ei_device};
use super::{ffi, keymap::Keymap};
#[derive(PartialEq, Eq)]
pub struct Device {
device: DeviceRef,
}
impl Drop for Device {
fn drop(&mut self) {
log::trace!("Device dropped");
let device = self.as_raw();
unsafe {
ffi::ei_device_close(device);
ffi::ei_device_unref(device)
};
}
}
impl Deref for Device {
type Target = DeviceRef;
fn deref(&self) -> &Self::Target {
&self.device
}
}
impl Clone for Device {
fn clone(&self) -> Self {
self.device.to_owned()
}
}
impl Borrow<DeviceRef> for Device {
fn borrow(&self) -> &DeviceRef {
&self.device
}
}
impl AsRef<DeviceRef> for Device {
fn as_ref(&self) -> &DeviceRef {
&self.device
}
}
impl Display for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.device)
}
}
impl PartialEq<DeviceRef> for Device {
fn eq(&self, other: &DeviceRef) -> bool {
self.device == *other
}
}
#[derive(PartialEq, Eq)]
pub struct DeviceRef {
device: NonNull<ffi::ei_device>,
}
impl DeviceRef {
pub(crate) fn new(device: *mut ei_device) -> Option<Self> {
NonNull::new(device).map(|device| Self { device })
}
pub fn start_emulating(&self, sequence: u32) {
unsafe { ffi::ei_device_start_emulating(self.as_raw(), sequence) }
}
pub fn keyboard_key(&self, key: u32, pressed: bool) {
unsafe {
ffi::ei_device_keyboard_key(self.as_raw(), key, pressed);
}
}
pub fn frame(&self, time: Option<u64>) {
let time = time.unwrap_or_else(|| unsafe {
let ei = ffi::ei_device_get_context(self.as_raw());
ffi::ei_now(ei)
});
unsafe {
ffi::ei_device_frame(self.as_raw(), time);
}
}
pub(crate) fn as_raw(&self) -> *mut ffi::ei_device {
self.device.as_ptr()
}
pub fn get_keymap(&self) -> Option<Keymap> {
Keymap::from_device(self)
}
pub fn get_context(&self) -> Ei {
unsafe { Ei::from_raw(ffi::ei_device_get_context(self.as_raw())) }
}
}
impl Display for DeviceRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = unsafe { CStr::from_ptr(ffi::ei_device_get_name(self.as_raw())) }
.to_str()
.unwrap();
write!(f, "{str}",)
}
}
impl ToOwned for DeviceRef {
type Owned = Device;
fn to_owned(&self) -> Self::Owned {
let ptr = unsafe { ffi::ei_device_ref(self.as_raw()) };
Device {
device: DeviceRef {
device: NonNull::new(ptr).unwrap(),
},
}
}
}
impl PartialEq<Device> for DeviceRef {
fn eq(&self, other: &Device) -> bool {
*other == *self
}
}

107
libeirs/src/eirc.rs Normal file
View File

@@ -0,0 +1,107 @@
use std::{
os::fd::{BorrowedFd, IntoRawFd, OwnedFd},
ptr::NonNull,
};
use super::{events::Event, ffi};
const CLIENT_NAME: &std::ffi::CStr = c"teipe";
pub struct Ei {
ei: *mut ffi::ei,
}
unsafe extern "C" fn null_handler(
_ei: *mut ffi::ei,
_priority: ffi::ei_log_priority,
_message: *const ::std::os::raw::c_char,
_context: *mut ffi::ei_log_context,
) {
// no op
}
unsafe extern "C" fn log_handler(
_ei: *mut ffi::ei,
priority: ffi::ei_log_priority,
message: *const ::std::os::raw::c_char,
ctx: *mut ffi::ei_log_context,
) {
let (file, func, line, msg) = unsafe {
(
std::ffi::CStr::from_ptr(ffi::ei_log_context_get_file(ctx)),
std::ffi::CStr::from_ptr(ffi::ei_log_context_get_func(ctx)),
ffi::ei_log_context_get_line(ctx),
std::ffi::CStr::from_ptr(message),
)
};
log::log!(
target:"libei",
priority.into(),
file:?, func:?, line:?;
"{msg:?}"
);
}
impl Ei {
pub(crate) fn new_sender(fd: OwnedFd) -> Self {
// FIX: handle errors
log::info!("Creating ei");
let ei = unsafe { ffi::ei_new_sender(std::ptr::null_mut()) };
unsafe {
if let Some(level) = log::max_level().to_level() {
ffi::ei_log_set_handler(ei, Some(log_handler));
ffi::ei_log_set_priority(ei, level.into());
} else {
ffi::ei_log_set_handler(ei, Some(null_handler));
}
ffi::ei_configure_name(ei, CLIENT_NAME.as_ptr());
ffi::ei_setup_backend_fd(ei, fd.into_raw_fd());
}
Self { ei }
}
pub(crate) unsafe fn from_raw(ei: *mut ffi::ei) -> Self {
unsafe {
ffi::ei_ref(ei);
}
Self { ei }
}
pub fn get_fd(&self) -> BorrowedFd {
unsafe {
let fd = ffi::ei_get_fd(self.ei);
BorrowedFd::borrow_raw(fd)
}
}
pub fn dispatch(&self) {
log::trace!("Dispatch");
unsafe {
ffi::ei_dispatch(self.ei);
}
}
pub fn get_event(&self) -> Option<Event> {
let e = unsafe { ffi::ei_get_event(self.ei) };
NonNull::new(e).map(Event::new)
}
pub fn now(&self) -> u64 {
unsafe { ffi::ei_now(self.ei) }
}
}
impl Clone for Ei {
fn clone(&self) -> Self {
Self {
ei: unsafe { ffi::ei_ref(self.ei) },
}
}
}
impl Drop for Ei {
fn drop(&mut self) {
unsafe { ffi::ei_unref(self.ei) };
}
}

47
libeirs/src/events.rs Normal file
View File

@@ -0,0 +1,47 @@
use std::{fmt::Display, ptr::NonNull};
use crate::device::DeviceRef;
use super::{
ffi::{self, ei_event},
seat::Seat,
};
pub use ffi::ei_event_type as EventType;
pub struct Event(NonNull<ei_event>);
impl Drop for Event {
fn drop(&mut self) {
unsafe { ffi::ei_event_unref(self.as_raw()) };
}
}
impl Event {
pub fn new(event: std::ptr::NonNull<ffi::ei_event>) -> Self {
Self(event)
}
pub fn as_raw(&self) -> *mut ei_event {
self.0.as_ptr()
}
pub fn get_type(&self) -> EventType {
unsafe { ffi::ei_event_get_type(self.as_raw()) }
}
pub fn get_seat(&self) -> Option<Seat> {
Seat::from_event(self)
}
pub fn get_device(&self) -> Option<DeviceRef> {
DeviceRef::new(unsafe { ffi::ei_event_get_device(self.as_raw()) })
}
}
impl Display for Event {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Event")
.field("Type", &self.get_type())
.finish()
}
}

49
libeirs/src/ffi.rs Normal file
View File

@@ -0,0 +1,49 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
use std::{
ffi::CStr,
fmt::{Debug, Display},
};
include!(concat!(env!("OUT_DIR"), "/libei.rs"));
impl From<ei_log_priority> for log::Level {
fn from(value: ei_log_priority) -> Self {
match value {
ei_log_priority::EI_LOG_PRIORITY_DEBUG => log::Level::Debug,
ei_log_priority::EI_LOG_PRIORITY_INFO => log::Level::Info,
ei_log_priority::EI_LOG_PRIORITY_WARNING => log::Level::Warn,
ei_log_priority::EI_LOG_PRIORITY_ERROR => log::Level::Error,
_ => log::Level::Debug,
}
}
}
impl From<log::Level> for ei_log_priority {
fn from(value: log::Level) -> Self {
match value {
log::Level::Trace | log::Level::Debug => ei_log_priority::EI_LOG_PRIORITY_DEBUG,
log::Level::Info => ei_log_priority::EI_LOG_PRIORITY_INFO,
log::Level::Warn => ei_log_priority::EI_LOG_PRIORITY_WARNING,
log::Level::Error => ei_log_priority::EI_LOG_PRIORITY_ERROR,
}
}
}
impl Debug for ei_event_type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl Display for ei_event_type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = unsafe { CStr::from_ptr(ei_event_type_to_string(*self)) }
.to_str()
.unwrap();
write!(f, "{}", str)
}
}

49
libeirs/src/keymap.rs Normal file
View File

@@ -0,0 +1,49 @@
use std::{error::Error, fmt::Debug, ptr::NonNull};
use super::{device::DeviceRef, ffi};
use ffi::ei_keymap_type as KeymapType;
pub struct Keymap(NonNull<ffi::ei_keymap>);
impl Keymap {
pub(crate) fn from_device(device: &DeviceRef) -> Option<Self> {
let keymap = unsafe {
let keymap = ffi::ei_device_keyboard_get_keymap(device.as_raw());
ffi::ei_keymap_ref(keymap)
};
NonNull::new(keymap).map(Self)
}
pub fn as_raw(&self) -> *mut ffi::ei_keymap {
self.0.as_ptr()
}
pub fn get_type(&self) -> KeymapType {
unsafe { ffi::ei_keymap_get_type(self.as_raw()) }
}
pub fn mmap_keymap(&self) -> Result<impl AsRef<[u8]>, impl Error> {
unsafe {
let fd = ffi::ei_keymap_get_fd(self.as_raw());
memmap2::Mmap::map(fd)
}
}
}
impl Debug for Keymap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Keymap").field(&self.0).finish()
}
}
impl Drop for Keymap {
fn drop(&mut self) {
unsafe { ffi::ei_keymap_unref(self.as_raw()) };
}
}
impl Clone for Keymap {
fn clone(&self) -> Self {
Self(unsafe { NonNull::new_unchecked(ffi::ei_keymap_ref(self.as_raw())) })
}
}

13
libeirs/src/lib.rs Normal file
View File

@@ -0,0 +1,13 @@
mod device;
mod eirc;
pub mod events;
mod ffi;
mod keymap;
pub mod seat;
mod sender;
pub use device::*;
pub use events::*;
pub use keymap::Keymap;
pub use seat::Seat;
pub use sender::Sender;

41
libeirs/src/seat.rs Normal file
View File

@@ -0,0 +1,41 @@
use std::{ffi::CStr, fmt::Display, ptr::NonNull};
use super::{events::Event, ffi};
pub use ffi::ei_device_capability as DeviceCapability;
pub struct Seat {
seat: NonNull<ffi::ei_seat>,
}
impl Seat {
pub fn from_event(event: &Event) -> Option<Self> {
let seat = unsafe {
let seat = ffi::ei_event_get_seat(event.as_raw());
ffi::ei_seat_ref(seat)
};
NonNull::new(seat).map(|seat| Self { seat })
}
pub fn bind_capabilities(&self, capabilities: DeviceCapability) {
unsafe {
ffi::ei_seat_bind_capabilities(self.seat.as_ptr(), capabilities, 0);
}
}
}
impl Display for Seat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = unsafe { CStr::from_ptr(ffi::ei_seat_get_name(self.seat.as_ptr())) }
.to_str()
.unwrap();
write!(f, "{str}")
}
}
impl Drop for Seat {
fn drop(&mut self) {
log::trace!("Dropping seat {self}");
unsafe { ffi::ei_seat_unref(self.seat.as_ptr()) };
}
}

23
libeirs/src/sender.rs Normal file
View File

@@ -0,0 +1,23 @@
use std::{ops::Deref, os::fd::OwnedFd};
use super::eirc::Ei;
pub struct Sender {
ei: Ei,
}
impl Sender {
pub fn new(fd: OwnedFd) -> Self {
Self {
ei: Ei::new_sender(fd),
}
}
}
impl Deref for Sender {
type Target = Ei;
fn deref(&self) -> &Self::Target {
&self.ei
}
}