lumen_script/
clipboard.rs1use bevy_ecs::message::{MessageReader, MessageWriter};
10use bevy_ecs::prelude::*;
11use crossbeam_channel::{Receiver, Sender, unbounded};
12use lumen_core::warn_line;
13use lumen_os_clipboard::ClipboardHost;
14
15use crate::runtime::ScriptCommandEvent;
16use crate::{PluginEvent, ScriptCommand, ScriptValue};
17
18pub struct PendingClipboardReads {
25 tx: Sender<(String, String)>,
27 rx: Receiver<(String, String)>,
29}
30
31impl Default for PendingClipboardReads {
32 fn default() -> Self {
33 let (tx, rx) = unbounded();
34 Self { tx, rx }
35 }
36}
37
38pub fn apply_clipboard_commands(
41 mut events: MessageReader<ScriptCommandEvent>,
42 clipboard: Option<NonSend<ClipboardHost>>,
43 pending: Local<PendingClipboardReads>,
44 mut out: MessageWriter<PluginEvent>,
45) {
46 for ev in events.read() {
47 match &ev.0 {
48 ScriptCommand::ClipboardWrite { text } => match clipboard.as_deref() {
49 Some(clip) => {
50 if !clip.write_text(text) {
51 warn_line!("clipboard_write: the clipboard refused the text");
52 }
53 }
54 None => warn_line!("clipboard_write: no clipboard backend"),
55 },
56 ScriptCommand::ClipboardRead { tag } => {
57 let tx = pending.tx.clone();
58 let tag = tag.clone();
59 match clipboard.as_deref() {
60 Some(clip) => clip.read_text_then(move |text| {
61 let _ = tx.send((tag, text));
62 }),
63 None => {
64 let _ = tx.send((tag, String::new()));
65 }
66 }
67 }
68 _ => {}
69 }
70 }
71 for (tag, text) in pending.rx.try_iter() {
72 out.write(clipboard_read_event(tag, text));
73 }
74}
75
76pub fn clipboard_read_event(tag: String, text: String) -> PluginEvent {
81 PluginEvent::Call {
82 event: "clipboard".to_string(),
83 key: tag,
84 fallback: "on_clipboard".to_string(),
85 args: vec![ScriptValue::Str(text)],
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use bevy_ecs::message::{MessageRegistry, Messages};
92
93 use super::*;
94
95 #[test]
98 fn with_no_clipboard_a_read_answers_empty_and_a_write_is_dropped() {
99 let mut world = World::new();
100 MessageRegistry::register_message::<ScriptCommandEvent>(&mut world);
101 crate::runtime::register_plugin_event_message(&mut world);
102 world.write_message(ScriptCommandEvent(ScriptCommand::ClipboardWrite {
103 text: "copied".to_string(),
104 }));
105 world.write_message(ScriptCommandEvent(ScriptCommand::ClipboardRead {
106 tag: "paste".to_string(),
107 }));
108
109 let mut schedule = Schedule::default();
110 schedule.add_systems(apply_clipboard_commands);
111 schedule.run(&mut world);
112
113 let reads = world.resource::<Messages<PluginEvent>>();
114 let delivered: Vec<(&str, &str, &str, &[ScriptValue])> = reads
115 .iter_current_update_messages()
116 .filter_map(|event| match event {
117 PluginEvent::Call {
118 event,
119 key,
120 fallback,
121 args,
122 } => Some((event.as_str(), key.as_str(), fallback.as_str(), &args[..])),
123 PluginEvent::Commands(_) => None,
124 })
125 .collect();
126 assert_eq!(
127 delivered,
128 [(
129 "clipboard",
130 "paste",
131 "on_clipboard",
132 &[ScriptValue::Str(String::new())][..]
133 )]
134 );
135 }
136}