Skip to content

Commit 285bf64

Browse files
authored
feat(core): add clipboard writeText and readText APIs (#2035)
1 parent 3280c4a commit 285bf64

File tree

13 files changed

+265
-35
lines changed

13 files changed

+265
-35
lines changed

.changes/clipboard-api.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"api": patch
3+
"tauri": patch
4+
"tauri-runtime": patch
5+
"tauri-runtime-wry": patch
6+
---
7+
8+
Adds `clipboard` APIs (write and read text).

core/tauri-runtime-wry/src/lib.rs

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use tauri_runtime::{
1313
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
1414
DetachedWindow, PendingWindow, WindowEvent,
1515
},
16-
Dispatch, Error, GlobalShortcutManager, Icon, Params, Result, RunEvent, RunIteration, Runtime,
17-
RuntimeHandle, UserAttentionType,
16+
ClipboardManager, Dispatch, Error, GlobalShortcutManager, Icon, Params, Result, RunEvent,
17+
RunIteration, Runtime, RuntimeHandle, UserAttentionType,
1818
};
1919

2020
#[cfg(feature = "menu")]
@@ -33,6 +33,7 @@ use uuid::Uuid;
3333
use wry::{
3434
application::{
3535
accelerator::{Accelerator, AcceleratorId},
36+
clipboard::Clipboard,
3637
dpi::{
3738
LogicalPosition as WryLogicalPosition, LogicalSize as WryLogicalSize,
3839
PhysicalPosition as WryPhysicalPosition, PhysicalSize as WryPhysicalSize,
@@ -97,7 +98,7 @@ macro_rules! dispatcher_getter {
9798
}};
9899
}
99100

100-
macro_rules! shortcut_getter {
101+
macro_rules! getter {
101102
($self: ident, $rx: expr, $message: expr) => {{
102103
if current_thread().id() == $self.context.main_thread_id
103104
&& !$self.context.is_event_loop_running.load(Ordering::Relaxed)
@@ -113,30 +114,30 @@ macro_rules! shortcut_getter {
113114
}};
114115
}
115116

116-
#[derive(Debug, Clone)]
117-
struct GlobalShortcutWrapper(GlobalShortcut);
118-
119-
unsafe impl Send for GlobalShortcutWrapper {}
120-
121117
#[derive(Clone)]
122-
struct GlobalShortcutManagerContext {
118+
struct EventLoopContext {
123119
main_thread_id: ThreadId,
124120
is_event_loop_running: Arc<AtomicBool>,
125121
proxy: EventLoopProxy<Message>,
126122
}
127123

124+
#[derive(Debug, Clone)]
125+
struct GlobalShortcutWrapper(GlobalShortcut);
126+
127+
unsafe impl Send for GlobalShortcutWrapper {}
128+
128129
/// Wrapper around [`WryShortcutManager`].
129130
#[derive(Clone)]
130131
pub struct GlobalShortcutManagerHandle {
131-
context: GlobalShortcutManagerContext,
132+
context: EventLoopContext,
132133
shortcuts: HashMap<String, (AcceleratorId, GlobalShortcutWrapper)>,
133134
listeners: GlobalShortcutListeners,
134135
}
135136

136137
impl GlobalShortcutManager for GlobalShortcutManagerHandle {
137138
fn is_registered(&self, accelerator: &str) -> Result<bool> {
138139
let (tx, rx) = channel();
139-
Ok(shortcut_getter!(
140+
Ok(getter!(
140141
self,
141142
rx,
142143
Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered(
@@ -150,7 +151,7 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle {
150151
let wry_accelerator: Accelerator = accelerator.parse().expect("invalid accelerator");
151152
let id = wry_accelerator.clone().id();
152153
let (tx, rx) = channel();
153-
let shortcut = shortcut_getter!(
154+
let shortcut = getter!(
154155
self,
155156
rx,
156157
Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx))
@@ -164,7 +165,7 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle {
164165

165166
fn unregister_all(&mut self) -> Result<()> {
166167
let (tx, rx) = channel();
167-
shortcut_getter!(
168+
getter!(
168169
self,
169170
rx,
170171
Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx))
@@ -177,20 +178,43 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle {
177178
fn unregister(&mut self, accelerator: &str) -> Result<()> {
178179
if let Some((accelerator_id, shortcut)) = self.shortcuts.remove(accelerator) {
179180
let (tx, rx) = channel();
180-
shortcut_getter!(
181+
getter!(
181182
self,
182183
rx,
183-
Message::GlobalShortcut(GlobalShortcutMessage::Unregister(
184-
GlobalShortcutWrapper(shortcut.0),
185-
tx
186-
))
184+
Message::GlobalShortcut(GlobalShortcutMessage::Unregister(shortcut, tx))
187185
)?;
188186
self.listeners.lock().unwrap().remove(&accelerator_id);
189187
}
190188
Ok(())
191189
}
192190
}
193191

192+
#[derive(Clone)]
193+
pub struct ClipboardManagerWrapper {
194+
context: EventLoopContext,
195+
}
196+
197+
impl ClipboardManager for ClipboardManagerWrapper {
198+
fn read_text(&self) -> Result<Option<String>> {
199+
let (tx, rx) = channel();
200+
Ok(getter!(
201+
self,
202+
rx,
203+
Message::Clipboard(ClipboardMessage::ReadText(tx))
204+
))
205+
}
206+
207+
fn write_text<T: Into<String>>(&mut self, text: T) -> Result<()> {
208+
let (tx, rx) = channel();
209+
getter!(
210+
self,
211+
rx,
212+
Message::Clipboard(ClipboardMessage::WriteText(text.into(), tx))
213+
);
214+
Ok(())
215+
}
216+
}
217+
194218
/// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`].
195219
pub struct WryIcon(WindowIcon);
196220

@@ -650,6 +674,12 @@ pub(crate) enum GlobalShortcutMessage {
650674
UnregisterAll(Sender<Result<()>>),
651675
}
652676

677+
#[derive(Clone)]
678+
pub(crate) enum ClipboardMessage {
679+
WriteText(String, Sender<()>),
680+
ReadText(Sender<Option<String>>),
681+
}
682+
653683
#[derive(Clone)]
654684
pub(crate) enum Message {
655685
Task(MainTask),
@@ -659,6 +689,7 @@ pub(crate) enum Message {
659689
Tray(TrayMessage),
660690
CreateWebview(Arc<Mutex<Option<CreateWebviewHandler>>>, Sender<WindowId>),
661691
GlobalShortcut(GlobalShortcutMessage),
692+
Clipboard(ClipboardMessage),
662693
}
663694

664695
#[derive(Clone)]
@@ -1099,6 +1130,8 @@ pub struct Wry {
10991130
main_thread_id: ThreadId,
11001131
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
11011132
global_shortcut_manager_handle: GlobalShortcutManagerHandle,
1133+
clipboard_manager: Arc<Mutex<Clipboard>>,
1134+
clipboard_manager_handle: ClipboardManagerWrapper,
11021135
is_event_loop_running: Arc<AtomicBool>,
11031136
event_loop: EventLoop<Message>,
11041137
webviews: Arc<Mutex<HashMap<WindowId, WebviewWrapper>>>,
@@ -1159,28 +1192,39 @@ impl Runtime for Wry {
11591192
type Dispatcher = WryDispatcher;
11601193
type Handle = WryHandle;
11611194
type GlobalShortcutManager = GlobalShortcutManagerHandle;
1195+
type ClipboardManager = ClipboardManagerWrapper;
11621196
#[cfg(feature = "system-tray")]
11631197
type TrayHandler = SystemTrayHandle;
11641198

11651199
fn new() -> Result<Self> {
11661200
let event_loop = EventLoop::<Message>::with_user_event();
1167-
let global_shortcut_manager = WryShortcutManager::new(&event_loop);
1168-
let global_shortcut_listeners = GlobalShortcutListeners::default();
11691201
let proxy = event_loop.create_proxy();
11701202
let main_thread_id = current_thread().id();
11711203
let is_event_loop_running = Arc::new(AtomicBool::default());
1204+
1205+
let event_loop_context = EventLoopContext {
1206+
main_thread_id,
1207+
is_event_loop_running: is_event_loop_running.clone(),
1208+
proxy,
1209+
};
1210+
1211+
let global_shortcut_manager = WryShortcutManager::new(&event_loop);
1212+
let global_shortcut_listeners = GlobalShortcutListeners::default();
1213+
let clipboard_manager = Clipboard::new();
1214+
let clipboard_manager_handle = ClipboardManagerWrapper {
1215+
context: event_loop_context.clone(),
1216+
};
1217+
11721218
Ok(Self {
11731219
main_thread_id,
11741220
global_shortcut_manager: Arc::new(Mutex::new(global_shortcut_manager)),
11751221
global_shortcut_manager_handle: GlobalShortcutManagerHandle {
1176-
context: GlobalShortcutManagerContext {
1177-
main_thread_id,
1178-
is_event_loop_running: is_event_loop_running.clone(),
1179-
proxy,
1180-
},
1222+
context: event_loop_context,
11811223
shortcuts: Default::default(),
11821224
listeners: global_shortcut_listeners,
11831225
},
1226+
clipboard_manager: Arc::new(Mutex::new(clipboard_manager)),
1227+
clipboard_manager_handle,
11841228
is_event_loop_running,
11851229
event_loop,
11861230
webviews: Default::default(),
@@ -1209,6 +1253,10 @@ impl Runtime for Wry {
12091253
self.global_shortcut_manager_handle.clone()
12101254
}
12111255

1256+
fn clipboard_manager(&self) -> Self::ClipboardManager {
1257+
self.clipboard_manager_handle.clone()
1258+
}
1259+
12121260
fn create_window<P: Params<Runtime = Self>>(
12131261
&self,
12141262
pending: PendingWindow<P>,
@@ -1298,6 +1346,7 @@ impl Runtime for Wry {
12981346
let tray_context = self.tray_context.clone();
12991347
let global_shortcut_manager = self.global_shortcut_manager.clone();
13001348
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
1349+
let clipboard_manager = self.clipboard_manager.clone();
13011350

13021351
let mut iteration = RunIteration::default();
13031352

@@ -1318,6 +1367,7 @@ impl Runtime for Wry {
13181367
window_event_listeners: window_event_listeners.clone(),
13191368
global_shortcut_manager: global_shortcut_manager.clone(),
13201369
global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
1370+
clipboard_manager: clipboard_manager.clone(),
13211371
#[cfg(feature = "menu")]
13221372
menu_event_listeners: menu_event_listeners.clone(),
13231373
#[cfg(feature = "system-tray")]
@@ -1340,6 +1390,7 @@ impl Runtime for Wry {
13401390
let tray_context = self.tray_context;
13411391
let global_shortcut_manager = self.global_shortcut_manager.clone();
13421392
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
1393+
let clipboard_manager = self.clipboard_manager.clone();
13431394

13441395
self.event_loop.run(move |event, event_loop, control_flow| {
13451396
handle_event_loop(
@@ -1352,6 +1403,7 @@ impl Runtime for Wry {
13521403
window_event_listeners: window_event_listeners.clone(),
13531404
global_shortcut_manager: global_shortcut_manager.clone(),
13541405
global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
1406+
clipboard_manager: clipboard_manager.clone(),
13551407
#[cfg(feature = "menu")]
13561408
menu_event_listeners: menu_event_listeners.clone(),
13571409
#[cfg(feature = "system-tray")]
@@ -1368,6 +1420,7 @@ struct EventLoopIterationContext<'a> {
13681420
window_event_listeners: WindowEventListeners,
13691421
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
13701422
global_shortcut_manager_handle: GlobalShortcutManagerHandle,
1423+
clipboard_manager: Arc<Mutex<Clipboard>>,
13711424
#[cfg(feature = "menu")]
13721425
menu_event_listeners: MenuEventListeners,
13731426
#[cfg(feature = "system-tray")]
@@ -1386,6 +1439,7 @@ fn handle_event_loop(
13861439
window_event_listeners,
13871440
global_shortcut_manager,
13881441
global_shortcut_manager_handle,
1442+
clipboard_manager,
13891443
#[cfg(feature = "menu")]
13901444
menu_event_listeners,
13911445
#[cfg(feature = "system-tray")]
@@ -1689,6 +1743,15 @@ fn handle_event_loop(
16891743
)
16901744
.unwrap(),
16911745
},
1746+
Message::Clipboard(message) => match message {
1747+
ClipboardMessage::WriteText(text, tx) => {
1748+
clipboard_manager.lock().unwrap().write_text(text);
1749+
tx.send(()).unwrap();
1750+
}
1751+
ClipboardMessage::ReadText(tx) => tx
1752+
.send(clipboard_manager.lock().unwrap().read_text())
1753+
.unwrap(),
1754+
},
16921755
},
16931756
_ => (),
16941757
}

core/tauri-runtime/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,25 @@ pub trait GlobalShortcutManager {
270270
fn unregister(&mut self, accelerator: &str) -> crate::Result<()>;
271271
}
272272

273+
/// Clipboard manager.
274+
pub trait ClipboardManager {
275+
/// Writes the text into the clipboard as plain text.
276+
///
277+
/// # Panics
278+
///
279+
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
280+
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
281+
282+
fn write_text<T: Into<String>>(&mut self, text: T) -> Result<()>;
283+
/// Read the content in the clipboard as plain text.
284+
///
285+
/// # Panics
286+
///
287+
/// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
288+
/// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
289+
fn read_text(&self) -> Result<Option<String>>;
290+
}
291+
273292
/// The webview runtime interface.
274293
pub trait Runtime: Sized + 'static {
275294
/// The message dispatcher.
@@ -278,6 +297,8 @@ pub trait Runtime: Sized + 'static {
278297
type Handle: RuntimeHandle<Runtime = Self>;
279298
/// The global shortcut manager type.
280299
type GlobalShortcutManager: GlobalShortcutManager + Clone + Send;
300+
/// The clipboard manager type.
301+
type ClipboardManager: ClipboardManager + Clone + Send;
281302
/// The tray handler type.
282303
#[cfg(feature = "system-tray")]
283304
type TrayHandler: menu::TrayHandle + Clone + Send;
@@ -291,6 +312,9 @@ pub trait Runtime: Sized + 'static {
291312
/// Gets the global shortcut manager.
292313
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager;
293314

315+
/// Gets the clipboard manager.
316+
fn clipboard_manager(&self) -> Self::ClipboardManager;
317+
294318
/// Create a new webview window.
295319
fn create_window<P: Params<Runtime = Self>>(
296320
&self,

0 commit comments

Comments
 (0)