Skip to content

Commit

Permalink
x11: Add support for getting clipboard contents
Browse files Browse the repository at this point in the history
This commit implements X11 clipboard transfers as specified in ICCCM.
This allows to get the contents of the clipboard.

X11/ICCM call the underlying mechanism "selections". This works with
ConvertSelection requests. The X11 server forwards these requests to the
selection owner which then uses SendEvent requests to answer. Thus, this
requires some way to blockingly wait for events. For this purpose, a
FIFO queue (VecDeque) of pending events is introduced. When waiting for
the "right" event, "wrong" events are pushed to this queue for later
processing.

Doing selection transfers requires an up-to-date-ish X11 timestamp.
Thus, the Application now tracks a timestamp and updates it whenever it
gets a newer timestamp.

As an unrelated refactor, this changes the X11 screen number to be saved
as usize instead of i32. This saves a couple of unnecessary casts. I
didn't want to do this too much in this commit, so screen_num() still
returns i32 instead of usize, even though I think it should be an usize.

Besides the above, the actual selection transfer is fully contained in
clipboard.rs.

The basic steps for getting the selection contents are:

- create an invisible window (used for the reply)
- send a ConvertSelection request with this window
- wait for a SelectionNotify event (at this point, the selection owner
  set a property on the window)
- get the contents of the property from the window
- in case the selection contents are larger than allowed for window
  properties, the property as type INCR. This indicates an "incremental
  transfer" which works as follows:
  - every time the property is deleted, the selection owner creates a
    new property with the next chunk of the property
  - thus, we have to wait for PropertyNotify events for our special
    window and react to them by getting the next piece of data

Signed-off-by: Uli Schlachter <[email protected]>
  • Loading branch information
