mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-08-17 06:41:03 +02:00
refactor(wayland): update to 0.30.0
This is pretty much a rewrite of the Wayland client code for `wayland-client` and `wayland-protocols` v0.30.0, and `smithay-client-toolkit` v0.17.0
This commit is contained in:
parent
5c18ec8ba0
commit
7f46cb4976
23 changed files with 1779 additions and 1338 deletions
|
@ -3,28 +3,25 @@ pub mod manager;
|
|||
pub mod offer;
|
||||
pub mod source;
|
||||
|
||||
use super::Env;
|
||||
use crate::clients::wayland::DData;
|
||||
use crate::send;
|
||||
use color_eyre::Report;
|
||||
use device::{DataControlDevice, DataControlDeviceEvent};
|
||||
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
||||
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
||||
use self::source::DataControlSourceHandler;
|
||||
use crate::clients::wayland::Environment;
|
||||
use crate::{lock, send};
|
||||
use device::DataControlDevice;
|
||||
use glib::Bytes;
|
||||
use manager::{DataControlDeviceHandling, DataControlDeviceStatusListener};
|
||||
use smithay_client_toolkit::data_device::WritePipe;
|
||||
use smithay_client_toolkit::environment::Environment;
|
||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||
use smithay_client_toolkit::MissingGlobal;
|
||||
use source::DataControlSource;
|
||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{debug, error, trace};
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::DispatchData;
|
||||
use tracing::{debug, error};
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;
|
||||
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
|
@ -34,6 +31,11 @@ fn get_id() -> usize {
|
|||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub struct SelectionOfferItem {
|
||||
offer: SelectionOffer,
|
||||
token: Option<RegistrationToken>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct ClipboardItem {
|
||||
pub id: usize,
|
||||
|
@ -47,77 +49,27 @@ impl PartialEq<Self> for ClipboardItem {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum ClipboardValue {
|
||||
Text(String),
|
||||
Image(Bytes),
|
||||
Other,
|
||||
}
|
||||
|
||||
impl DataControlDeviceHandling for Env {
|
||||
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
||||
where
|
||||
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
||||
{
|
||||
self.data_control_device.listen(f)
|
||||
}
|
||||
|
||||
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
||||
where
|
||||
F: FnOnce(&DataControlDevice),
|
||||
{
|
||||
self.data_control_device.with_data_control_device(seat, f)
|
||||
}
|
||||
|
||||
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
||||
where
|
||||
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
||||
{
|
||||
self.data_control_device.create_source(mime_types, callback)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_to_clipboard<E>(
|
||||
env: &Environment<E>,
|
||||
seat: &WlSeat,
|
||||
item: &ClipboardItem,
|
||||
) -> Result<(), MissingGlobal>
|
||||
where
|
||||
E: DataControlDeviceHandling,
|
||||
{
|
||||
debug!("Copying item with id {} [{}]", item.id, item.mime_type);
|
||||
trace!("Copying: {item:?}");
|
||||
|
||||
let item = item.clone();
|
||||
|
||||
env.with_inner(|env| {
|
||||
let mime_types = vec![INTERNAL_MIME_TYPE.to_string(), item.mime_type];
|
||||
let source = env.create_source(mime_types, move |mime_type, mut pipe, _ddata| {
|
||||
debug!(
|
||||
"Triggering source callback for item with id {} [{}]",
|
||||
item.id, mime_type
|
||||
);
|
||||
|
||||
// FIXME: Not working for large (buffered) values in xwayland
|
||||
let bytes = match &item.value {
|
||||
ClipboardValue::Text(text) => text.as_bytes(),
|
||||
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
||||
ClipboardValue::Other => panic!(
|
||||
"{:?}",
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Attempted to copy unsupported mime type",
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
if let Err(err) = pipe.write_all(bytes) {
|
||||
error!("{err:?}");
|
||||
impl Debug for ClipboardValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Text(text) => text.clone(),
|
||||
Self::Image(bytes) => {
|
||||
format!("[{} Bytes]", bytes.len())
|
||||
}
|
||||
Self::Other => "[Unknown]".to_string(),
|
||||
}
|
||||
});
|
||||
|
||||
env.with_data_control_device(seat, |device| device.set_selection(&source))
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -133,126 +85,230 @@ enum MimeTypeCategory {
|
|||
}
|
||||
|
||||
impl MimeType {
|
||||
fn parse(mime_types: &[String]) -> Option<Self> {
|
||||
mime_types
|
||||
.iter()
|
||||
.map(|s| s.to_lowercase())
|
||||
.find_map(|mime_type| match mime_type.as_str() {
|
||||
"text"
|
||||
| "string"
|
||||
| "utf8_string"
|
||||
| "text/plain"
|
||||
| "text/plain;charset=utf-8"
|
||||
| "text/plain;charset=iso-8859-1"
|
||||
| "text/plain;charset=us-ascii"
|
||||
| "text/plain;charset=unicode" => Some(Self {
|
||||
value: mime_type,
|
||||
category: MimeTypeCategory::Text,
|
||||
}),
|
||||
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
||||
| "image/x-bmp" | "image/icon" => Some(Self {
|
||||
value: mime_type,
|
||||
category: MimeTypeCategory::Image,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
fn parse(mime_type: &str) -> Option<Self> {
|
||||
match mime_type.to_lowercase().as_str() {
|
||||
"text"
|
||||
| "string"
|
||||
| "utf8_string"
|
||||
| "text/plain"
|
||||
| "text/plain;charset=utf-8"
|
||||
| "text/plain;charset=iso-8859-1"
|
||||
| "text/plain;charset=us-ascii"
|
||||
| "text/plain;charset=unicode" => Some(Self {
|
||||
value: mime_type.to_string(),
|
||||
category: MimeTypeCategory::Text,
|
||||
}),
|
||||
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
||||
| "image/x-bmp" | "image/icon" => Some(Self {
|
||||
value: mime_type.to_string(),
|
||||
category: MimeTypeCategory::Image,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_multiple(mime_types: &[String]) -> Option<Self> {
|
||||
mime_types.iter().find_map(|mime| Self::parse(mime))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_offer(
|
||||
event: DataControlDeviceEvent,
|
||||
handle: &LoopHandle<DData>,
|
||||
tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||
mut ddata: DispatchData,
|
||||
) {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Could not get epoch, system time is probably very wrong")
|
||||
.as_nanos();
|
||||
impl Environment {
|
||||
pub fn copy_to_clipboard(&mut self, item: Arc<ClipboardItem>, qh: &QueueHandle<Self>) {
|
||||
debug!("Copying item to clipboard");
|
||||
|
||||
let offer = event.0;
|
||||
// TODO: Proper device tracking
|
||||
let device = self.data_control_devices.first();
|
||||
if let Some(device) = device {
|
||||
let source = self
|
||||
.data_control_device_manager_state
|
||||
.create_copy_paste_source(qh, [item.mime_type.as_str()]);
|
||||
|
||||
let ddata = ddata
|
||||
.get::<DData>()
|
||||
.expect("Expected dispatch data to exist");
|
||||
source.set_selection(&device.device);
|
||||
self.copy_paste_sources.push(source);
|
||||
|
||||
let handle2 = handle.clone();
|
||||
lock!(self.clipboard).replace(item);
|
||||
}
|
||||
}
|
||||
|
||||
let res = offer.with_mime_types(|mime_types| {
|
||||
debug!("Offer mime types: {mime_types:?}");
|
||||
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
||||
let value = match mime_type.category {
|
||||
MimeTypeCategory::Text => {
|
||||
let mut txt = String::new();
|
||||
file.read_to_string(&mut txt)?;
|
||||
|
||||
ClipboardValue::Text(txt)
|
||||
}
|
||||
MimeTypeCategory::Image => {
|
||||
let mut bytes = vec![];
|
||||
file.read_to_end(&mut bytes)?;
|
||||
let bytes = Bytes::from(&bytes);
|
||||
|
||||
ClipboardValue::Image(bytes)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ClipboardItem {
|
||||
id: get_id(),
|
||||
value,
|
||||
mime_type: mime_type.value.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DataControlDeviceHandler for Environment {
|
||||
fn selection(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
data_device: DataControlDevice,
|
||||
) {
|
||||
debug!("Handler received selection event");
|
||||
|
||||
let mime_types = data_device.selection_mime_types();
|
||||
|
||||
if mime_types.contains(&INTERNAL_MIME_TYPE.to_string()) {
|
||||
debug!("Skipping value provided by bar");
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
let mime_type = MimeType::parse(mime_types);
|
||||
debug!("Detected mime type: {mime_type:?}");
|
||||
if let Some(offer) = data_device.selection_offer() {
|
||||
self.selection_offers
|
||||
.push(SelectionOfferItem { offer, token: None });
|
||||
|
||||
match mime_type {
|
||||
Some(mime_type) => {
|
||||
debug!("[{timestamp}] Sending clipboard read request ({mime_type:?})");
|
||||
let read_pipe = offer.receive(mime_type.value.clone())?;
|
||||
let source = handle.insert_source(read_pipe, move |(), file, ddata| {
|
||||
debug!(
|
||||
"[{timestamp}] Reading clipboard contents ({:?})",
|
||||
&mime_type.category
|
||||
);
|
||||
match read_file(&mime_type, file) {
|
||||
Ok(item) => {
|
||||
send!(tx, Arc::new(item));
|
||||
}
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
let cur_offer = self
|
||||
.selection_offers
|
||||
.last_mut()
|
||||
.expect("Failed to get current offer");
|
||||
|
||||
if let Some(src) = ddata.offer_tokens.remove(×tamp) {
|
||||
handle2.remove(src);
|
||||
}
|
||||
})?;
|
||||
|
||||
ddata.offer_tokens.insert(timestamp, source);
|
||||
}
|
||||
None => {
|
||||
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
|
||||
lock!(self.clipboard).take();
|
||||
// send an event so the clipboard module is aware it's changed
|
||||
send!(
|
||||
tx,
|
||||
self.clipboard_tx,
|
||||
Arc::new(ClipboardItem {
|
||||
id: usize::MAX,
|
||||
mime_type: String::new(),
|
||||
value: ClipboardValue::Other
|
||||
})
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(read_pipe) = cur_offer.offer.receive(mime_type.value.clone()) {
|
||||
let offer_clone = cur_offer.offer.clone();
|
||||
|
||||
let tx = self.clipboard_tx.clone();
|
||||
let clipboard = self.clipboard.clone();
|
||||
|
||||
let token = self
|
||||
.loop_handle
|
||||
.insert_source(read_pipe, move |_, file, state| {
|
||||
let item = state
|
||||
.selection_offers
|
||||
.iter()
|
||||
.position(|o| o.offer == offer_clone)
|
||||
.map(|p| state.selection_offers.remove(p))
|
||||
.expect("Failed to find selection offer item");
|
||||
|
||||
match Self::read_file(&mime_type, file) {
|
||||
Ok(item) => {
|
||||
let item = Arc::new(item);
|
||||
lock!(clipboard).replace(item.clone());
|
||||
send!(tx, item);
|
||||
}
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
|
||||
state
|
||||
.loop_handle
|
||||
.remove(item.token.expect("Missing item token"));
|
||||
});
|
||||
|
||||
match token {
|
||||
Ok(token) => {
|
||||
cur_offer.token.replace(token);
|
||||
}
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), Report>(())
|
||||
});
|
||||
|
||||
if let Err(err) = res {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
||||
let value = match mime_type.category {
|
||||
MimeTypeCategory::Text => {
|
||||
let mut txt = String::new();
|
||||
file.read_to_string(&mut txt)?;
|
||||
|
||||
ClipboardValue::Text(txt)
|
||||
}
|
||||
MimeTypeCategory::Image => {
|
||||
let mut bytes = vec![];
|
||||
file.read_to_end(&mut bytes)?;
|
||||
let bytes = Bytes::from(&bytes);
|
||||
|
||||
ClipboardValue::Image(bytes)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ClipboardItem {
|
||||
id: get_id(),
|
||||
value,
|
||||
mime_type: mime_type.value.clone(),
|
||||
})
|
||||
impl DataControlOfferHandler for Environment {
|
||||
fn offer(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_offer: &mut DataControlDeviceOffer,
|
||||
_mime_type: String,
|
||||
) {
|
||||
debug!("Handler received offer");
|
||||
}
|
||||
}
|
||||
|
||||
impl DataControlSourceHandler for Environment {
|
||||
fn accept_mime(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_source: &ZwlrDataControlSourceV1,
|
||||
mime: Option<String>,
|
||||
) {
|
||||
debug!("Accepted mime type: {mime:?}");
|
||||
}
|
||||
|
||||
fn send_request(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
source: &ZwlrDataControlSourceV1,
|
||||
mime: String,
|
||||
write_pipe: WritePipe,
|
||||
) {
|
||||
debug!("Handler received source send request event");
|
||||
|
||||
if let Some(item) = lock!(self.clipboard).clone() {
|
||||
let fd = OwnedFd::from(write_pipe);
|
||||
if let Some(_source) = self
|
||||
.copy_paste_sources
|
||||
.iter_mut()
|
||||
.find(|s| s.inner() == source && MimeType::parse(&mime).is_some())
|
||||
{
|
||||
let mut file = File::from(fd);
|
||||
|
||||
// FIXME: Not working for large (buffered) values in xwayland
|
||||
// Might be something strange going on with byte count?
|
||||
let bytes = match &item.value {
|
||||
ClipboardValue::Text(text) => text.as_bytes(),
|
||||
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
||||
ClipboardValue::Other => panic!(
|
||||
"{:?}",
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Attempted to copy unsupported mime type",
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
if let Err(err) = file.write_all(bytes) {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cancelled(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
source: &ZwlrDataControlSourceV1,
|
||||
) {
|
||||
debug!("Handler received source cancelled event");
|
||||
|
||||
self.copy_paste_sources
|
||||
.iter()
|
||||
.position(|s| s.inner() == source)
|
||||
.map(|pos| self.copy_paste_sources.remove(pos));
|
||||
source.destroy();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue