1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-16 14:21:03 +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
parent f204b24ba0
commit a0231559d0
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
6 changed files with 1188 additions and 1020 deletions

1899
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -53,7 +53,7 @@ http = ["dep:reqwest"]
cairo = ["lua-src", "mlua", "cairo-rs"]
clipboard = ["dep:nix"]
clipboard = ["dep:rustix"]
clock = ["chrono"]
@ -61,7 +61,7 @@ custom = []
focused = []
keyboard = ["dep:input", "dep:evdev-rs", "dep:libc", "dep:nix"]
keyboard = ["dep:colpetto", "dep:evdev-rs", "dep:rustix"]
"keyboard+all" = ["keyboard", "keyboard+sway", "keyboard+hyprland"]
"keyboard+sway" = ["keyboard", "sway"]
"keyboard+hyprland" = ["keyboard", "hyprland"]
@ -115,7 +115,7 @@ tokio = { version = "1.44.1", features = [
] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracing-error = { version = "0.2.1" , default-features = false }
tracing-error = { version = "0.2.1", default-features = false }
tracing-appender = "0.2.3"
strip-ansi-escapes = "0.2.0"
color-eyre = "0.6.3"
@ -151,9 +151,8 @@ cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
chrono = { version = "0.4.40", optional = true, default-features = false, features = ["clock", "unstable-locales"] }
# keyboard
input = { version = "0.9.1", optional = true }
colpetto = { version = "0.6.0", features = ["tokio", "tracing"], optional = true }
evdev-rs = { version = "0.6.1", optional = true }
libc = { version = "0.2.171", optional = true }
# music
mpd-utils = { version = "0.2.1", optional = true }
@ -176,10 +175,10 @@ libpulse-binding = { version = "2.29.0", optional = true }
# shared
futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces
nix = { version = "0.29.0", optional = true, features = ["event", "fs", "poll"] } # clipboard, input
zbus = { version = "5.5.0", default-features = false, features = ["tokio"], optional = true } # network_manager, notifications, upower
swayipc-async = { version = "2.0.4", optional = true } # workspaces, keyboard
hyprland = { version = "0.4.0-alpha.3", features = ["silent"], optional = true } # workspaces, keyboard
rustix = { version = "0.38.43", default-features = false, features = ["fs", "pipe"], optional = true } # clipboard, input
# schema
schemars = { version = "0.8.22", optional = true }
@ -189,3 +188,7 @@ clap = { version = "4.5.34", features = ["derive"] }
clap_complete = "4.5.47"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
pkg-config = "0.3.31" # specify version to fix dep resolution issue
#[patch.crates-io]
#system-tray = { path = "../system-tray" }

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() {
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,28 +134,64 @@ 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 {
async fn run(&self) -> Result<()> {
let mut libinput = Libinput::with_tracing(Self::open_restricted, Self::close_restricted)?;
libinput.udev_assign_seat(CString::new(&*self.seat)?.as_c_str())?;
let mut stream = libinput.event_stream()?;
while let Some(event) = stream.try_next().await? {
match event {
input::Event::Keyboard(KeyboardEvent::Key(event))
colpetto::Event::Device(DeviceEvent::Added(event)) => {
let device = event.device();
if !device.has_capability(DeviceCapability::Keyboard) {
continue;
}
let name = device.name();
let Some(device) = 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.to_string_lossy(),
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) = (unsafe { event.device().udev_device() }) else {
let Some(device) = event.device().udev_device() else {
continue;
};
let Some(
key @ (EV_KEY::KEY_CAPSLOCK
| EV_KEY::KEY_NUMLOCK
| EV_KEY::KEY_SCROLLLOCK),
key @ (EV_KEY::KEY_CAPSLOCK | EV_KEY::KEY_NUMLOCK | EV_KEY::KEY_SCROLLLOCK),
) = int_to_ev_key(event.key())
else {
continue;
@ -187,40 +213,11 @@ impl Client {
});
}
}
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);
}
}
}
_ => {}
}
}
// we need to sleep for a short period to avoid hogging cpu
std::thread::sleep(Duration::from_millis(20));
}
Err(Report::msg("unexpected end of stream"))
}
pub fn get_state(&self, key: Key) -> bool {

View file

@ -148,10 +148,13 @@ impl Clients {
#[cfg(feature = "keyboard")]
pub fn libinput(&mut self, seat: &str) -> Arc<libinput::Client> {
self.libinput
.entry(seat.into())
.or_insert_with(|| libinput::Client::init(seat.to_string()))
.clone()
if let Some(client) = self.libinput.get(seat) {
client.clone()
} else {
let client = libinput::Client::init(seat.to_string());
self.libinput.insert(seat.into(), client.clone());
client
}
}
#[cfg(feature = "music")]

View file

@ -8,17 +8,19 @@ use self::offer::{DataControlDeviceOffer, DataControlOfferHandler};
use self::source::DataControlSourceHandler;
use super::{Client, Environment, Event, Request, Response};
use crate::{Ironbar, lock, spawn, try_send};
use color_eyre::Result;
use device::DataControlDevice;
use glib::Bytes;
use nix::fcntl::{F_GETPIPE_SZ, F_SETPIPE_SZ, fcntl};
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags, EpollTimeout};
use rustix::event::epoll;
use rustix::event::epoll::CreateFlags;
use rustix::pipe::{fcntl_getpipe_size, fcntl_setpipe_size};
use smithay_client_toolkit::data_device_manager::WritePipe;
use std::cmp::min;
use std::ffi::c_int;
use std::fmt::{Debug, Formatter};
use std::fs::File;
use std::io::{ErrorKind, Write};
use std::os::fd::RawFd;
use std::os::fd::{AsRawFd, OwnedFd};
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::sync::Arc;
use std::{fs, io};
use tokio::io::AsyncReadExt;
@ -154,7 +156,7 @@ impl Environment {
let source = self
.data_control_device_manager_state
.create_copy_paste_source(&self.queue_handle, [INTERNAL_MIME_TYPE, &item.mime_type]);
.create_copy_paste_source(&self.queue_handle, [&item.mime_type, INTERNAL_MIME_TYPE]);
source.set_selection(&device.device);
self.copy_paste_sources.push(source);
@ -273,7 +275,7 @@ impl DataControlSourceHandler for Environment {
source: &ZwlrDataControlSourceV1,
mime: String,
write_pipe: WritePipe,
) {
) -> Result<()> {
debug!("Handler received source send request event ({mime})");
if let Some(item) = lock!(self.clipboard).clone() {
@ -294,28 +296,26 @@ impl DataControlSourceHandler for Environment {
),
};
let pipe_size = set_pipe_size(fd.as_raw_fd(), bytes.len())
.expect("Failed to increase pipe size");
let pipe_size =
set_pipe_size(fd.as_fd(), bytes.len()).expect("Failed to increase pipe size");
let mut file = File::from(fd.try_clone().expect("to be able to clone"));
debug!("Writing {} bytes", bytes.len());
let mut events = (0..16).map(|_| EpollEvent::empty()).collect::<Vec<_>>();
let epoll_event = EpollEvent::new(EpollFlags::EPOLLOUT, 0);
let epoll = epoll::create(CreateFlags::CLOEXEC)?;
epoll::add(
&epoll,
fd,
epoll::EventData::new_u64(0),
epoll::EventFlags::OUT,
)?;
let epoll_fd =
Epoll::new(EpollCreateFlags::empty()).expect("to get valid file descriptor");
epoll_fd
.add(fd, epoll_event)
.expect("to send valid epoll operation");
let mut events = epoll::EventVec::with_capacity(16);
let timeout = EpollTimeout::from(100u16);
while !bytes.is_empty() {
let chunk = &bytes[..min(pipe_size as usize, bytes.len())];
let chunk = &bytes[..min(pipe_size, bytes.len())];
epoll_fd
.wait(&mut events, timeout)
.expect("Failed to wait to epoll");
epoll::wait(&epoll, &mut events, 100u16 as c_int)?;
match file.write(chunk) {
Ok(written) => {
@ -331,9 +331,11 @@ impl DataControlSourceHandler for Environment {
debug!("Done writing");
} else {
error!("Failed to find source");
error!("Failed to find source (mime: '{mime}')");
}
}
Ok(())
}
fn cancelled(
@ -358,7 +360,7 @@ impl DataControlSourceHandler for Environment {
/// it will be clamped at this.
///
/// Returns the new size if succeeded.
fn set_pipe_size(fd: RawFd, size: usize) -> io::Result<i32> {
fn set_pipe_size(fd: BorrowedFd, size: usize) -> io::Result<usize> {
// clamp size at kernel max
let max_pipe_size = fs::read_to_string("/proc/sys/fs/pipe-max-size")
.expect("Failed to find pipe-max-size virtual kernel file")
@ -368,23 +370,24 @@ fn set_pipe_size(fd: RawFd, size: usize) -> io::Result<i32> {
let size = min(size, max_pipe_size);
let curr_size = fcntl(fd, F_GETPIPE_SZ)? as usize;
let curr_size = fcntl_getpipe_size(fd)?;
trace!("Current pipe size: {curr_size}");
let new_size = if size > curr_size {
trace!("Requesting pipe size increase to (at least): {size}");
let res = fcntl(fd, F_SETPIPE_SZ(size as i32))?;
fcntl_setpipe_size(fd, size)?;
let res = fcntl_getpipe_size(fd)?;
trace!("New pipe size: {res}");
if res < size as i32 {
if res < size {
return Err(io::Error::last_os_error());
}
res
} else {
size as i32
size
};
Ok(new_size)

View file

@ -1,7 +1,6 @@
use super::manager::DataControlDeviceManagerState;
use crate::lock;
use nix::fcntl::OFlag;
use nix::unistd::pipe2;
use rustix::pipe::{PipeFlags, pipe_with};
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
use std::ops::DerefMut;
use std::os::fd::AsFd;
@ -171,7 +170,7 @@ where
/// could not be created.
pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result<Receiver> {
// create a pipe
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK)?;
let (readfd, writefd) = pipe_with(PipeFlags::CLOEXEC)?;
offer.receive(mime_type, writefd.as_fd());