Add experimental support for clipboard backend on linux.

This commit is contained in:
Federico Terzi 2019-09-07 00:38:13 +02:00
parent 0a651cc187
commit 30c127786d
8 changed files with 123 additions and 8 deletions

View File

@ -202,4 +202,12 @@ void delete_string(int32_t count) {
for (int i = 0; i<count; i++) {
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "BackSpace", 8000);
}
}
void trigger_paste() {
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Control_L+v", 8000);
}
void trigger_terminal_paste() {
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Control_L+Shift+v", 8000);
}

View File

@ -42,4 +42,14 @@ extern "C" void send_string(const char * string);
*/
extern "C" void delete_string(int32_t count);
/*
* Trigger normal paste ( Pressing CTRL+V )
*/
extern "C" void trigger_paste();
/*
* Trigger terminal paste ( Pressing CTRL+SHIFT+V )
*/
extern "C" void trigger_terminal_paste();
#endif //ESPANSO_BRIDGE_H

54
src/clipboard/linux.rs Normal file
View File

@ -0,0 +1,54 @@
use std::process::{Command, ExitStatus, Stdio};
use std::io::{Write, Read};
pub struct LinuxClipboardManager {
}
impl super::ClipboardManager for LinuxClipboardManager {
fn initialize(&self) {
// TODO: check if xclip is present and log an error otherwise.
}
fn get_clipboard(&self) -> Option<String> {
let res = Command::new("xclip")
.args(&["-o", "-sel", "clip"])
.output();
if let Ok(output) = res {
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout);
return Some((*s).to_owned());
}
}
None
}
fn set_clipboard(&self, payload: &str) {
let mut res = Command::new("xclip")
.args(&["-sel", "clip"])
.stdin(Stdio::piped())
.spawn();
if let Ok(mut child) = res {
let mut stdin = child.stdin.as_mut();
if let Some(mut output) = stdin {
output.write_all(payload.as_bytes());
// let status = child.wait();
//
// if let Ok(status) = status {
// if !status.success() {
// //TODO: log error
// }
// }
}
}
}
}
impl LinuxClipboardManager {
}

22
src/clipboard/mod.rs Normal file
View File

@ -0,0 +1,22 @@
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
pub trait ClipboardManager {
fn initialize(&self);
fn get_clipboard(&self) -> Option<String>;
fn set_clipboard(&self, payload: &str);
}
// LINUX IMPLEMENTATION
#[cfg(target_os = "linux")]
pub fn get_manager() -> impl ClipboardManager {
let manager = linux::LinuxClipboardManager{};
manager.initialize();
manager
}

View File

@ -1,19 +1,22 @@
use crate::matcher::{Match, MatchReceiver};
use crate::keyboard::KeyboardSender;
use crate::config::Configs;
use crate::clipboard::ClipboardManager;
use std::sync::Arc;
pub struct Engine<S> where S: KeyboardSender {
pub struct Engine<S, C> where S: KeyboardSender, C: ClipboardManager {
sender: S,
clipboard_manager: Arc<C>,
configs: Configs,
}
impl <S> Engine<S> where S: KeyboardSender{
pub fn new(sender: S, configs: Configs) -> Engine<S> where S: KeyboardSender {
Engine{sender, configs }
impl <S, C> Engine<S, C> where S: KeyboardSender, C: ClipboardManager{
pub fn new(sender: S, clipboard_manager: Arc<C>, configs: Configs) -> Engine<S, C> where S: KeyboardSender, C: ClipboardManager {
Engine{sender, clipboard_manager, configs }
}
}
impl <S> MatchReceiver for Engine<S> where S: KeyboardSender{
impl <S, C> MatchReceiver for Engine<S, C> where S: KeyboardSender, C: ClipboardManager{
fn on_match(&self, m: &Match) {
self.sender.delete_string(m.trigger.len() as i32);
@ -21,7 +24,9 @@ impl <S> MatchReceiver for Engine<S> where S: KeyboardSender{
// while on windows and macos, we need to emulate a Enter key press.
if cfg!(target_os = "linux") {
self.sender.send_string(m.replace.as_str());
self.clipboard_manager.set_clipboard(m.replace.as_str());
self.sender.trigger_paste();
//self.sender.send_string(m.replace.as_str());
}else{
// To handle newlines, substitute each "\n" char with an Enter key press.
let splits = m.replace.lines();

View File

@ -47,6 +47,12 @@ impl super::KeyboardSender for LinuxKeyboardSender {
// On linux this is not needed, so NOOP
}
fn trigger_paste(&self) {
unsafe { trigger_paste(); }
// TODO: detect when in terminal and use trigger_terminal_paste() instead
}
fn delete_string(&self, count: i32) {
unsafe {delete_string(count)}
}
@ -94,4 +100,6 @@ extern {
fn cleanup();
fn send_string(string: *const c_char);
fn delete_string(count: i32);
fn trigger_paste();
fn trigger_terminal_paste();
}

View File

@ -18,6 +18,7 @@ pub trait KeyboardInterceptor {
pub trait KeyboardSender {
fn send_string(&self, s: &str);
fn send_enter(&self);
fn trigger_paste(&self);
fn delete_string(&self, count: i32);
}

View File

@ -1,10 +1,11 @@
use std::sync::mpsc;
use std::sync::{mpsc, Arc};
use crate::keyboard::KeyboardInterceptor;
use crate::matcher::Matcher;
use crate::matcher::scrolling::ScrollingMatcher;
use crate::engine::Engine;
use crate::config::Configs;
use crate::ui::UIManager;
use crate::clipboard::ClipboardManager;
use std::thread;
use clap::{App, Arg};
use std::path::Path;
@ -14,6 +15,7 @@ mod matcher;
mod engine;
mod config;
mod ui;
mod clipboard;
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -54,11 +56,16 @@ fn espanso_main(configs: Configs) {
let ui_manager = ui::get_uimanager();
ui_manager.notify("Hello guys");
let clipboard_manager = clipboard::get_manager();
let clipboard_manager_arc = Arc::new(clipboard_manager);
let (txc, rxc) = mpsc::channel();
let sender = keyboard::get_sender();
let engine = Engine::new(sender, configs.clone());
let engine = Engine::new(sender,
Arc::clone(&clipboard_manager_arc),
configs.clone());
thread::spawn(move || {
let matcher = ScrollingMatcher::new(configs.clone(), engine);