linux(x11): Add support for pasting images from clipboard (#29387)
Closes: https://github.com/zed-industries/zed/pull/29177#issuecomment-2823359242 Removes dependency on [quininer/x11-clipboard](https://github.com/quininer/x11-clipboard) as it is in [maintenance mode](https://github.com/quininer/x11-clipboard/issues/19). X11 clipboard functionality is now built-in to GPUI which was accomplished by stripping the non-x11-related code/abstractions from [1Password/arboard](https://github.com/1Password/arboard) and extending it to support all image formats already supported by GPUI on wayland and macos. A benefit of switching over to the `arboard` implementation, is that we now make an attempt to have an X11 "clipboard manager" (if available - something the user has to setup themselves) save the contents of clipboard (if the last copy operation was within Zed) so that the copied contents can still be pasted once Zed has completely stopped. Release Notes: - Linux(X11): Add support for pasting images from clipboard
This commit is contained in:
@@ -1479,6 +1479,21 @@ pub enum ImageFormat {
|
||||
Tiff,
|
||||
}
|
||||
|
||||
impl ImageFormat {
|
||||
/// Returns the mime type for the ImageFormat
|
||||
pub const fn mime_type(self) -> &'static str {
|
||||
match self {
|
||||
ImageFormat::Png => "image/png",
|
||||
ImageFormat::Jpeg => "image/jpeg",
|
||||
ImageFormat::Webp => "image/webp",
|
||||
ImageFormat::Gif => "image/gif",
|
||||
ImageFormat::Svg => "image/svg+xml",
|
||||
ImageFormat::Bmp => "image/bmp",
|
||||
ImageFormat::Tiff => "image/tiff",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An image, with a format and certain bytes
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Image {
|
||||
|
||||
@@ -119,15 +119,7 @@ impl<T: ReceiveData> DataOffer<T> {
|
||||
|
||||
fn read_image(&self, connection: &Connection) -> Option<ClipboardItem> {
|
||||
for format in ImageFormat::iter() {
|
||||
let mime_type = match format {
|
||||
ImageFormat::Png => "image/png",
|
||||
ImageFormat::Jpeg => "image/jpeg",
|
||||
ImageFormat::Webp => "image/webp",
|
||||
ImageFormat::Gif => "image/gif",
|
||||
ImageFormat::Svg => "image/svg+xml",
|
||||
ImageFormat::Bmp => "image/bmp",
|
||||
ImageFormat::Tiff => "image/tiff",
|
||||
};
|
||||
let mime_type = format.mime_type();
|
||||
if !self.has_mime_type(mime_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod client;
|
||||
mod clipboard;
|
||||
mod display;
|
||||
mod event;
|
||||
mod window;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::platform::scap_screen_capture::scap_screen_sources;
|
||||
use core::str;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -41,8 +40,9 @@ use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSIO
|
||||
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask, STATE_LAYOUT_EFFECTIVE};
|
||||
|
||||
use super::{
|
||||
ButtonOrScroll, ScrollDirection, button_or_scroll_from_event_detail, get_valuator_axis_index,
|
||||
modifiers_from_state, pressed_button_from_mask,
|
||||
ButtonOrScroll, ScrollDirection, button_or_scroll_from_event_detail,
|
||||
clipboard::{self, Clipboard},
|
||||
get_valuator_axis_index, modifiers_from_state, pressed_button_from_mask,
|
||||
};
|
||||
use super::{X11Display, X11WindowStatePtr, XcbAtoms};
|
||||
use super::{XimCallbackEvent, XimHandler};
|
||||
@@ -56,6 +56,7 @@ use crate::platform::{
|
||||
reveal_path_internal,
|
||||
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
|
||||
},
|
||||
scap_screen_capture::scap_screen_sources,
|
||||
};
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke,
|
||||
@@ -201,7 +202,7 @@ pub struct X11ClientState {
|
||||
pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>,
|
||||
|
||||
pub(crate) common: LinuxCommon,
|
||||
pub(crate) clipboard: x11_clipboard::Clipboard,
|
||||
pub(crate) clipboard: Clipboard,
|
||||
pub(crate) clipboard_item: Option<ClipboardItem>,
|
||||
pub(crate) xdnd_state: Xdnd,
|
||||
}
|
||||
@@ -388,7 +389,7 @@ impl X11Client {
|
||||
.reply()
|
||||
.unwrap();
|
||||
|
||||
let clipboard = x11_clipboard::Clipboard::new().unwrap();
|
||||
let clipboard = Clipboard::new().unwrap();
|
||||
|
||||
let xcb_connection = Rc::new(xcb_connection);
|
||||
|
||||
@@ -1496,39 +1497,36 @@ impl LinuxClient for X11Client {
|
||||
let state = self.0.borrow_mut();
|
||||
state
|
||||
.clipboard
|
||||
.store(
|
||||
state.clipboard.setter.atoms.primary,
|
||||
state.clipboard.setter.atoms.utf8_string,
|
||||
item.text().unwrap_or_default().as_bytes(),
|
||||
.set_text(
|
||||
std::borrow::Cow::Owned(item.text().unwrap_or_default()),
|
||||
clipboard::ClipboardKind::Primary,
|
||||
clipboard::WaitConfig::None,
|
||||
)
|
||||
.ok();
|
||||
.context("Failed to write to clipboard (primary)")
|
||||
.log_with_level(log::Level::Debug);
|
||||
}
|
||||
|
||||
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
|
||||
let mut state = self.0.borrow_mut();
|
||||
state
|
||||
.clipboard
|
||||
.store(
|
||||
state.clipboard.setter.atoms.clipboard,
|
||||
state.clipboard.setter.atoms.utf8_string,
|
||||
item.text().unwrap_or_default().as_bytes(),
|
||||
.set_text(
|
||||
std::borrow::Cow::Owned(item.text().unwrap_or_default()),
|
||||
clipboard::ClipboardKind::Clipboard,
|
||||
clipboard::WaitConfig::None,
|
||||
)
|
||||
.ok();
|
||||
.context("Failed to write to clipboard (clipboard)")
|
||||
.log_with_level(log::Level::Debug);
|
||||
state.clipboard_item.replace(item);
|
||||
}
|
||||
|
||||
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
|
||||
let state = self.0.borrow_mut();
|
||||
state
|
||||
return state
|
||||
.clipboard
|
||||
.load(
|
||||
state.clipboard.getter.atoms.primary,
|
||||
state.clipboard.getter.atoms.utf8_string,
|
||||
state.clipboard.getter.atoms.property,
|
||||
Duration::from_secs(3),
|
||||
)
|
||||
.map(|text| crate::ClipboardItem::new_string(String::from_utf8(text).unwrap()))
|
||||
.ok()
|
||||
.get_any(clipboard::ClipboardKind::Primary)
|
||||
.context("Failed to read from clipboard (primary)")
|
||||
.log_with_level(log::Level::Debug);
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||
@@ -1537,26 +1535,15 @@ impl LinuxClient for X11Client {
|
||||
// which has metadata attached.
|
||||
if state
|
||||
.clipboard
|
||||
.setter
|
||||
.connection
|
||||
.get_selection_owner(state.clipboard.setter.atoms.clipboard)
|
||||
.ok()
|
||||
.and_then(|r| r.reply().ok())
|
||||
.map(|reply| reply.owner == state.clipboard.setter.window)
|
||||
.unwrap_or(false)
|
||||
.is_owner(clipboard::ClipboardKind::Clipboard)
|
||||
{
|
||||
return state.clipboard_item.clone();
|
||||
}
|
||||
state
|
||||
return state
|
||||
.clipboard
|
||||
.load(
|
||||
state.clipboard.getter.atoms.clipboard,
|
||||
state.clipboard.getter.atoms.utf8_string,
|
||||
state.clipboard.getter.atoms.property,
|
||||
Duration::from_secs(3),
|
||||
)
|
||||
.map(|text| crate::ClipboardItem::new_string(String::from_utf8(text).unwrap()))
|
||||
.ok()
|
||||
.get_any(clipboard::ClipboardKind::Clipboard)
|
||||
.context("Failed to read from clipboard (clipboard)")
|
||||
.log_with_level(log::Level::Debug);
|
||||
}
|
||||
|
||||
fn run(&self) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user