1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-16 22:31:03 +02:00

fix(clipboard): possible deadlock when copying (#863)

* Change clipboard calloop event loop with blocking read to tokio async readers.

Co-authored-by: Ridan Vandenbergh <ridanvandenbergh@gmail.com>

* cargo fmt

---------

Co-authored-by: Ridan Vandenbergh <ridanvandenbergh@gmail.com>
This commit is contained in:
Merlijn 2025-02-06 17:29:33 +01:00 committed by GitHub
parent 68ccc7ac4d
commit 75375aa341
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 32 additions and 82 deletions

View file

@ -11,7 +11,7 @@
<option name="backtrace" value="SHORT" /> <option name="backtrace" value="SHORT" />
<envs> <envs>
<env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" /> <env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
<env name="RUST_LOG" value="debug" /> <env name="IRONBAR_LOG" value="debug" />
</envs> </envs>
<option name="isRedirectInput" value="false" /> <option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" /> <option name="redirectInputPath" value="" />

View file

@ -12,7 +12,7 @@ use cfg_if::cfg_if;
use color_eyre::Report; use color_eyre::Report;
use smithay_client_toolkit::output::OutputState; use smithay_client_toolkit::output::OutputState;
use smithay_client_toolkit::reexports::calloop::channel as calloop_channel; use smithay_client_toolkit::reexports::calloop::channel as calloop_channel;
use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle}; use smithay_client_toolkit::reexports::calloop::EventLoop;
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource; use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState}; use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
use smithay_client_toolkit::seat::SeatState; use smithay_client_toolkit::seat::SeatState;
@ -43,7 +43,6 @@ cfg_if! {
use self::wlr_data_control::device::DataControlDevice; use self::wlr_data_control::device::DataControlDevice;
use self::wlr_data_control::manager::DataControlDeviceManagerState; use self::wlr_data_control::manager::DataControlDeviceManagerState;
use self::wlr_data_control::source::CopyPasteSource; use self::wlr_data_control::source::CopyPasteSource;
use self::wlr_data_control::SelectionOfferItem;
use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::protocol::wl_seat::WlSeat;
pub use wlr_data_control::{ClipboardItem, ClipboardValue}; pub use wlr_data_control::{ClipboardItem, ClipboardValue};
@ -195,7 +194,6 @@ pub struct Environment {
seat_state: SeatState, seat_state: SeatState,
queue_handle: QueueHandle<Self>, queue_handle: QueueHandle<Self>,
loop_handle: LoopHandle<'static, Self>,
event_tx: mpsc::Sender<Event>, event_tx: mpsc::Sender<Event>,
response_tx: std::sync::mpsc::Sender<Response>, response_tx: std::sync::mpsc::Sender<Response>,
@ -212,8 +210,6 @@ pub struct Environment {
data_control_devices: Vec<DataControlDeviceEntry>, data_control_devices: Vec<DataControlDeviceEntry>,
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
copy_paste_sources: Vec<CopyPasteSource>, copy_paste_sources: Vec<CopyPasteSource>,
#[cfg(feature = "clipboard")]
selection_offers: Vec<SelectionOfferItem>,
// local state // local state
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
@ -281,7 +277,6 @@ impl Environment {
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
data_control_device_manager_state, data_control_device_manager_state,
queue_handle: qh, queue_handle: qh,
loop_handle: loop_handle.clone(),
event_tx, event_tx,
response_tx, response_tx,
#[cfg(any(feature = "focused", feature = "launcher"))] #[cfg(any(feature = "focused", feature = "launcher"))]
@ -292,8 +287,6 @@ impl Environment {
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
copy_paste_sources: vec![], copy_paste_sources: vec![],
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
selection_offers: vec![],
#[cfg(feature = "clipboard")]
clipboard: arc_mut!(None), clipboard: arc_mut!(None),
}; };

View file

@ -4,23 +4,24 @@ pub mod offer;
pub mod source; pub mod source;
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler}; use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer}; use self::offer::{DataControlDeviceOffer, DataControlOfferHandler};
use self::source::DataControlSourceHandler; use self::source::DataControlSourceHandler;
use super::{Client, Environment, Event, Request, Response}; use super::{Client, Environment, Event, Request, Response};
use crate::{lock, try_send, Ironbar}; use crate::{lock, spawn, try_send, Ironbar};
use device::DataControlDevice; use device::DataControlDevice;
use glib::Bytes; use glib::Bytes;
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ}; use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags, EpollTimeout}; use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags, EpollTimeout};
use smithay_client_toolkit::data_device_manager::WritePipe; use smithay_client_toolkit::data_device_manager::WritePipe;
use smithay_client_toolkit::reexports::calloop::{PostAction, RegistrationToken};
use std::cmp::min; use std::cmp::min;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::fs::File; use std::fs::File;
use std::io::{ErrorKind, Read, Write}; use std::io::{ErrorKind, Write};
use std::os::fd::{AsRawFd, OwnedFd, RawFd}; use std::os::fd::RawFd;
use std::os::fd::{AsRawFd, OwnedFd};
use std::sync::Arc; use std::sync::Arc;
use std::{fs, io}; use std::{fs, io};
use tokio::io::AsyncReadExt;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
use wayland_client::{Connection, QueueHandle}; use wayland_client::{Connection, QueueHandle};
@ -28,12 +29,6 @@ use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1
const INTERNAL_MIME_TYPE: &str = "x-ironbar-internal"; const INTERNAL_MIME_TYPE: &str = "x-ironbar-internal";
#[derive(Debug)]
pub struct SelectionOfferItem {
offer: SelectionOffer,
token: Option<RegistrationToken>,
}
/// Represents a value which can be read/written /// Represents a value which can be read/written
/// to/from the system clipboard and surrounding metadata. /// to/from the system clipboard and surrounding metadata.
/// ///
@ -168,22 +163,20 @@ impl Environment {
} }
/// Reads an offer file handle into a new `ClipboardItem`. /// Reads an offer file handle into a new `ClipboardItem`.
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> { async fn read_file(
mime_type: &MimeType,
file: &mut tokio::net::unix::pipe::Receiver,
) -> io::Result<ClipboardItem> {
let mut buf = vec![];
file.read_to_end(&mut buf).await?;
let value = match mime_type.category { let value = match mime_type.category {
MimeTypeCategory::Text => { MimeTypeCategory::Text => {
let mut txt = String::new(); let txt = String::from_utf8_lossy(&buf).to_string();
file.read_to_string(&mut txt)?;
ClipboardValue::Text(txt) ClipboardValue::Text(txt)
} }
MimeTypeCategory::Image => { MimeTypeCategory::Image => {
let mut bytes = vec![]; let bytes = Bytes::from(&buf);
file.read_to_end(&mut bytes)?;
debug!("Read bytes: {}", bytes.len());
let bytes = Bytes::from(&bytes);
ClipboardValue::Image(bytes) ClipboardValue::Image(bytes)
} }
}; };
@ -214,14 +207,6 @@ impl DataControlDeviceHandler for Environment {
} }
if let Some(offer) = data_device.selection_offer() { if let Some(offer) = data_device.selection_offer() {
self.selection_offers
.push(SelectionOfferItem { offer, token: None });
let cur_offer = self
.selection_offers
.last_mut()
.expect("Failed to get current offer");
// clear prev // clear prev
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else { let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
lock!(self.clipboard).take(); lock!(self.clipboard).take();
@ -238,44 +223,19 @@ impl DataControlDeviceHandler for Environment {
}; };
debug!("Receiving mime type: {}", mime_type.value); debug!("Receiving mime type: {}", mime_type.value);
if let Ok(mut read_pipe) = offer.receive(mime_type.value.clone()) {
if let Ok(read_pipe) = cur_offer.offer.receive(mime_type.value.clone()) {
let offer_clone = cur_offer.offer.clone();
let tx = self.event_tx.clone(); let tx = self.event_tx.clone();
let clipboard = self.clipboard.clone(); let clipboard = self.clipboard.clone();
let token = spawn(async move {
self.loop_handle match Self::read_file(&mime_type, &mut read_pipe).await {
.insert_source(read_pipe, move |(), file, state| unsafe { Ok(item) => {
let item = state lock!(clipboard).replace(item.clone());
.selection_offers try_send!(tx, Event::Clipboard(item));
.iter() }
.position(|o| o.offer == offer_clone) Err(err) => error!("{err:?}"),
.map(|p| state.selection_offers.remove(p))
.expect("Failed to find selection offer item");
match Self::read_file(&mime_type, file.get_mut()) {
Ok(item) => {
lock!(clipboard).replace(item.clone());
try_send!(tx, Event::Clipboard(item));
}
Err(err) => error!("{err:?}"),
}
state
.loop_handle
.remove(item.token.expect("Missing item token"));
PostAction::Remove
});
match token {
Ok(token) => {
cur_offer.token.replace(token);
} }
Err(err) => error!("Failed to insert read pipe event: {err:?}"), });
}
} }
} }
} }

