1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-10-06 04:31:55 +02:00

refactor(keys): switch to colpetto

Switches from the `input` crate to new async `colpetto`
This commit is contained in:
Jake Stanger 2025-01-16 23:01:48 +00:00
commit a0231559d0
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
6 changed files with 1188 additions and 1020 deletions

View file

@ -1,17 +1,20 @@
use crate::{arc_rw, read_lock, send, spawn, spawn_blocking, write_lock};
use crate::{Ironbar, arc_rw, read_lock, send, spawn, write_lock};
use color_eyre::{Report, Result};
use colpetto::event::{AsRawEvent, DeviceEvent, KeyState, KeyboardEvent};
use colpetto::{DeviceCapability, Libinput};
use evdev_rs::DeviceWrapper;
use evdev_rs::enums::{EV_KEY, EV_LED, EventCode, int_to_ev_key};
use input::event::keyboard::{KeyState, KeyboardEventTrait};
use input::event::{DeviceEvent, EventTrait, KeyboardEvent};
use input::{DeviceCapability, Libinput, LibinputInterface};
use libc::{O_ACCMODE, O_RDONLY, O_RDWR};
use std::fs::{File, OpenOptions};
use std::os::unix::{fs::OpenOptionsExt, io::OwnedFd};
use futures_lite::StreamExt;
use rustix::fs::{Mode, OFlags, open};
use rustix::io::Errno;
use std::ffi::{CStr, CString, c_int};
use std::os::fd::{FromRawFd, IntoRawFd, RawFd};
use std::os::unix::io::OwnedFd;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use tokio::sync::broadcast;
use tokio::task::LocalSet;
use tokio::time::sleep;
use tracing::{debug, error};
@ -88,28 +91,6 @@ impl<P: AsRef<Path>> TryFrom<KeyData<P>> for Event {
}
}
pub struct Interface;
impl LibinputInterface for Interface {
fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<OwnedFd, i32> {
// No idea what these flags do honestly, just copied them from the example.
let op = OpenOptions::new()
.custom_flags(flags)
.read((flags & O_ACCMODE == O_RDONLY) | (flags & O_ACCMODE == O_RDWR))
.open(path)
.map(OwnedFd::from);
if let Err(err) = &op {
error!("error opening {}: {err:?}", path.display());
}
op.map_err(|err| err.raw_os_error().unwrap_or(-1))
}
fn close_restricted(&mut self, fd: OwnedFd) {
drop(File::from(fd));
}
}
#[derive(Debug)]
pub struct Client {
tx: broadcast::Sender<Event>,
@ -122,14 +103,23 @@ pub struct Client {
impl Client {
pub fn init(seat: String) -> Arc<Self> {
let client = Arc::new(Self::new(seat));
{
let client = client.clone();
spawn_blocking(move || {
if let Err(err) = client.run() {
error!("{err:?}");
}
std::thread::spawn(move || {
let local = LocalSet::new();
local.spawn_local(async move {
if let Err(err) = client.run().await {
error!("{err:?}");
}
});
Ironbar::runtime().block_on(local);
});
}
client
}
@ -144,83 +134,90 @@ impl Client {
}
}
fn run(&self) -> Result<()> {
let mut input = Libinput::new_with_udev(Interface);
input
.udev_assign_seat(&self.seat)
.map_err(|()| Report::msg("failed to assign seat"))?;
fn open_restricted(path: &CStr, flags: c_int) -> std::result::Result<RawFd, i32> {
open(path, OFlags::from_bits_retain(flags as u32), Mode::empty())
.map(IntoRawFd::into_raw_fd)
.map_err(Errno::raw_os_error)
}
loop {
input.dispatch()?;
fn close_restricted(fd: c_int) {
drop(unsafe { OwnedFd::from_raw_fd(fd) });
}
for event in &mut input {
match event {
input::Event::Keyboard(KeyboardEvent::Key(event))
if event.key_state() == KeyState::Released =>
{
let Some(device) = (unsafe { event.device().udev_device() }) else {
continue;
};
async fn run(&self) -> Result<()> {
let mut libinput = Libinput::with_tracing(Self::open_restricted, Self::close_restricted)?;
let Some(
key @ (EV_KEY::KEY_CAPSLOCK
| EV_KEY::KEY_NUMLOCK
| EV_KEY::KEY_SCROLLLOCK),
) = int_to_ev_key(event.key())
else {
continue;
};
libinput.udev_assign_seat(CString::new(&*self.seat)?.as_c_str())?;
if let Some(device_path) = device.devnode().map(PathBuf::from) {
let tx = self.tx.clone();
let mut stream = libinput.event_stream()?;
while let Some(event) = stream.try_next().await? {
match event {
colpetto::Event::Device(DeviceEvent::Added(event)) => {
let device = event.device();
if !device.has_capability(DeviceCapability::Keyboard) {
continue;
}
// need to spawn a task to avoid blocking
spawn(async move {
// wait for kb to change
sleep(Duration::from_millis(50)).await;
let name = device.name();
let Some(device) = event.device().udev_device() else {
continue;
};
let data = KeyData { device_path, key };
if let Some(device_path) = device.devnode() {
// not all devices which report as keyboards actually are one -
// fire test event so we can figure out if it is
let caps_event: Result<Event> = KeyData {
device_path,
key: EV_KEY::KEY_CAPSLOCK,
}
.try_into();
if let Ok(event) = data.try_into() {
send!(tx, event);
}
});
if caps_event.is_ok() {
debug!(
"new keyboard device: {} | {}",
name.to_string_lossy(),
device_path.display()
);
write_lock!(self.known_devices).push(device_path.to_path_buf());
send!(self.tx, Event::Device);
}
}
input::Event::Device(DeviceEvent::Added(event)) => {
let device = event.device();
if !device.has_capability(DeviceCapability::Keyboard) {
continue;
}
let name = device.name();
let Some(device) = (unsafe { event.device().udev_device() }) else {
continue;
};
if let Some(device_path) = device.devnode() {
// not all devices which report as keyboards actually are one -
// fire test event so we can figure out if it is
let caps_event: Result<Event> = KeyData {
device_path,
key: EV_KEY::KEY_CAPSLOCK,
}
.try_into();
if caps_event.is_ok() {
debug!("new keyboard device: {name} | {}", device_path.display());
write_lock!(self.known_devices).push(device_path.to_path_buf());
send!(self.tx, Event::Device);
}
}
}
_ => {}
}
}
colpetto::Event::Keyboard(KeyboardEvent::Key(event))
if event.key_state() == KeyState::Released =>
{
let Some(device) = event.device().udev_device() else {
continue;
};
// we need to sleep for a short period to avoid hogging cpu
std::thread::sleep(Duration::from_millis(20));
let Some(
key @ (EV_KEY::KEY_CAPSLOCK | EV_KEY::KEY_NUMLOCK | EV_KEY::KEY_SCROLLLOCK),
) = int_to_ev_key(event.key())
else {
continue;
};
if let Some(device_path) = device.devnode().map(PathBuf::from) {
let tx = self.tx.clone();
// need to spawn a task to avoid blocking
spawn(async move {
// wait for kb to change
sleep(Duration::from_millis(50)).await;
let data = KeyData { device_path, key };
if let Ok(event) = data.try_into() {
send!(tx, event);
}
});
}
}
_ => {}
}
}
Err(Report::msg("unexpected end of stream"))
}
pub fn get_state(&self, key: Key) -> bool {