psychon committed Jul 1, 2021
1 parent f19ddf1 commit d0c8e1b
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ You can find its changes [documented below](#070---2021-01-01).
- X11 backend now supports custom cursors ([#1801] by [@psychon])
- X11: Add support for transparent windows ([#1803] by [@psychon])
- `has_focus` method on `WidgetPod` ([#1825] by [@ForLoveOfCats])
- x11: Add support for getting clipboard contents ([#1805] by [@psychon])

### Changed

Expand Down Expand Up @@ -732,6 +733,7 @@ Last release without a changelog :(
[#1801]: https://github.com/linebender/druid/pull/1800
[#1802]: https://github.com/linebender/druid/pull/1802
[#1803]: https://github.com/linebender/druid/pull/1803
[#1805]: https://github.com/linebender/druid/pull/1805
[#1820]: https://github.com/linebender/druid/pull/1820
[#1825]: https://github.com/linebender/druid/pull/1825

Expand Down
55 changes: 42 additions & 13 deletions druid-shell/src/platform/x11/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

//! X11 implementation of features at the application scope.
use std::cell::RefCell;
use std::collections::HashMap;
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::convert::{TryFrom, TryInto};
use std::os::unix::io::RawFd;
use std::rc::Rc;
Expand All @@ -27,7 +27,7 @@ use x11rb::protocol::present::ConnectionExt as _;
use x11rb::protocol::render::{self, ConnectionExt as _, Pictformat};
use x11rb::protocol::xfixes::ConnectionExt as _;
use x11rb::protocol::xproto::{
self, ConnectionExt, CreateWindowAux, EventMask, Visualtype, WindowClass,
self, ConnectionExt, CreateWindowAux, EventMask, Timestamp, Visualtype, WindowClass,
};
use x11rb::protocol::Event;
use x11rb::resource_manager::Database as ResourceDb;
Expand Down Expand Up @@ -58,6 +58,8 @@ pub(crate) struct Application {
root_visual_type: Visualtype,
/// The visual for windows with transparent backgrounds, if supported
argb_visual_type: Option<Visualtype>,
/// Pending events that need to be handled later
pending_events: Rc<RefCell<VecDeque<Event>>>,

/// The X11 resource database used to query dpi.
pub(crate) rdb: Rc<ResourceDb>,
Expand All @@ -72,7 +74,7 @@ pub(crate) struct Application {
/// In practice multiple physical monitor drawing areas are present on a single screen.
/// This is achieved via various X server extensions (XRandR/Xinerama/TwinView),
/// with XRandR seeming like the best choice.
screen_num: i32, // Needs a container when no longer const
screen_num: usize, // Needs a container when no longer const
/// The X11 window id of this `Application`.
///
/// This is an input-only non-visual X11 window that is created first during initialization,
Expand All @@ -93,6 +95,8 @@ pub(crate) struct Application {
present_opcode: Option<u8>,
/// Support for the render extension in at least version 0.5?
render_argb32_pictformat_cursor: Option<Pictformat>,
/// Newest timestamp that we received
timestamp: Rc<Cell<Timestamp>>,
}

/// The mutable `Application` state.
Expand Down Expand Up @@ -127,7 +131,7 @@ impl Application {
let (conn, screen_num) = XCBConnection::connect(None)?;
let rdb = Rc::new(ResourceDb::new_from_default(&conn)?);
let connection = Rc::new(conn);
let window_id = Application::create_event_window(&connection, screen_num as i32)?;
let window_id = Application::create_event_window(&connection, screen_num)?;
let state = Rc::new(RefCell::new(State {
quitting: false,
windows: HashMap::new(),
Expand Down Expand Up @@ -208,7 +212,7 @@ impl Application {
Ok(Application {
connection,
rdb,
screen_num: screen_num as i32,
screen_num,
window_id,
state,
idle_read,
Expand All @@ -217,8 +221,10 @@ impl Application {
present_opcode,
root_visual_type,
argb_visual_type,
pending_events: Default::default(),
marker: std::marker::PhantomData,
render_argb32_pictformat_cursor,
timestamp: Rc::new(Cell::new(x11rb::CURRENT_TIME)),
})
}

Expand Down Expand Up @@ -275,12 +281,12 @@ impl Application {
self.render_argb32_pictformat_cursor
}

fn create_event_window(conn: &Rc<XCBConnection>, screen_num: i32) -> Result<u32, Error> {
fn create_event_window(conn: &Rc<XCBConnection>, screen_num: usize) -> Result<u32, Error> {
let id = conn.generate_id()?;
let setup = conn.setup();
let screen = setup
.roots
.get(screen_num as usize)
.get(screen_num)
.ok_or_else(|| anyhow!("invalid screen num: {}", screen_num))?;

// Create the actual window
Expand Down Expand Up @@ -341,7 +347,7 @@ impl Application {

#[inline]
pub(crate) fn screen_num(&self) -> i32 {
self.screen_num
self.screen_num as _
}

#[inline]
Expand Down Expand Up @@ -373,6 +379,21 @@ impl Application {

/// Returns `Ok(true)` if we want to exit the main loop.
fn handle_event(&self, ev: &Event) -> Result<bool, Error> {
if ev.server_generated() {
// Update our latest timestamp
let timestamp = match ev {
Event::KeyPress(ev) => ev.time,
Event::KeyRelease(ev) => ev.time,
Event::ButtonPress(ev) => ev.time,
Event::ButtonRelease(ev) => ev.time,
Event::MotionNotify(ev) => ev.time,
Event::EnterNotify(ev) => ev.time,
Event::LeaveNotify(ev) => ev.time,
Event::PropertyNotify(ev) => ev.time,
_ => self.timestamp.get(),
};
self.timestamp.set(timestamp);
}
match ev {
// NOTE: When adding handling for any of the following events,
// there must be a check against self.window_id
Expand Down Expand Up @@ -512,10 +533,15 @@ impl Application {

self.connection.flush()?;

// Deal with pending events
let mut event = self.pending_events.borrow_mut().pop_front();

// Before we poll on the connection's file descriptor, check whether there are any
// events ready. It could be that XCB has some events in its internal buffers because
// of something that happened during the idle loop.
let mut event = self.connection.poll_for_event()?;
if event.is_none() {
event = self.connection.poll_for_event()?;
}

if event.is_none() {
poll_with_timeout(
Expand Down Expand Up @@ -605,9 +631,12 @@ impl Application {
}

pub fn clipboard(&self) -> Clipboard {
// TODO(x11/clipboard): implement Application::clipboard
tracing::warn!("Application::clipboard is currently unimplemented for X11 platforms.");
Clipboard {}
Clipboard::new(
Rc::clone(&self.connection),
self.screen_num,
Rc::clone(&self.pending_events),
Rc::clone(&self.timestamp),
)
}

pub fn get_locale() -> String {
Expand Down
Loading

0 comments on commit d0c8e1b

Please sign in to comment.