View file

@ -3,10 +3,10 @@ use crate::lock;
use nix::fcntl::OFlag; use nix::fcntl::OFlag;
use nix::unistd::pipe2; use nix::unistd::pipe2;
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError; use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
use smithay_client_toolkit::data_device_manager::ReadPipe;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::os::fd::AsFd; use std::os::fd::AsFd;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use tokio::net::unix::pipe::Receiver;
use tracing::trace; use tracing::trace;
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle}; use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{ use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{
@ -36,8 +36,8 @@ impl PartialEq for SelectionOffer {
} }
impl SelectionOffer { impl SelectionOffer {
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> { pub fn receive(&self, mime_type: String) -> Result<Receiver, DataOfferError> {
unsafe { receive(&self.data_offer, mime_type) }.map_err(DataOfferError::Io) receive(&self.data_offer, mime_type).map_err(DataOfferError::Io)
} }
} }
@ -169,14 +169,11 @@ where
/// ///
/// Fails if too many file descriptors were already open and a pipe /// Fails if too many file descriptors were already open and a pipe
/// could not be created. /// could not be created.
pub unsafe fn receive( pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result<Receiver> {
offer: &ZwlrDataControlOfferV1,
mime_type: String,
) -> std::io::Result<ReadPipe> {
// create a pipe // create a pipe
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?; let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK)?;
offer.receive(mime_type, writefd.as_fd()); offer.receive(mime_type, writefd.as_fd());
Ok(ReadPipe::from(readfd)) Ok(Receiver::from_owned_fd(readfd)?)
} }