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:
parent
f204b24ba0
commit
a0231559d0
6 changed files with 1188 additions and 1020 deletions
1899
Cargo.lock
generated
1899
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
45
Cargo.toml
45
Cargo.toml
|
@ -39,11 +39,11 @@ ipc = ["dep:serde_json"]
|
|||
http = ["dep:reqwest"]
|
||||
|
||||
"config+all" = [
|
||||
"config+json",
|
||||
"config+yaml",
|
||||
"config+toml",
|
||||
"config+corn",
|
||||
"config+ron",
|
||||
"config+json",
|
||||
"config+yaml",
|
||||
"config+toml",
|
||||
"config+corn",
|
||||
"config+ron",
|
||||
]
|
||||
"config+json" = ["universal-config/json"]
|
||||
"config+yaml" = ["universal-config/yaml"]
|
||||
|
@ -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"]
|
||||
|
@ -105,17 +105,17 @@ gtk = "0.18.2"
|
|||
gtk-layer-shell = "0.8.2"
|
||||
glib = "0.18.5"
|
||||
tokio = { version = "1.44.1", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
"process",
|
||||
"sync",
|
||||
"io-util",
|
||||
"net",
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
"process",
|
||||
"sync",
|
||||
"io-util",
|
||||
"net",
|
||||
] }
|
||||
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"
|
||||
|
@ -127,7 +127,7 @@ notify = { version = "8.0.0", default-features = false }
|
|||
wayland-client = "0.31.1"
|
||||
wayland-protocols-wlr = { version = "0.2.0", features = ["client"] }
|
||||
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [
|
||||
"calloop",
|
||||
"calloop",
|
||||
] }
|
||||
universal-config = { version = "0.5.0", default-features = false }
|
||||
ctrlc = "3.4.5"
|
||||
|
@ -143,7 +143,7 @@ serde_json = { version = "1.0.140", optional = true }
|
|||
reqwest = { version = "0.12.15", default-features = false, features = ["default-tls", "http2"], optional = true }
|
||||
|
||||
# cairo
|
||||
lua-src = { version = "547.0.0", optional = true }
|
||||
lua-src = { version = "547.0.0", optional = true }
|
||||
mlua = { version = "0.9.9", optional = true, features = ["luajit"] }
|
||||
cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -188,4 +187,8 @@ schemars = { version = "0.8.22", optional = true }
|
|||
clap = { version = "4.5.34", features = ["derive"] }
|
||||
clap_complete = "4.5.47"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
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" }
|
|
@ -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 {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue