Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

X11: Add timeout for getting clipboard contents #1866

Merged
merged 2 commits into from
Jul 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ You can find its changes [documented below](#070---2021-01-01).
- X11: Added support for `get_monitors` ([#1804] by [@psychon])
- x11: Remove some unnecessary casts ([#1851] by [@psychon])
- `has_focus` method on `WidgetPod` ([#1825] by [@ForLoveOfCats])
- x11: Add support for getting and setting clipboard contents ([#1805] and [#1851] by [@psychon])
- x11: Add support for getting and setting clipboard contents ([#1805], [#1851], and [#1866] by [@psychon])
- Linux extension: primary_clipboard ([#1843] by [@Maan2003])

### Changed
Expand Down Expand Up @@ -746,6 +746,7 @@ Last release without a changelog :(
[#1851]: https://github.com/linebender/druid/pull/1851
[#1861]: https://github.com/linebender/druid/pull/1861
[#1863]: https://github.com/linebender/druid/pull/1863
[#1866]: https://github.com/linebender/druid/pull/1866

[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
[0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0
Expand Down
57 changes: 55 additions & 2 deletions druid-shell/src/backend/x11/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::rc::Rc;
use std::time::{Duration, Instant};

use x11rb::connection::{Connection, RequestConnection};
use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};
Expand Down Expand Up @@ -263,6 +264,8 @@ impl ClipboardState {
{
debug!("Getting clipboard contents in format {}", format);

let deadline = Instant::now() + Duration::from_secs(5);

let conn = &*self.connection;
let format_atom = conn.intern_atom(false, format.as_bytes())?.reply()?.atom;

Expand All @@ -280,7 +283,7 @@ impl ClipboardState {
// Now wait for the selection notify event
conn.flush()?;
let notify = loop {
match conn.wait_for_event()? {
match wait_for_event_with_deadline(conn, deadline)? {
Event::SelectionNotify(notify) if notify.requestor == window.window => {
break notify
}
Expand Down Expand Up @@ -326,7 +329,7 @@ impl ClipboardState {
conn.flush()?;
let mut value = Vec::new();
loop {
match conn.wait_for_event()? {
match wait_for_event_with_deadline(conn, deadline)? {
Event::PropertyNotify(notify)
if (notify.window, notify.state) == (window.window, Property::NEW_VALUE) =>
{
Expand Down Expand Up @@ -648,3 +651,53 @@ fn reject_transfer(
conn.send_event(false, event.requestor, EventMask::NO_EVENT, &event)?;
Ok(())
}

/// Wait for an X11 event or return a timeout error if the given deadline is in the past.
fn wait_for_event_with_deadline(
conn: &XCBConnection,
deadline: Instant,
) -> Result<Event, ConnectionError> {
use nix::poll::{poll, PollFd, PollFlags};
use std::os::raw::c_int;
use std::os::unix::io::AsRawFd;

loop {
// Is there already an event?
if let Some(event) = conn.poll_for_event()? {
return Ok(event);
}

// Are we past the deadline?
let now = Instant::now();
if deadline <= now {
return Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"Timeout while waiting for selection owner to reply",
)
.into());
}

// Use poll() to wait for the socket to become readable.
let mut poll_fds = [PollFd::new(conn.as_raw_fd(), PollFlags::POLLIN)];
let poll_timeout = c_int::try_from(deadline.duration_since(now).as_millis())
.unwrap_or(c_int::max_value() - 1)
// The above rounds down, but we don't want to wake up to early, so add one
.saturating_add(1);

// Wait for the socket to be readable via poll() and try again
match poll(&mut poll_fds, poll_timeout) {
Ok(_) => {}
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {}
Err(e) => return Err(nix_error_to_io(e).into()),
}
}
}

fn nix_error_to_io(e: nix::Error) -> std::io::Error {
use std::io::{Error, ErrorKind};
match e {
nix::Error::Sys(errno) => errno.into(),
nix::Error::InvalidPath | nix::Error::InvalidUtf8 => Error::new(ErrorKind::InvalidInput, e),
nix::Error::UnsupportedOperation => std::io::Error::new(ErrorKind::Other, e),
}
}