Skip to content

Commit

Permalink
feat(core): add clipboard writeText and readText APIs (#2035)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Jun 21, 2021
1 parent 3280c4a commit 285bf64
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 35 deletions.
8 changes: 8 additions & 0 deletions .changes/clipboard-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"api": patch
"tauri": patch
"tauri-runtime": patch
"tauri-runtime-wry": patch
---

Adds `clipboard` APIs (write and read text).
113 changes: 88 additions & 25 deletions core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use tauri_runtime::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
DetachedWindow, PendingWindow, WindowEvent,
},
Dispatch, Error, GlobalShortcutManager, Icon, Params, Result, RunEvent, RunIteration, Runtime,
RuntimeHandle, UserAttentionType,
ClipboardManager, Dispatch, Error, GlobalShortcutManager, Icon, Params, Result, RunEvent,
RunIteration, Runtime, RuntimeHandle, UserAttentionType,
};

#[cfg(feature = "menu")]
Expand All @@ -33,6 +33,7 @@ use uuid::Uuid;
use wry::{
application::{
accelerator::{Accelerator, AcceleratorId},
clipboard::Clipboard,
dpi::{
LogicalPosition as WryLogicalPosition, LogicalSize as WryLogicalSize,
PhysicalPosition as WryPhysicalPosition, PhysicalSize as WryPhysicalSize,
Expand Down Expand Up @@ -97,7 +98,7 @@ macro_rules! dispatcher_getter {
}};
}

macro_rules! shortcut_getter {
macro_rules! getter {
($self: ident, $rx: expr, $message: expr) => {{
if current_thread().id() == $self.context.main_thread_id
&& !$self.context.is_event_loop_running.load(Ordering::Relaxed)
Expand All @@ -113,30 +114,30 @@ macro_rules! shortcut_getter {
}};
}

#[derive(Debug, Clone)]
struct GlobalShortcutWrapper(GlobalShortcut);

unsafe impl Send for GlobalShortcutWrapper {}

#[derive(Clone)]
struct GlobalShortcutManagerContext {
struct EventLoopContext {
main_thread_id: ThreadId,
is_event_loop_running: Arc<AtomicBool>,
proxy: EventLoopProxy<Message>,
}

#[derive(Debug, Clone)]
struct GlobalShortcutWrapper(GlobalShortcut);

unsafe impl Send for GlobalShortcutWrapper {}

/// Wrapper around [`WryShortcutManager`].
#[derive(Clone)]
pub struct GlobalShortcutManagerHandle {
context: GlobalShortcutManagerContext,
context: EventLoopContext,
shortcuts: HashMap<String, (AcceleratorId, GlobalShortcutWrapper)>,
listeners: GlobalShortcutListeners,
}

impl GlobalShortcutManager for GlobalShortcutManagerHandle {
fn is_registered(&self, accelerator: &str) -> Result<bool> {
let (tx, rx) = channel();
Ok(shortcut_getter!(
Ok(getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered(
Expand All @@ -150,7 +151,7 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle {
let wry_accelerator: Accelerator = accelerator.parse().expect("invalid accelerator");
let id = wry_accelerator.clone().id();
let (tx, rx) = channel();
let shortcut = shortcut_getter!(
let shortcut = getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx))
Expand All @@ -164,7 +165,7 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle {

fn unregister_all(&mut self) -> Result<()> {
let (tx, rx) = channel();
shortcut_getter!(
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx))
Expand All @@ -177,20 +178,43 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle {
fn unregister(&mut self, accelerator: &str) -> Result<()> {
if let Some((accelerator_id, shortcut)) = self.shortcuts.remove(accelerator) {
let (tx, rx) = channel();
shortcut_getter!(
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Unregister(
GlobalShortcutWrapper(shortcut.0),
tx
))
Message::GlobalShortcut(GlobalShortcutMessage::Unregister(shortcut, tx))
)?;
self.listeners.lock().unwrap().remove(&accelerator_id);
}
Ok(())
}
}

#[derive(Clone)]
pub struct ClipboardManagerWrapper {
context: EventLoopContext,
}

impl ClipboardManager for ClipboardManagerWrapper {
fn read_text(&self) -> Result<Option<String>> {
let (tx, rx) = channel();
Ok(getter!(
self,
rx,
Message::Clipboard(ClipboardMessage::ReadText(tx))
))
}

fn write_text<T: Into<String>>(&mut self, text: T) -> Result<()> {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::Clipboard(ClipboardMessage::WriteText(text.into(), tx))
);
Ok(())
}
}

/// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`].
pub struct WryIcon(WindowIcon);

Expand Down Expand Up @@ -650,6 +674,12 @@ pub(crate) enum GlobalShortcutMessage {
UnregisterAll(Sender<Result<()>>),
}

#[derive(Clone)]
pub(crate) enum ClipboardMessage {
WriteText(String, Sender<()>),
ReadText(Sender<Option<String>>),
}

#[derive(Clone)]
pub(crate) enum Message {
Task(MainTask),
Expand All @@ -659,6 +689,7 @@ pub(crate) enum Message {
Tray(TrayMessage),
CreateWebview(Arc<Mutex<Option<CreateWebviewHandler>>>, Sender<WindowId>),
GlobalShortcut(GlobalShortcutMessage),
Clipboard(ClipboardMessage),
}

#[derive(Clone)]
Expand Down Expand Up @@ -1099,6 +1130,8 @@ pub struct Wry {
main_thread_id: ThreadId,
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
global_shortcut_manager_handle: GlobalShortcutManagerHandle,
clipboard_manager: Arc<Mutex<Clipboard>>,
clipboard_manager_handle: ClipboardManagerWrapper,
is_event_loop_running: Arc<AtomicBool>,
event_loop: EventLoop<Message>,
webviews: Arc<Mutex<HashMap<WindowId, WebviewWrapper>>>,
Expand Down Expand Up @@ -1159,28 +1192,39 @@ impl Runtime for Wry {
type Dispatcher = WryDispatcher;
type Handle = WryHandle;
type GlobalShortcutManager = GlobalShortcutManagerHandle;
type ClipboardManager = ClipboardManagerWrapper;
#[cfg(feature = "system-tray")]
type TrayHandler = SystemTrayHandle;

fn new() -> Result<Self> {
let event_loop = EventLoop::<Message>::with_user_event();
let global_shortcut_manager = WryShortcutManager::new(&event_loop);
let global_shortcut_listeners = GlobalShortcutListeners::default();
let proxy = event_loop.create_proxy();
let main_thread_id = current_thread().id();
let is_event_loop_running = Arc::new(AtomicBool::default());

let event_loop_context = EventLoopContext {
main_thread_id,
is_event_loop_running: is_event_loop_running.clone(),
proxy,
};

let global_shortcut_manager = WryShortcutManager::new(&event_loop);
let global_shortcut_listeners = GlobalShortcutListeners::default();
let clipboard_manager = Clipboard::new();
let clipboard_manager_handle = ClipboardManagerWrapper {
context: event_loop_context.clone(),
};

Ok(Self {
main_thread_id,
global_shortcut_manager: Arc::new(Mutex::new(global_shortcut_manager)),
global_shortcut_manager_handle: GlobalShortcutManagerHandle {
context: GlobalShortcutManagerContext {
main_thread_id,
is_event_loop_running: is_event_loop_running.clone(),
proxy,
},
context: event_loop_context,
shortcuts: Default::default(),
listeners: global_shortcut_listeners,
},
clipboard_manager: Arc::new(Mutex::new(clipboard_manager)),
clipboard_manager_handle,
is_event_loop_running,
event_loop,
webviews: Default::default(),
Expand Down Expand Up @@ -1209,6 +1253,10 @@ impl Runtime for Wry {
self.global_shortcut_manager_handle.clone()
}

fn clipboard_manager(&self) -> Self::ClipboardManager {
self.clipboard_manager_handle.clone()
}

fn create_window<P: Params<Runtime = Self>>(
&self,
pending: PendingWindow<P>,
Expand Down Expand Up @@ -1298,6 +1346,7 @@ impl Runtime for Wry {
let tray_context = self.tray_context.clone();
let global_shortcut_manager = self.global_shortcut_manager.clone();
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
let clipboard_manager = self.clipboard_manager.clone();

let mut iteration = RunIteration::default();

Expand All @@ -1318,6 +1367,7 @@ impl Runtime for Wry {
window_event_listeners: window_event_listeners.clone(),
global_shortcut_manager: global_shortcut_manager.clone(),
global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
clipboard_manager: clipboard_manager.clone(),
#[cfg(feature = "menu")]
menu_event_listeners: menu_event_listeners.clone(),
#[cfg(feature = "system-tray")]
Expand All @@ -1340,6 +1390,7 @@ impl Runtime for Wry {
let tray_context = self.tray_context;
let global_shortcut_manager = self.global_shortcut_manager.clone();
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
let clipboard_manager = self.clipboard_manager.clone();

self.event_loop.run(move |event, event_loop, control_flow| {
handle_event_loop(
Expand All @@ -1352,6 +1403,7 @@ impl Runtime for Wry {
window_event_listeners: window_event_listeners.clone(),
global_shortcut_manager: global_shortcut_manager.clone(),
global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
clipboard_manager: clipboard_manager.clone(),
#[cfg(feature = "menu")]
menu_event_listeners: menu_event_listeners.clone(),
#[cfg(feature = "system-tray")]
Expand All @@ -1368,6 +1420,7 @@ struct EventLoopIterationContext<'a> {
window_event_listeners: WindowEventListeners,
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
global_shortcut_manager_handle: GlobalShortcutManagerHandle,
clipboard_manager: Arc<Mutex<Clipboard>>,
#[cfg(feature = "menu")]
menu_event_listeners: MenuEventListeners,
#[cfg(feature = "system-tray")]
Expand All @@ -1386,6 +1439,7 @@ fn handle_event_loop(
window_event_listeners,
global_shortcut_manager,
global_shortcut_manager_handle,
clipboard_manager,
#[cfg(feature = "menu")]
menu_event_listeners,
#[cfg(feature = "system-tray")]
Expand Down Expand Up @@ -1689,6 +1743,15 @@ fn handle_event_loop(
)
.unwrap(),
},
Message::Clipboard(message) => match message {
ClipboardMessage::WriteText(text, tx) => {
clipboard_manager.lock().unwrap().write_text(text);
tx.send(()).unwrap();
}
ClipboardMessage::ReadText(tx) => tx
.send(clipboard_manager.lock().unwrap().read_text())
.unwrap(),
},
},
_ => (),
}
Expand Down
24 changes: 24 additions & 0 deletions core/tauri-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,25 @@ pub trait GlobalShortcutManager {
fn unregister(&mut self, accelerator: &str) -> crate::Result<()>;
}

/// Clipboard manager.
pub trait ClipboardManager {
/// Writes the text into the clipboard as plain text.
///
/// # Panics
///
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.

fn write_text<T: Into<String>>(&mut self, text: T) -> Result<()>;
/// Read the content in the clipboard as plain text.
///
/// # Panics
///
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
fn read_text(&self) -> Result<Option<String>>;
}

/// The webview runtime interface.
pub trait Runtime: Sized + 'static {
/// The message dispatcher.
Expand All @@ -278,6 +297,8 @@ pub trait Runtime: Sized + 'static {
type Handle: RuntimeHandle<Runtime = Self>;
/// The global shortcut manager type.
type GlobalShortcutManager: GlobalShortcutManager + Clone + Send;
/// The clipboard manager type.
type ClipboardManager: ClipboardManager + Clone + Send;
/// The tray handler type.
#[cfg(feature = "system-tray")]
type TrayHandler: menu::TrayHandle + Clone + Send;
Expand All @@ -291,6 +312,9 @@ pub trait Runtime: Sized + 'static {
/// Gets the global shortcut manager.
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager;

/// Gets the clipboard manager.
fn clipboard_manager(&self) -> Self::ClipboardManager;

/// Create a new webview window.
fn create_window<P: Params<Runtime = Self>>(
&self,
Expand Down
Loading

0 comments on commit 285bf64

Please sign in to comment.