mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-08-16 22:31:03 +02:00
Merge pull request #846 from JakeStanger/refactor/keys/colpetto
refactor(keys): switch to `colpetto`
This commit is contained in:
commit
28a0e0fb99
7 changed files with 1172 additions and 1016 deletions
1869
Cargo.lock
generated
1869
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
@ -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 = "1.0.5", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input
|
||||
|
||||
# schema
|
||||
schemars = { version = "0.8.22", optional = true }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,18 +8,22 @@ 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::buffer::spare_capacity;
|
||||
use rustix::event::epoll;
|
||||
use rustix::event::epoll::CreateFlags;
|
||||
use rustix::fs::Timespec;
|
||||
use rustix::pipe::{fcntl_getpipe_size, fcntl_setpipe_size};
|
||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||
use std::cmp::min;
|
||||
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::time::Duration;
|
||||
use std::{fs, io};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::sync::broadcast;
|
||||
|
@ -154,7 +158,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 +277,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 +298,30 @@ 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 = Vec::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,
|
||||
spare_capacity(&mut events),
|
||||
Some(&Timespec::try_from(Duration::from_millis(100))?),
|
||||
)?;
|
||||
|
||||
match file.write(chunk) {
|
||||
Ok(written) => {
|
||||
|
@ -331,9 +337,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 +366,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 +376,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());
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use super::device::DataControlDevice;
|
||||
use super::manager::DataControlDeviceManagerState;
|
||||
use color_eyre::Result;
|
||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||
use tracing::error;
|
||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
|
||||
Event, ZwlrDataControlSourceV1,
|
||||
|
@ -41,7 +43,7 @@ pub trait DataControlSourceHandler: Sized {
|
|||
source: &ZwlrDataControlSourceV1,
|
||||
mime: String,
|
||||
fd: WritePipe,
|
||||
);
|
||||
) -> Result<()>;
|
||||
|
||||
/// The data source is no longer valid
|
||||
/// Cleanup & destroy this resource
|
||||
|
@ -68,7 +70,9 @@ where
|
|||
) {
|
||||
match event {
|
||||
Event::Send { mime_type, fd } => {
|
||||
state.send_request(conn, qh, source, mime_type, fd.into());
|
||||
if let Err(err) = state.send_request(conn, qh, source, mime_type, fd.into()) {
|
||||
error!("{err:#}");
|
||||
}
|
||||
}
|
||||
Event::Cancelled => {
|
||||
state.cancelled(conn, qh, source);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue