mirror of
https://github.com/wez/wezterm.git
synced 2024-11-10 06:34:17 +03:00
use the clipboard crate
This removes some tricksy code in favor of a third party crate, which unlocks the clipboard on windows. I'm not sure how well this will work on the pure x11 impl yet.
This commit is contained in:
parent
3a4673f22c
commit
d69c718a73
@ -28,6 +28,7 @@ addons:
|
||||
- binutils-dev
|
||||
- cmake
|
||||
- gcc
|
||||
- xorg-dev
|
||||
- libcurl4-openssl-dev
|
||||
- libdw-dev
|
||||
- libegl1-mesa-dev
|
||||
|
@ -20,6 +20,7 @@ unicode-width = "~0.1"
|
||||
directories = "~1.0"
|
||||
font-loader = "0.8"
|
||||
rusttype = "0.7"
|
||||
clipboard = "0.2"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
harfbuzz-sys = "~0.2"
|
||||
|
1
get-deps
1
get-deps
@ -19,6 +19,7 @@ case `lsb_release -ds` in
|
||||
Ubuntu*|Debian*)
|
||||
apt-get install -y \
|
||||
cmake \
|
||||
xorg-dev \
|
||||
bsdutils \
|
||||
libxcb-icccm4-dev \
|
||||
libxcb-ewmh-dev \
|
||||
|
@ -1,35 +0,0 @@
|
||||
use failure::Error;
|
||||
use guiloop::{GuiSender, WindowId};
|
||||
|
||||
mod none;
|
||||
#[cfg(any(target_os = "macos", windows))]
|
||||
pub use self::none::NoClipboard as Clipboard;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
mod x11;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub use self::x11::Clipboard;
|
||||
|
||||
/// A fragment of the clipboard data received from another
|
||||
/// app during paste.
|
||||
#[derive(Debug)]
|
||||
pub enum Paste {
|
||||
/// The whole content of the paste is available
|
||||
All(String),
|
||||
/// Someone else now owns the selection. You should
|
||||
/// clear the selection locally.
|
||||
Cleared,
|
||||
/// The clipboard window has initialized successfully
|
||||
Running,
|
||||
}
|
||||
|
||||
/// Abstracts away system specific clipboard implementation details.
|
||||
pub trait ClipboardImpl {
|
||||
fn new(wakeup: GuiSender<WindowId>, window_id: WindowId) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn set_clipboard(&self, text: Option<String>) -> Result<(), Error>;
|
||||
fn get_clipboard(&self) -> Result<String, Error>;
|
||||
fn try_get_paste(&self) -> Result<Option<Paste>, Error>;
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
use clipboard::{ClipboardImpl, Paste};
|
||||
use failure::Error;
|
||||
use guiloop::{GuiSender, WindowId};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
|
||||
/// A no-op clipboard implementation
|
||||
#[allow(dead_code)]
|
||||
pub struct NoClipboard {
|
||||
receiver: Receiver<Paste>,
|
||||
sender: Sender<Paste>,
|
||||
}
|
||||
|
||||
impl ClipboardImpl for NoClipboard {
|
||||
fn new(_wakeup: GuiSender<WindowId>, _window_id: WindowId) -> Result<Self, Error> {
|
||||
let (sender, receiver) = channel();
|
||||
Ok(Self { sender, receiver })
|
||||
}
|
||||
|
||||
fn set_clipboard(&self, _text: Option<String>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_clipboard(&self) -> Result<String, Error> {
|
||||
Ok("".into())
|
||||
}
|
||||
|
||||
fn try_get_paste(&self) -> Result<Option<Paste>, Error> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
@ -1,459 +0,0 @@
|
||||
//! Interface with the X11 clipboard/selection
|
||||
//! Check out <https://tronche.com/gui/x/icccm/sec-2.html> for some deep and complex
|
||||
//! background on what's happening in here.
|
||||
use failure::{self, Error};
|
||||
use guiloop::{GuiSender, WindowId};
|
||||
use mio::unix::EventedFd;
|
||||
use mio::{Events, Poll, PollOpt, Ready, Token};
|
||||
use mio_extras::channel::{channel as mio_channel, Receiver as MioReceiver, Sender as MioSender};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
|
||||
use std::thread::{self, JoinHandle};
|
||||
use std::time::Duration;
|
||||
use xcb;
|
||||
use xcb_util;
|
||||
|
||||
use clipboard::{ClipboardImpl, Paste};
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "ClipTerminated")]
|
||||
struct ClipTerminated;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ClipRequest {
|
||||
/// tell the system that we want to set (or clear) the
|
||||
/// selection to the supplied parameter.
|
||||
SetClipboard(Option<String>),
|
||||
/// Ask the system to send us the clipboard contents
|
||||
RequestClipboard,
|
||||
/// Ask the clipboard manager to shutdown
|
||||
Terminate,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
/// if we own the clipboard, here's its string content
|
||||
owned: Option<String>,
|
||||
receiver: MioReceiver<ClipRequest>,
|
||||
sender: Sender<Paste>,
|
||||
conn: xcb::Connection,
|
||||
window_id: xcb::xproto::Window,
|
||||
atom_utf8_string: xcb::Atom,
|
||||
atom_xsel_data: xcb::Atom,
|
||||
atom_targets: xcb::Atom,
|
||||
atom_clipboard: xcb::Atom,
|
||||
wakeup: GuiSender<WindowId>,
|
||||
wakeup_window_id: WindowId,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new(
|
||||
receiver: MioReceiver<ClipRequest>,
|
||||
sender: Sender<Paste>,
|
||||
wakeup: GuiSender<WindowId>,
|
||||
wakeup_window_id: WindowId,
|
||||
) -> Result<Self, Error> {
|
||||
let (conn, screen) = xcb::Connection::connect(None)?;
|
||||
|
||||
let atom_utf8_string = xcb::intern_atom(&conn, false, "UTF8_STRING")
|
||||
.get_reply()?
|
||||
.atom();
|
||||
let atom_xsel_data = xcb::intern_atom(&conn, false, "XSEL_DATA")
|
||||
.get_reply()?
|
||||
.atom();
|
||||
let atom_targets = xcb::intern_atom(&conn, false, "TARGETS")
|
||||
.get_reply()?
|
||||
.atom();
|
||||
let atom_clipboard = xcb::intern_atom(&conn, false, "CLIPBOARD")
|
||||
.get_reply()?
|
||||
.atom();
|
||||
|
||||
let window_id = conn.generate_id();
|
||||
{
|
||||
let setup = conn.get_setup();
|
||||
let screen = setup
|
||||
.roots()
|
||||
.nth(screen as usize)
|
||||
.ok_or_else(|| failure::err_msg("no screen?"))?;
|
||||
|
||||
xcb::create_window_checked(
|
||||
&conn,
|
||||
xcb::COPY_FROM_PARENT as u8,
|
||||
window_id,
|
||||
screen.root(),
|
||||
// x, y
|
||||
0,
|
||||
0,
|
||||
// width, height
|
||||
1,
|
||||
1,
|
||||
// border width
|
||||
0,
|
||||
xcb::WINDOW_CLASS_INPUT_ONLY as u16,
|
||||
screen.root_visual(),
|
||||
&[(xcb::CW_EVENT_MASK, 0)],
|
||||
)
|
||||
.request_check()?;
|
||||
}
|
||||
|
||||
Ok(Inner {
|
||||
conn,
|
||||
owned: None,
|
||||
receiver,
|
||||
sender,
|
||||
window_id,
|
||||
atom_utf8_string,
|
||||
atom_xsel_data,
|
||||
atom_targets,
|
||||
atom_clipboard,
|
||||
wakeup,
|
||||
wakeup_window_id,
|
||||
})
|
||||
}
|
||||
|
||||
fn send(&mut self, packet: Paste) -> Result<(), Error> {
|
||||
match self.sender.send(packet) {
|
||||
Ok(_) => {
|
||||
self.wakeup.send(self.wakeup_window_id)?;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
self.wakeup.send(self.wakeup_window_id)?;
|
||||
bail!("clipboard: error sending to channel: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inform X that we either own the selection or that we no longer own the
|
||||
/// selection.
|
||||
fn update_owner(&self) {
|
||||
let owner = if self.owned.is_some() {
|
||||
self.window_id
|
||||
} else {
|
||||
xcb::NONE
|
||||
};
|
||||
xcb::set_selection_owner(&self.conn, owner, xcb::ATOM_PRIMARY, xcb::CURRENT_TIME);
|
||||
// Also set the CLIPBOARD atom, not just the PRIMARY selection.
|
||||
// TODO: make xterm clipboard selection configurable
|
||||
xcb::set_selection_owner(&self.conn, owner, self.atom_clipboard, xcb::CURRENT_TIME);
|
||||
}
|
||||
|
||||
fn selection_clear(&mut self) -> Result<(), Error> {
|
||||
// Someone else now owns the selection
|
||||
debug!("SELECTION_CLEAR received");
|
||||
self.owned = None;
|
||||
self.send(Paste::Cleared)
|
||||
}
|
||||
|
||||
fn selection_notify(&mut self, selection: &xcb::SelectionNotifyEvent) -> Result<(), Error> {
|
||||
debug!("SELECTION_NOTIFY received");
|
||||
|
||||
if (selection.selection() == xcb::ATOM_PRIMARY
|
||||
|| selection.selection() == self.atom_clipboard)
|
||||
&& selection.property() != xcb::NONE
|
||||
{
|
||||
match xcb_util::icccm::get_text_property(
|
||||
&self.conn,
|
||||
selection.requestor(),
|
||||
selection.property(),
|
||||
)
|
||||
.get_reply()
|
||||
{
|
||||
Ok(prop) => self.send(Paste::All(prop.name().into())),
|
||||
Err(err) => {
|
||||
eprintln!("clipboard: err while getting clipboard property: {:?}", err);
|
||||
self.send(Paste::All("".into()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.send(Paste::All("".into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn selection_request(&mut self, request: &xcb::SelectionRequestEvent) -> Result<(), Error> {
|
||||
// Someone is asking for our selected text
|
||||
debug!(
|
||||
"SEL: time={} owner={} requestor={} selection={} target={} property={}",
|
||||
request.time(),
|
||||
request.owner(),
|
||||
request.requestor(),
|
||||
request.selection(),
|
||||
request.target(),
|
||||
request.property()
|
||||
);
|
||||
debug!(
|
||||
"XSEL={}, UTF8={} PRIMARY={} clip={}",
|
||||
self.atom_xsel_data,
|
||||
self.atom_utf8_string,
|
||||
xcb::ATOM_PRIMARY,
|
||||
self.atom_clipboard,
|
||||
);
|
||||
|
||||
// I'd like to use `match` here, but the atom values are not
|
||||
// known at compile time so we have to `if` like a caveman :-p
|
||||
let selprop = if request.target() == self.atom_targets {
|
||||
// They want to know which targets we support
|
||||
debug!("responding with targets list");
|
||||
let atoms: [u32; 1] = [self.atom_utf8_string];
|
||||
xcb::xproto::change_property(
|
||||
&self.conn,
|
||||
xcb::xproto::PROP_MODE_REPLACE as u8,
|
||||
request.requestor(),
|
||||
request.property(),
|
||||
xcb::xproto::ATOM_ATOM,
|
||||
32, /* 32-bit atom value */
|
||||
&atoms,
|
||||
);
|
||||
|
||||
// let the requestor know that we set their property
|
||||
request.property()
|
||||
} else if request.target() == self.atom_utf8_string
|
||||
|| request.target() == xcb::xproto::ATOM_STRING
|
||||
{
|
||||
// We'll accept requests for UTF-8 or STRING data.
|
||||
// We don't and won't do any conversion from UTF-8 to
|
||||
// whatever STRING represents; let's just assume that
|
||||
// the other end is going to handle it correctly.
|
||||
if let Some(ref text) = self.owned {
|
||||
debug!("going to respond with clip text because we own it");
|
||||
xcb::xproto::change_property(
|
||||
&self.conn,
|
||||
xcb::xproto::PROP_MODE_REPLACE as u8,
|
||||
request.requestor(),
|
||||
request.property(),
|
||||
request.target(),
|
||||
8, /* 8-bit string data */
|
||||
text.as_bytes(),
|
||||
);
|
||||
// let the requestor know that we set their property
|
||||
request.property()
|
||||
} else {
|
||||
debug!("we don't own the clipboard");
|
||||
// We have no clipboard so there is nothing to report
|
||||
xcb::NONE
|
||||
}
|
||||
} else {
|
||||
debug!("don't know what to do");
|
||||
// We didn't support their request, so there is nothing
|
||||
// we can report back to them.
|
||||
xcb::NONE
|
||||
};
|
||||
debug!("responding with selprop={}", selprop);
|
||||
|
||||
xcb::xproto::send_event(
|
||||
&self.conn,
|
||||
true,
|
||||
request.requestor(),
|
||||
0,
|
||||
&xcb::xproto::SelectionNotifyEvent::new(
|
||||
request.time(),
|
||||
request.requestor(),
|
||||
request.selection(),
|
||||
request.target(),
|
||||
selprop, // the disposition from the operation above
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_xcb_event(&mut self, event: &xcb::GenericEvent) -> Result<(), Error> {
|
||||
match event.response_type() & 0x7f {
|
||||
xcb::SELECTION_CLEAR => self.selection_clear(),
|
||||
xcb::SELECTION_NOTIFY => self.selection_notify(unsafe { xcb::cast_event(event) }),
|
||||
|
||||
xcb::SELECTION_REQUEST => self.selection_request(unsafe { xcb::cast_event(event) }),
|
||||
xcb::MAPPING_NOTIFY => {
|
||||
// Nothing to do here; just don't want to print an error
|
||||
// in the case below.
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"clipboard: got unhandled XCB event type {}",
|
||||
event.response_type() & 0x7f
|
||||
);
|
||||
// Don't bail here; we just want to log the event and carry on.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_queued_xcb(&mut self) -> Result<(), Error> {
|
||||
match self.conn.poll_for_event() {
|
||||
None => match self.conn.has_error() {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
bail!("clipboard window connection is broken: {:?}", err);
|
||||
}
|
||||
},
|
||||
Some(event) => match self.process_xcb_event(&event) {
|
||||
Ok(_) => (),
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
}
|
||||
self.conn.flush();
|
||||
|
||||
loop {
|
||||
match self.conn.poll_for_queued_event() {
|
||||
None => return Ok(()),
|
||||
Some(event) => self.process_xcb_event(&event)?,
|
||||
}
|
||||
self.conn.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn process_receiver(&mut self) -> Result<(), Error> {
|
||||
match self.receiver.try_recv() {
|
||||
Err(TryRecvError::Empty) => Ok(()),
|
||||
Err(err) => bail!("clipboard: Error while reading channel: {:?}", err),
|
||||
Ok(request) => self.process_one_cliprequest(request),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_one_cliprequest(&mut self, request: ClipRequest) -> Result<(), Error> {
|
||||
match request {
|
||||
ClipRequest::Terminate => return Err(ClipTerminated.into()),
|
||||
ClipRequest::SetClipboard(clip) => {
|
||||
self.owned = clip;
|
||||
self.update_owner();
|
||||
}
|
||||
ClipRequest::RequestClipboard => {
|
||||
// Find the owner and ask them to send us the buffer
|
||||
xcb::convert_selection(
|
||||
&self.conn,
|
||||
self.window_id,
|
||||
xcb::ATOM_PRIMARY,
|
||||
self.atom_utf8_string,
|
||||
self.atom_xsel_data,
|
||||
xcb::CURRENT_TIME,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits for events from either the X server or the main thread of the
|
||||
/// application.
|
||||
fn clip_thread(&mut self) {
|
||||
let poll = Poll::new().expect("mio Poll failed to init");
|
||||
|
||||
poll.register(
|
||||
&EventedFd(&self.conn.as_raw_fd()),
|
||||
Token(0),
|
||||
Ready::readable(),
|
||||
PollOpt::level(),
|
||||
)
|
||||
.expect("failed to register xcb conn for clipboard with mio");
|
||||
|
||||
poll.register(
|
||||
&self.receiver,
|
||||
Token(1),
|
||||
Ready::readable(),
|
||||
PollOpt::level(),
|
||||
)
|
||||
.expect("failed to register receiver for clipboard with mio");
|
||||
|
||||
let mut events = Events::with_capacity(2);
|
||||
|
||||
self.sender
|
||||
.send(Paste::Running)
|
||||
.expect("failed to send Running notice");
|
||||
|
||||
loop {
|
||||
match poll.poll(&mut events, None) {
|
||||
Ok(_) => {
|
||||
for event in &events {
|
||||
if event.token() == Token(0) {
|
||||
if let Err(err) = self.process_queued_xcb() {
|
||||
eprintln!("clipboard: {:?}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if event.token() == Token(1) {
|
||||
if let Err(err) = self.process_receiver() {
|
||||
// No need to print the error trace if we were shutdown gracefully
|
||||
match err.downcast_ref::<ClipTerminated>() {
|
||||
Some(_) => return,
|
||||
_ => {
|
||||
eprintln!("clipboard: {:?}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.conn.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("clipboard: {:?}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A clipboard client allows getting or setting the clipboard.
|
||||
pub struct Clipboard {
|
||||
sender: MioSender<ClipRequest>,
|
||||
receiver: Receiver<Paste>,
|
||||
clip_thread: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ClipboardImpl for Clipboard {
|
||||
/// Create a new clipboard instance. `ping` is
|
||||
fn new(wakeup: GuiSender<WindowId>, window_id: WindowId) -> Result<Self, Error> {
|
||||
let (sender_clip, receiver_clip) = mio_channel();
|
||||
let (sender_paste, receiver_paste) = channel();
|
||||
let clip_thread = thread::spawn(move || {
|
||||
match Inner::new(receiver_clip, sender_paste, wakeup, window_id) {
|
||||
Ok(mut clip) => clip.clip_thread(),
|
||||
Err(err) => eprintln!("failed to init clipboard window: {:?}", err),
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure that it started up ok
|
||||
match receiver_paste.recv_timeout(Duration::from_secs(10)) {
|
||||
Ok(Paste::Running) => {}
|
||||
other => bail!("failed to init clipboard window: {:?}", other),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
sender: sender_clip,
|
||||
receiver: receiver_paste,
|
||||
clip_thread: Some(clip_thread),
|
||||
})
|
||||
}
|
||||
|
||||
/// Tell X that we own the selection and its contents are `text`
|
||||
fn set_clipboard(&self, text: Option<String>) -> Result<(), Error> {
|
||||
self.sender.send(ClipRequest::SetClipboard(text))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Blocks until the clipboard contents have been retrieved
|
||||
fn get_clipboard(&self) -> Result<String, Error> {
|
||||
self.sender.send(ClipRequest::RequestClipboard)?;
|
||||
match self.receiver.recv_timeout(Duration::from_secs(10)) {
|
||||
Ok(Paste::All(result)) => Ok(result),
|
||||
Ok(Paste::Cleared) => Ok("".into()),
|
||||
other => bail!("unexpected result while waiting for paste: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_paste(&self) -> Result<Option<Paste>, Error> {
|
||||
match self.receiver.try_recv() {
|
||||
Err(TryRecvError::Empty) => Ok(None),
|
||||
Err(err) => bail!("{:?}", err),
|
||||
Ok(paste) => Ok(Some(paste)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Clipboard {
|
||||
fn drop(&mut self) {
|
||||
self.sender.send(ClipRequest::Terminate).ok();
|
||||
if let Some(thread) = self.clip_thread.take() {
|
||||
thread.join().ok();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
use super::MasterPty;
|
||||
use super::{Child, Command};
|
||||
use clipboard::{Clipboard, ClipboardImpl, Paste};
|
||||
use clipboard::ClipboardContext;
|
||||
use config::Config;
|
||||
use failure::Error;
|
||||
use font::FontConfiguration;
|
||||
@ -22,7 +22,7 @@ use termwiz::hyperlink::Hyperlink;
|
||||
struct Host {
|
||||
display: glium::Display,
|
||||
pty: MasterPty,
|
||||
clipboard: Clipboard,
|
||||
clipboard: ClipboardContext,
|
||||
window_position: Option<(i32, i32)>,
|
||||
/// is is_some, holds position to be restored after exiting
|
||||
/// fullscreen mode.
|
||||
@ -44,12 +44,21 @@ impl term::TerminalHost for Host {
|
||||
}
|
||||
|
||||
fn get_clipboard(&mut self) -> Result<String, Error> {
|
||||
self.clipboard.get_clipboard()
|
||||
self.clipboard
|
||||
.get_contents()
|
||||
.map_err(|e| format_err!("{}", e))
|
||||
}
|
||||
|
||||
fn set_clipboard(&mut self, clip: Option<String>) -> Result<(), Error> {
|
||||
self.clipboard.set_clipboard(clip)
|
||||
self.clipboard
|
||||
.set_contents(clip.unwrap_or_else(|| "".into()))
|
||||
.map_err(|e| format_err!("{}", e))
|
||||
// Request the clipboard contents we just set; on some systems
|
||||
// if we copy and paste in wezterm, the clipboard isn't visible
|
||||
// to us again until the second call to get_clipboard.
|
||||
.and_then(|_| self.get_clipboard().map(|_| ()))
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.display.gl_window().set_title(title);
|
||||
}
|
||||
@ -142,13 +151,12 @@ impl TerminalWindow {
|
||||
glium::Display::new(window, pref_context, &*mut_loop)
|
||||
.map_err(|e| format_err!("{:?}", e))?
|
||||
};
|
||||
let window_id = display.gl_window().id();
|
||||
let window_position = display.gl_window().get_position();
|
||||
|
||||
let host = Host {
|
||||
display,
|
||||
pty,
|
||||
clipboard: Clipboard::new(event_loop.paster.clone(), window_id)?,
|
||||
clipboard: ClipboardContext::new().map_err(|e| format_err!("{}", e))?,
|
||||
window_position,
|
||||
is_fullscreen: None,
|
||||
};
|
||||
@ -581,18 +589,6 @@ impl TerminalWindow {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_clipboard(&mut self) -> Result<(), Error> {
|
||||
match self.host.clipboard.try_get_paste() {
|
||||
Ok(Some(Paste::Cleared)) => {
|
||||
self.terminal.clear_selection();
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(err) => bail!("clipboard thread died? {:?}", err),
|
||||
}
|
||||
self.paint_if_needed()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_for_child_exit(&mut self) -> Result<(), SessionTerminated> {
|
||||
match self.process.try_wait() {
|
||||
Ok(Some(status)) => Err(SessionTerminated::ProcessStatus { status }),
|
||||
|
@ -63,8 +63,6 @@ pub struct GuiEventLoop {
|
||||
pub core: futurecore::Core,
|
||||
poll_tx: GuiSender<IOEvent>,
|
||||
poll_rx: Receiver<IOEvent>,
|
||||
pub paster: GuiSender<WindowId>,
|
||||
paster_rx: Receiver<WindowId>,
|
||||
#[cfg(unix)]
|
||||
sigchld_rx: Receiver<()>,
|
||||
tick_rx: Receiver<()>,
|
||||
@ -78,7 +76,6 @@ impl GuiEventLoop {
|
||||
let core = futurecore::Core::new(fut_tx, fut_rx);
|
||||
|
||||
let (poll_tx, poll_rx) = channel(event_loop.create_proxy());
|
||||
let (paster, paster_rx) = channel(event_loop.create_proxy());
|
||||
#[cfg(unix)]
|
||||
let (sigchld_tx, sigchld_rx) = channel(event_loop.create_proxy());
|
||||
|
||||
@ -102,8 +99,6 @@ impl GuiEventLoop {
|
||||
core,
|
||||
poll_tx,
|
||||
poll_rx,
|
||||
paster,
|
||||
paster_rx,
|
||||
tick_rx,
|
||||
#[cfg(unix)]
|
||||
sigchld_rx,
|
||||
@ -225,23 +220,6 @@ impl GuiEventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
/// Process paste notifications and route them to their owning windows.
|
||||
fn process_paste(&self) -> Result<(), Error> {
|
||||
loop {
|
||||
match self.paster_rx.try_recv() {
|
||||
Ok(window_id) => {
|
||||
self.windows
|
||||
.borrow_mut()
|
||||
.by_id
|
||||
.get_mut(&window_id)
|
||||
.map(|w| w.process_clipboard());
|
||||
}
|
||||
Err(TryRecvError::Empty) => return Ok(()),
|
||||
Err(err) => bail!("paster_rx disconnected {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_tick(&self) -> Result<(), Error> {
|
||||
loop {
|
||||
match self.tick_rx.try_recv() {
|
||||
@ -266,7 +244,7 @@ impl GuiEventLoop {
|
||||
self.test_for_child_exit();
|
||||
}
|
||||
Err(TryRecvError::Empty) => return Ok(()),
|
||||
Err(err) => bail!("paster_rx disconnected {:?}", err),
|
||||
Err(err) => bail!("sigchld_rx disconnected {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -342,7 +320,6 @@ impl GuiEventLoop {
|
||||
|
||||
self.run_event_loop()?;
|
||||
self.process_poll()?;
|
||||
self.process_paste()?;
|
||||
#[cfg(unix)]
|
||||
self.process_sigchld()?;
|
||||
self.process_tick()?;
|
||||
|
@ -59,8 +59,6 @@ struct Windows {
|
||||
pub struct GuiEventLoop {
|
||||
poll: Poll,
|
||||
pub conn: Rc<Connection>,
|
||||
pub paster: GuiSender<WindowId>,
|
||||
paster_rx: GuiReceiver<WindowId>,
|
||||
sigchld_rx: GuiReceiver<()>,
|
||||
pub core: futurecore::Core,
|
||||
windows: Rc<RefCell<Windows>>,
|
||||
@ -68,7 +66,6 @@ pub struct GuiEventLoop {
|
||||
}
|
||||
|
||||
const TOK_CORE: usize = 0xffff_ffff;
|
||||
const TOK_PASTER: usize = 0xffff_fffe;
|
||||
const TOK_CHLD: usize = 0xffff_fffd;
|
||||
const TOK_XCB: usize = 0xffff_fffc;
|
||||
|
||||
@ -89,14 +86,6 @@ impl GuiEventLoop {
|
||||
)?;
|
||||
let core = futurecore::Core::new(fut_tx, fut_rx);
|
||||
|
||||
let (paster, paster_rx) = channel();
|
||||
poll.register(
|
||||
&paster_rx,
|
||||
Token(TOK_PASTER),
|
||||
Ready::readable(),
|
||||
PollOpt::level(),
|
||||
)?;
|
||||
|
||||
let (sigchld_tx, sigchld_rx) = channel();
|
||||
poll.register(
|
||||
&sigchld_rx,
|
||||
@ -110,8 +99,6 @@ impl GuiEventLoop {
|
||||
conn,
|
||||
poll,
|
||||
core,
|
||||
paster,
|
||||
paster_rx,
|
||||
sigchld_rx,
|
||||
interval: Duration::from_millis(50),
|
||||
windows: Rc::new(RefCell::new(Default::default())),
|
||||
@ -122,7 +109,6 @@ impl GuiEventLoop {
|
||||
let mut events = Events::with_capacity(8);
|
||||
|
||||
let tok_core = Token(TOK_CORE);
|
||||
let tok_paster = Token(TOK_PASTER);
|
||||
let tok_chld = Token(TOK_CHLD);
|
||||
let tok_xcb = Token(TOK_XCB);
|
||||
|
||||
@ -146,8 +132,6 @@ impl GuiEventLoop {
|
||||
let t = event.token();
|
||||
if t == tok_core {
|
||||
self.process_futures();
|
||||
} else if t == tok_paster {
|
||||
self.process_paste()?;
|
||||
} else if t == tok_chld {
|
||||
self.process_sigchld()?;
|
||||
} else if t == tok_xcb {
|
||||
@ -412,23 +396,6 @@ impl GuiEventLoop {
|
||||
self.conn.flush();
|
||||
}
|
||||
|
||||
/// Process paste notifications and route them to their owning windows.
|
||||
fn process_paste(&self) -> Result<(), Error> {
|
||||
loop {
|
||||
match self.paster_rx.try_recv() {
|
||||
Ok(window_id) => {
|
||||
self.windows
|
||||
.borrow_mut()
|
||||
.by_id
|
||||
.get_mut(&window_id)
|
||||
.map(|w| w.process_clipboard());
|
||||
}
|
||||
Err(TryRecvError::Empty) => return Ok(()),
|
||||
Err(err) => bail!("paster_rx disconnected {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If we were signalled by a child process completion, zip through
|
||||
/// the windows and have then notice and prepare to close.
|
||||
fn process_sigchld(&self) -> Result<(), Error> {
|
||||
@ -451,7 +418,7 @@ impl GuiEventLoop {
|
||||
}
|
||||
}
|
||||
Err(TryRecvError::Empty) => return Ok(()),
|
||||
Err(err) => bail!("paster_rx disconnected {:?}", err),
|
||||
Err(err) => bail!("sigchld_rx disconnected {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ extern crate palette;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate clipboard;
|
||||
extern crate term;
|
||||
extern crate termwiz;
|
||||
extern crate toml;
|
||||
@ -60,7 +61,6 @@ mod config;
|
||||
mod futurecore;
|
||||
mod opengl;
|
||||
|
||||
mod clipboard;
|
||||
#[cfg(any(windows, feature = "force-glutin", target_os = "macos"))]
|
||||
mod gliumwindows;
|
||||
mod guiloop;
|
||||
|
@ -3,7 +3,7 @@ use super::super::{get_shell, spawn_window};
|
||||
use super::super::{Child, Command};
|
||||
use super::xkeysyms;
|
||||
use super::{Connection, Window};
|
||||
use clipboard::{Clipboard, ClipboardImpl, Paste};
|
||||
use clipboard::ClipboardContext;
|
||||
use config::Config;
|
||||
use failure::Error;
|
||||
use font::FontConfiguration;
|
||||
@ -98,7 +98,7 @@ struct TabHost<'a> {
|
||||
/// Holds most of the information we need to implement `TerminalHost`
|
||||
struct Host {
|
||||
window: Window,
|
||||
clipboard: Clipboard,
|
||||
clipboard: ClipboardContext,
|
||||
event_loop: Rc<GuiEventLoop>,
|
||||
fonts: Rc<FontConfiguration>,
|
||||
config: Rc<Config>,
|
||||
@ -131,11 +131,21 @@ impl<'a> term::TerminalHost for TabHost<'a> {
|
||||
}
|
||||
|
||||
fn get_clipboard(&mut self) -> Result<String, Error> {
|
||||
self.host.clipboard.get_clipboard()
|
||||
self.host
|
||||
.clipboard
|
||||
.get_contents()
|
||||
.map_err(|e| format_err!("{}", e))
|
||||
}
|
||||
|
||||
fn set_clipboard(&mut self, clip: Option<String>) -> Result<(), Error> {
|
||||
self.host.clipboard.set_clipboard(clip)
|
||||
self.host
|
||||
.clipboard
|
||||
.set_contents(clip.unwrap_or_else(|| "".into()))
|
||||
.map_err(|e| format_err!("{}", e))
|
||||
// Request the clipboard contents we just set; on some systems
|
||||
// if we copy and paste in wezterm, the clipboard isn't visible
|
||||
// to us again until the second call to get_clipboard.
|
||||
.and_then(|_| self.get_clipboard().map(|_| ()))
|
||||
}
|
||||
|
||||
fn set_title(&mut self, _title: &str) {
|
||||
@ -244,11 +254,10 @@ impl TerminalWindow {
|
||||
|
||||
let window = Window::new(&event_loop.conn, width, height)?;
|
||||
window.set_title("wezterm");
|
||||
let window_id = window.window.window_id;
|
||||
|
||||
let host = Host {
|
||||
window,
|
||||
clipboard: Clipboard::new(event_loop.paster.clone(), window_id)?,
|
||||
clipboard: ClipboardContext::new().map_err(|e| format_err!("{}", e))?,
|
||||
event_loop: Rc::clone(event_loop),
|
||||
config: Rc::clone(config),
|
||||
fonts: Rc::clone(fonts),
|
||||
@ -365,22 +374,6 @@ impl TerminalWindow {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_clipboard(&mut self) -> Result<(), Error> {
|
||||
match self.host.clipboard.try_get_paste() {
|
||||
Ok(Some(Paste::Cleared)) => {
|
||||
self.tabs
|
||||
.get_active()
|
||||
.terminal
|
||||
.borrow_mut()
|
||||
.clear_selection();
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(err) => bail!("clipboard thread died? {:?}", err),
|
||||
}
|
||||
self.paint_if_needed()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_for_child_exit(&mut self) -> Result<(), SessionTerminated> {
|
||||
match self.tabs.get_active().process.borrow_mut().try_wait() {
|
||||
Ok(Some(status)) => Err(SessionTerminated::ProcessStatus { status }),
|
||||
|
Loading…
Reference in New Issue
Block a user