@valni/cli-darwin-x64 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -2
- package/package.json +1 -1
- package/runtime/LICENSE +1 -2
- package/runtime/examples/extensions/auto-commit-on-exit.ts +6 -6
- package/runtime/examples/extensions/bash-spawn-hook.ts +3 -3
- package/runtime/examples/extensions/bookmark.ts +5 -5
- package/runtime/examples/extensions/border-status-editor.ts +7 -7
- package/runtime/examples/extensions/built-in-tool-renderer.ts +6 -6
- package/runtime/examples/extensions/claude-rules.ts +4 -4
- package/runtime/examples/extensions/commands.ts +5 -5
- package/runtime/examples/extensions/confirm-destructive.ts +3 -3
- package/runtime/examples/extensions/custom-footer.ts +2 -2
- package/runtime/examples/extensions/custom-header.ts +5 -5
- package/runtime/examples/extensions/custom-provider-anthropic/index.ts +4 -4
- package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +4 -4
- package/runtime/examples/extensions/custom-provider-anthropic/package.json +3 -3
- package/runtime/examples/extensions/dirty-repo-guard.ts +7 -7
- package/runtime/examples/extensions/doom-overlay/doom/build.sh +4 -4
- package/runtime/examples/extensions/doom-overlay/doom/{doomgeneric_pi.c → doomgeneric_valni.c} +1 -1
- package/runtime/examples/extensions/doom-overlay/index.ts +3 -3
- package/runtime/examples/extensions/dynamic-resources/dynamic.json +0 -1
- package/runtime/examples/extensions/dynamic-resources/index.ts +2 -4
- package/runtime/examples/extensions/dynamic-tools.ts +4 -4
- package/runtime/examples/extensions/entry-renderer.ts +6 -6
- package/runtime/examples/extensions/event-bus.ts +8 -8
- package/runtime/examples/extensions/file-trigger.ts +3 -3
- package/runtime/examples/extensions/git-checkpoint.ts +7 -7
- package/runtime/examples/extensions/git-merge-and-resolve.ts +14 -14
- package/runtime/examples/extensions/github-issue-autocomplete.ts +6 -6
- package/runtime/examples/extensions/hello.ts +2 -2
- package/runtime/examples/extensions/hidden-thinking-label.ts +4 -4
- package/runtime/examples/extensions/inline-bash.ts +5 -5
- package/runtime/examples/extensions/input-transform-streaming.ts +5 -5
- package/runtime/examples/extensions/input-transform.ts +5 -5
- package/runtime/examples/extensions/interactive-shell.ts +3 -3
- package/runtime/examples/extensions/mac-system-theme.ts +5 -5
- package/runtime/examples/extensions/message-renderer.ts +4 -4
- package/runtime/examples/extensions/minimal-mode.ts +9 -9
- package/runtime/examples/extensions/modal-editor.ts +3 -3
- package/runtime/examples/extensions/model-status.ts +3 -3
- package/runtime/examples/extensions/notify.ts +5 -5
- package/runtime/examples/extensions/overlay-qa-tests.ts +20 -20
- package/runtime/examples/extensions/overlay-test.ts +3 -3
- package/runtime/examples/extensions/permission-gate.ts +2 -2
- package/runtime/examples/extensions/pirate.ts +4 -4
- package/runtime/examples/extensions/plan-mode/index.ts +21 -21
- package/runtime/examples/extensions/preset.ts +26 -26
- package/runtime/examples/extensions/project-trust.ts +7 -7
- package/runtime/examples/extensions/prompt-customizer.ts +3 -3
- package/runtime/examples/extensions/protected-paths.ts +2 -2
- package/runtime/examples/extensions/provider-payload.ts +3 -3
- package/runtime/examples/extensions/question.ts +2 -2
- package/runtime/examples/extensions/questionnaire.ts +2 -2
- package/runtime/examples/extensions/rainbow-editor.ts +3 -3
- package/runtime/examples/extensions/reload-runtime.ts +4 -4
- package/runtime/examples/extensions/rpc-demo.ts +10 -10
- package/runtime/examples/extensions/sandbox/index.ts +15 -15
- package/runtime/examples/extensions/sandbox/package-lock.json +4 -4
- package/runtime/examples/extensions/sandbox/package.json +3 -3
- package/runtime/examples/extensions/send-user-message.ts +13 -13
- package/runtime/examples/extensions/session-name.ts +4 -4
- package/runtime/examples/extensions/shutdown-command.ts +10 -10
- package/runtime/examples/extensions/snake.ts +3 -3
- package/runtime/examples/extensions/space-invaders.ts +3 -3
- package/runtime/examples/extensions/ssh.ts +12 -12
- package/runtime/examples/extensions/status-line.ts +4 -4
- package/runtime/examples/extensions/structured-output.ts +2 -2
- package/runtime/examples/extensions/subagent/index.ts +5 -5
- package/runtime/examples/extensions/system-prompt-header.ts +3 -3
- package/runtime/examples/extensions/tic-tac-toe.ts +15 -15
- package/runtime/examples/extensions/timed-confirm.ts +4 -4
- package/runtime/examples/extensions/titlebar-spinner.ts +11 -11
- package/runtime/examples/extensions/todo.ts +5 -5
- package/runtime/examples/extensions/tool-override.ts +5 -5
- package/runtime/examples/extensions/tools.ts +10 -10
- package/runtime/examples/extensions/trigger-compact.ts +3 -3
- package/runtime/examples/extensions/truncated-tool.ts +3 -3
- package/runtime/examples/extensions/widget-placement.ts +2 -2
- package/runtime/examples/extensions/with-deps/index.ts +2 -2
- package/runtime/examples/extensions/with-deps/package-lock.json +4 -4
- package/runtime/examples/extensions/with-deps/package.json +3 -3
- package/runtime/examples/extensions/working-indicator.ts +7 -7
- package/runtime/examples/extensions/working-message-test.ts +3 -3
- package/runtime/examples/sdk/01-minimal.ts +1 -1
- package/runtime/examples/sdk/03-custom-prompt.ts +1 -1
- package/runtime/examples/sdk/04-skills.ts +1 -1
- package/runtime/examples/sdk/05-tools.ts +1 -1
- package/runtime/examples/sdk/06-extensions.ts +11 -11
- package/runtime/examples/sdk/08-prompt-templates.ts +1 -1
- package/runtime/export-html/template.js +3 -3
- package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/runtime/package.json +8 -14
- package/runtime/theme/dark.json +0 -1
- package/runtime/theme/light.json +0 -1
- package/runtime/theme/theme-schema.json +2 -2
- package/runtime/valni +2 -2
- package/runtime/README.md +0 -674
- package/runtime/docs/extensions.md +0 -2910
- package/runtime/docs/json.md +0 -82
- package/runtime/docs/models.md +0 -540
- package/runtime/docs/packages.md +0 -228
- package/runtime/docs/rpc.md +0 -1480
- package/runtime/docs/sdk.md +0 -1183
- package/runtime/docs/session-format.md +0 -422
- package/runtime/docs/settings.md +0 -315
- package/runtime/docs/shell-aliases.md +0 -13
- package/runtime/docs/terminal-setup.md +0 -142
- package/runtime/docs/termux.md +0 -127
- package/runtime/docs/tmux.md +0 -63
- package/runtime/docs/tui.md +0 -927
- package/runtime/docs/windows.md +0 -17
- package/runtime/examples/README.md +0 -25
- package/runtime/examples/extensions/README.md +0 -212
- package/runtime/examples/extensions/doom-overlay/README.md +0 -46
- package/runtime/examples/extensions/dynamic-resources/SKILL.md +0 -8
- package/runtime/examples/extensions/dynamic-resources/dynamic.md +0 -5
- package/runtime/examples/extensions/gondolin/index.ts +0 -531
- package/runtime/examples/extensions/gondolin/package-lock.json +0 -185
- package/runtime/examples/extensions/gondolin/package.json +0 -19
- package/runtime/examples/extensions/plan-mode/README.md +0 -66
- package/runtime/examples/extensions/subagent/README.md +0 -175
- package/runtime/examples/extensions/subagent/agents/planner.md +0 -37
- package/runtime/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/runtime/examples/extensions/subagent/agents/scout.md +0 -50
- package/runtime/examples/extensions/subagent/agents/worker.md +0 -24
- package/runtime/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/runtime/examples/extensions/subagent/prompts/implement.md +0 -10
- package/runtime/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/runtime/examples/sdk/README.md +0 -140
- package/runtime/node_modules/@mariozechner/clipboard/.yarnrc.yml +0 -1
- package/runtime/node_modules/@mariozechner/clipboard/Cargo.toml +0 -45
- package/runtime/node_modules/@mariozechner/clipboard/README.md +0 -60
- package/runtime/node_modules/@mariozechner/clipboard/build.rs +0 -5
- package/runtime/node_modules/@mariozechner/clipboard/clipboard.darwin-x64.node +0 -0
- package/runtime/node_modules/@mariozechner/clipboard/exp.ts +0 -12
- package/runtime/node_modules/@mariozechner/clipboard/index.js +0 -606
- package/runtime/node_modules/@mariozechner/clipboard/package.json +0 -56
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/common.rs +0 -307
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/mod.rs +0 -93
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/ios.rs +0 -313
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/macos.rs +0 -471
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/mod.rs +0 -252
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/wayland.rs +0 -431
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/win.rs +0 -712
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/x11.rs +0 -916
- package/runtime/node_modules/@mariozechner/clipboard/src/lib.rs +0 -194
- package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/README.md +0 -3
- package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/clipboard.darwin-x64.node +0 -0
- package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/package.json +0 -22
|
@@ -1,916 +0,0 @@
|
|
|
1
|
-
use crate::clipboard_rs::{
|
|
2
|
-
common::Result,
|
|
3
|
-
// #[cfg(feature = "image")]
|
|
4
|
-
ClipboardContent,
|
|
5
|
-
ClipboardHandler,
|
|
6
|
-
ContentFormat,
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
#[cfg(feature = "image")]
|
|
10
|
-
use crate::clipboard_rs::{common::RustImage, RustImageData};
|
|
11
|
-
|
|
12
|
-
use crate::Clipboard;
|
|
13
|
-
use std::sync::mpsc::{self, Receiver, Sender};
|
|
14
|
-
use std::{
|
|
15
|
-
sync::{Arc, RwLock},
|
|
16
|
-
thread,
|
|
17
|
-
time::{Duration, Instant},
|
|
18
|
-
};
|
|
19
|
-
use x11rb::{
|
|
20
|
-
connection::Connection,
|
|
21
|
-
protocol::{
|
|
22
|
-
xfixes,
|
|
23
|
-
xproto::{
|
|
24
|
-
Atom, AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, Property,
|
|
25
|
-
SelectionNotifyEvent, SelectionRequestEvent, WindowClass, SELECTION_NOTIFY_EVENT,
|
|
26
|
-
},
|
|
27
|
-
Event,
|
|
28
|
-
},
|
|
29
|
-
rust_connection::RustConnection,
|
|
30
|
-
wrapper::ConnectionExt as _,
|
|
31
|
-
COPY_DEPTH_FROM_PARENT, CURRENT_TIME,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
x11rb::atom_manager! {
|
|
35
|
-
pub Atoms: AtomCookies {
|
|
36
|
-
CLIPBOARD,
|
|
37
|
-
CLIPBOARD_MANAGER,
|
|
38
|
-
PROPERTY,
|
|
39
|
-
SAVE_TARGETS,
|
|
40
|
-
TARGETS,
|
|
41
|
-
ATOM,
|
|
42
|
-
INCR,
|
|
43
|
-
TIMESTAMP,
|
|
44
|
-
MULTIPLE,
|
|
45
|
-
|
|
46
|
-
UTF8_STRING,
|
|
47
|
-
UTF8_MIME_0: b"text/plain;charset=utf-8",
|
|
48
|
-
UTF8_MIME_1: b"text/plain;charset=UTF-8",
|
|
49
|
-
// Text in ISO Latin-1 encoding
|
|
50
|
-
// See: https://tronche.com/gui/x/icccm/sec-2.html#s-2.6.2
|
|
51
|
-
STRING,
|
|
52
|
-
// Text in unknown encoding
|
|
53
|
-
// See: https://tronche.com/gui/x/icccm/sec-2.html#s-2.6.2
|
|
54
|
-
TEXT,
|
|
55
|
-
TEXT_MIME_UNKNOWN: b"text/plain",
|
|
56
|
-
// Rich Text Format
|
|
57
|
-
RTF: b"text/rtf",
|
|
58
|
-
RTF_1: b"text/richtext",
|
|
59
|
-
HTML: b"text/html",
|
|
60
|
-
PNG_MIME: b"image/png",
|
|
61
|
-
FILE_LIST: b"text/uri-list",
|
|
62
|
-
GNOME_COPY_FILES: b"x-special/gnome-copied-files",
|
|
63
|
-
NAUTILUS_FILE_LIST: b"x-special/nautilus-clipboard",
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
pub const DEFAULT_READ_TIMEOUT: u64 = 500;
|
|
68
|
-
|
|
69
|
-
// zh: 用于创建 X11 剪贴板上下文的选项
|
|
70
|
-
// en: Options for creating an X11 clipboard context
|
|
71
|
-
pub struct ClipboardContextX11Options {
|
|
72
|
-
// zh: 剪贴板读取操作超时
|
|
73
|
-
// en: Timeout for clipboard read operations
|
|
74
|
-
pub read_timeout: Option<Duration>,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const FILE_PATH_PREFIX: &str = "file://";
|
|
78
|
-
pub struct ClipboardContext {
|
|
79
|
-
inner: Arc<InnerContext>,
|
|
80
|
-
read_timeout: Option<Duration>,
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
struct ClipboardData {
|
|
84
|
-
format: Atom,
|
|
85
|
-
data: Vec<u8>,
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
struct InnerContext {
|
|
89
|
-
server: XServerContext,
|
|
90
|
-
server_for_write: XServerContext,
|
|
91
|
-
ignore_formats: Vec<Atom>,
|
|
92
|
-
// 此刻待写入的剪贴板内容
|
|
93
|
-
wait_write_data: RwLock<Vec<ClipboardData>>,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
impl InnerContext {
|
|
97
|
-
pub fn new() -> Result<Self> {
|
|
98
|
-
let server = XServerContext::new()?;
|
|
99
|
-
let server_for_write = XServerContext::new()?;
|
|
100
|
-
let wait_write_data = RwLock::new(Vec::new());
|
|
101
|
-
|
|
102
|
-
let ignore_formats = vec![
|
|
103
|
-
server.atoms.TIMESTAMP,
|
|
104
|
-
server.atoms.MULTIPLE,
|
|
105
|
-
server.atoms.TARGETS,
|
|
106
|
-
server.atoms.SAVE_TARGETS,
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
Ok(Self {
|
|
110
|
-
server,
|
|
111
|
-
server_for_write,
|
|
112
|
-
ignore_formats,
|
|
113
|
-
wait_write_data,
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
pub fn handle_selection_request(&self, event: SelectionRequestEvent) -> Result<()> {
|
|
118
|
-
let success;
|
|
119
|
-
let ctx = &self.server_for_write;
|
|
120
|
-
let atoms = ctx.atoms;
|
|
121
|
-
// we are asked for a list of supported conversion targets
|
|
122
|
-
if event.target == atoms.TARGETS {
|
|
123
|
-
let reader = self.wait_write_data.read();
|
|
124
|
-
match reader {
|
|
125
|
-
Ok(data_list) => {
|
|
126
|
-
let mut targets = Vec::with_capacity(10);
|
|
127
|
-
targets.push(atoms.TARGETS);
|
|
128
|
-
targets.push(atoms.SAVE_TARGETS);
|
|
129
|
-
if !data_list.is_empty() {
|
|
130
|
-
data_list.iter().for_each(|data| {
|
|
131
|
-
targets.push(data.format);
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
ctx.conn.change_property32(
|
|
135
|
-
PropMode::REPLACE,
|
|
136
|
-
event.requestor,
|
|
137
|
-
event.property,
|
|
138
|
-
AtomEnum::ATOM,
|
|
139
|
-
&targets,
|
|
140
|
-
)?;
|
|
141
|
-
success = true;
|
|
142
|
-
}
|
|
143
|
-
Err(_) => return Err("Failed to read clipboard data".into()),
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
let reader = self.wait_write_data.read();
|
|
147
|
-
match reader {
|
|
148
|
-
Ok(data_list) => {
|
|
149
|
-
success = match data_list.iter().find(|d| d.format == event.target) {
|
|
150
|
-
Some(data) => {
|
|
151
|
-
ctx.conn.change_property8(
|
|
152
|
-
PropMode::REPLACE,
|
|
153
|
-
event.requestor,
|
|
154
|
-
event.property,
|
|
155
|
-
event.target,
|
|
156
|
-
&data.data,
|
|
157
|
-
)?;
|
|
158
|
-
true
|
|
159
|
-
}
|
|
160
|
-
None => false,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
Err(_) => return Err("Failed to read clipboard data".into()),
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// on failure, we notify the requester of it
|
|
167
|
-
let property = if success {
|
|
168
|
-
event.property
|
|
169
|
-
} else {
|
|
170
|
-
AtomEnum::NONE.into()
|
|
171
|
-
};
|
|
172
|
-
// tell the requester that we finished sending data
|
|
173
|
-
ctx.conn.send_event(
|
|
174
|
-
false,
|
|
175
|
-
event.requestor,
|
|
176
|
-
EventMask::NO_EVENT,
|
|
177
|
-
SelectionNotifyEvent {
|
|
178
|
-
response_type: SELECTION_NOTIFY_EVENT,
|
|
179
|
-
sequence: event.sequence,
|
|
180
|
-
time: event.time,
|
|
181
|
-
requestor: event.requestor,
|
|
182
|
-
selection: event.selection,
|
|
183
|
-
target: event.target,
|
|
184
|
-
property,
|
|
185
|
-
},
|
|
186
|
-
)?;
|
|
187
|
-
ctx.conn.flush()?;
|
|
188
|
-
Ok(())
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
pub fn process_event(
|
|
192
|
-
&self,
|
|
193
|
-
buff: &mut Vec<u8>,
|
|
194
|
-
selection: Atom,
|
|
195
|
-
target: Atom,
|
|
196
|
-
property: Atom,
|
|
197
|
-
timeout: Option<Duration>,
|
|
198
|
-
sequence_number: u64,
|
|
199
|
-
) -> Result<()> {
|
|
200
|
-
let mut is_incr = false;
|
|
201
|
-
let start_time = if timeout.is_some() {
|
|
202
|
-
Some(Instant::now())
|
|
203
|
-
} else {
|
|
204
|
-
None
|
|
205
|
-
};
|
|
206
|
-
let ctx = &self.server;
|
|
207
|
-
let atoms = ctx.atoms;
|
|
208
|
-
loop {
|
|
209
|
-
if timeout
|
|
210
|
-
.into_iter()
|
|
211
|
-
.zip(start_time)
|
|
212
|
-
.next()
|
|
213
|
-
.map(|(timeout, time)| (Instant::now() - time) >= timeout)
|
|
214
|
-
.unwrap_or(false)
|
|
215
|
-
{
|
|
216
|
-
return Err("Timeout while waiting for clipboard data".into());
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let (event, seq) = match ctx.conn.poll_for_event_with_sequence()? {
|
|
220
|
-
Some(event) => event,
|
|
221
|
-
None => {
|
|
222
|
-
thread::park_timeout(Duration::from_millis(50));
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
if seq < sequence_number {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
match event {
|
|
232
|
-
Event::SelectionNotify(event) => {
|
|
233
|
-
if event.selection != selection {
|
|
234
|
-
continue;
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
let target_type = {
|
|
238
|
-
if target == atoms.TARGETS {
|
|
239
|
-
atoms.ATOM
|
|
240
|
-
} else {
|
|
241
|
-
target
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
let reply = ctx
|
|
246
|
-
.conn
|
|
247
|
-
.get_property(
|
|
248
|
-
false,
|
|
249
|
-
event.requestor,
|
|
250
|
-
event.property,
|
|
251
|
-
target_type,
|
|
252
|
-
buff.len() as u32,
|
|
253
|
-
u32::MAX,
|
|
254
|
-
)?
|
|
255
|
-
.reply()?;
|
|
256
|
-
|
|
257
|
-
if reply.type_ == atoms.INCR {
|
|
258
|
-
if let Some(mut value) = reply.value32() {
|
|
259
|
-
if let Some(size) = value.next() {
|
|
260
|
-
buff.reserve(size as usize);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
ctx.conn.delete_property(ctx.win_id, property)?.check()?;
|
|
264
|
-
is_incr = true;
|
|
265
|
-
continue;
|
|
266
|
-
} else if reply.type_ != target && reply.type_ != atoms.ATOM {
|
|
267
|
-
return Err("Clipboard data type mismatch".into());
|
|
268
|
-
}
|
|
269
|
-
buff.extend_from_slice(&reply.value);
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
Event::PropertyNotify(event) if is_incr => {
|
|
274
|
-
if event.state != Property::NEW_VALUE {
|
|
275
|
-
continue;
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
let cookie = ctx
|
|
279
|
-
.conn
|
|
280
|
-
.get_property(false, ctx.win_id, property, AtomEnum::ATOM, 0, 0)?;
|
|
281
|
-
|
|
282
|
-
let length = cookie.reply()?.bytes_after;
|
|
283
|
-
|
|
284
|
-
let cookie =
|
|
285
|
-
ctx
|
|
286
|
-
.conn
|
|
287
|
-
.get_property(true, ctx.win_id, property, AtomEnum::NONE, 0, length)?;
|
|
288
|
-
let reply = cookie.reply()?;
|
|
289
|
-
if reply.type_ != target {
|
|
290
|
-
continue;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
let value = reply.value;
|
|
294
|
-
|
|
295
|
-
if !value.is_empty() {
|
|
296
|
-
buff.extend_from_slice(&value);
|
|
297
|
-
} else {
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
_ => (),
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
Ok(())
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
impl ClipboardContext {
|
|
309
|
-
pub fn new() -> Result<Self> {
|
|
310
|
-
Self::new_with_options(ClipboardContextX11Options {
|
|
311
|
-
read_timeout: Some(Duration::from_millis(DEFAULT_READ_TIMEOUT)),
|
|
312
|
-
})
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
pub fn new_with_options(options: ClipboardContextX11Options) -> Result<Self> {
|
|
316
|
-
// build connection to X server
|
|
317
|
-
let ctx = InnerContext::new()?;
|
|
318
|
-
let ctx_arc = Arc::new(ctx);
|
|
319
|
-
let ctx_clone = ctx_arc.clone();
|
|
320
|
-
|
|
321
|
-
thread::spawn(move || {
|
|
322
|
-
let res = process_server_req(&ctx_clone);
|
|
323
|
-
if let Err(e) = res {
|
|
324
|
-
println!("process_server_req error: {e:?}");
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
Ok(Self {
|
|
329
|
-
inner: ctx_arc,
|
|
330
|
-
read_timeout: options.read_timeout,
|
|
331
|
-
})
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
fn read(&self, format: &Atom) -> Result<Vec<u8>> {
|
|
335
|
-
let ctx = &self.inner.server;
|
|
336
|
-
let atoms = ctx.atoms;
|
|
337
|
-
let clipboard = atoms.CLIPBOARD;
|
|
338
|
-
let win_id = ctx.win_id;
|
|
339
|
-
let cookie =
|
|
340
|
-
ctx
|
|
341
|
-
.conn
|
|
342
|
-
.convert_selection(win_id, clipboard, *format, atoms.PROPERTY, CURRENT_TIME)?;
|
|
343
|
-
let sequence_num = cookie.sequence_number();
|
|
344
|
-
cookie.check()?;
|
|
345
|
-
let mut buff = Vec::new();
|
|
346
|
-
|
|
347
|
-
self.inner.process_event(
|
|
348
|
-
&mut buff,
|
|
349
|
-
clipboard,
|
|
350
|
-
*format,
|
|
351
|
-
atoms.PROPERTY,
|
|
352
|
-
self.read_timeout,
|
|
353
|
-
sequence_num,
|
|
354
|
-
)?;
|
|
355
|
-
|
|
356
|
-
ctx.conn.delete_property(win_id, atoms.PROPERTY)?.check()?;
|
|
357
|
-
|
|
358
|
-
Ok(buff)
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
fn write(&self, data: Vec<ClipboardData>) -> Result<()> {
|
|
362
|
-
let writer = self.inner.wait_write_data.write();
|
|
363
|
-
match writer {
|
|
364
|
-
Ok(mut writer) => {
|
|
365
|
-
writer.clear();
|
|
366
|
-
writer.extend(data);
|
|
367
|
-
}
|
|
368
|
-
Err(_) => return Err("Failed to write clipboard data".into()),
|
|
369
|
-
}
|
|
370
|
-
let ctx = &self.inner.server_for_write;
|
|
371
|
-
let atoms = ctx.atoms;
|
|
372
|
-
|
|
373
|
-
let win_id = ctx.win_id;
|
|
374
|
-
let clipboard = atoms.CLIPBOARD;
|
|
375
|
-
ctx
|
|
376
|
-
.conn
|
|
377
|
-
.set_selection_owner(win_id, clipboard, CURRENT_TIME)?
|
|
378
|
-
.check()?;
|
|
379
|
-
|
|
380
|
-
if ctx
|
|
381
|
-
.conn
|
|
382
|
-
.get_selection_owner(clipboard)?
|
|
383
|
-
.reply()
|
|
384
|
-
.map(|reply| reply.owner == win_id)
|
|
385
|
-
.unwrap_or(false)
|
|
386
|
-
{
|
|
387
|
-
Ok(())
|
|
388
|
-
} else {
|
|
389
|
-
Err("Failed to take ownership of the clipboard".into())
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
fn process_server_req(context: &InnerContext) -> Result<()> {
|
|
395
|
-
let atoms = context.server_for_write.atoms;
|
|
396
|
-
loop {
|
|
397
|
-
match context
|
|
398
|
-
.server_for_write
|
|
399
|
-
.conn
|
|
400
|
-
.wait_for_event()
|
|
401
|
-
.map_err(|e| format!("wait_for_event error: {e:?}"))?
|
|
402
|
-
{
|
|
403
|
-
Event::DestroyNotify(_) => {
|
|
404
|
-
// This window is being destroyed.
|
|
405
|
-
println!("Clipboard server window is being destroyed x_x");
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
Event::SelectionClear(event) => {
|
|
409
|
-
// Someone else has new content in the clipboard, so it is
|
|
410
|
-
// notifying us that we should delete our data now.
|
|
411
|
-
println!("Somebody else owns the clipboard now");
|
|
412
|
-
if event.selection == atoms.CLIPBOARD {
|
|
413
|
-
// Clear the clipboard contents
|
|
414
|
-
context
|
|
415
|
-
.wait_write_data
|
|
416
|
-
.write()
|
|
417
|
-
.map(|mut writer| writer.clear())
|
|
418
|
-
.map_err(|e| format!("write clipboard data error: {e:?}"))?;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
Event::SelectionRequest(event) => {
|
|
422
|
-
// Someone is requesting the clipboard content from us.
|
|
423
|
-
context
|
|
424
|
-
.handle_selection_request(event)
|
|
425
|
-
.map_err(|e| format!("handle_selection_request error: {e:?}"))?;
|
|
426
|
-
}
|
|
427
|
-
Event::SelectionNotify(event) => {
|
|
428
|
-
// We've requested the clipboard content and this is the answer.
|
|
429
|
-
// Considering that this thread is not responsible for reading
|
|
430
|
-
// clipboard contents, this must come from the clipboard manager
|
|
431
|
-
// signaling that the data was handed over successfully.
|
|
432
|
-
if event.selection != atoms.CLIPBOARD_MANAGER {
|
|
433
|
-
println!("Received a `SelectionNotify` from a selection other than the CLIPBOARD_MANAGER. This is unexpected in this thread.");
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
_event => {
|
|
438
|
-
// May be useful for debugging but nothing else really.
|
|
439
|
-
// trace!("Received unwanted event: {:?}", event);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
Ok(())
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
impl Clipboard for ClipboardContext {
|
|
447
|
-
//https://source.chromium.org/chromium/chromium/src/+/main:ui/base/x/x11_clipboard_helper.cc;l=224;drc=4cc063ac39c4a0d1f6011421b259a9715bb16de1;bpv=0;bpt=1
|
|
448
|
-
fn available_formats(&self) -> Result<Vec<String>> {
|
|
449
|
-
let ctx = &self.inner.server;
|
|
450
|
-
let atoms = ctx.atoms;
|
|
451
|
-
self.read(&atoms.TARGETS).map(|data| {
|
|
452
|
-
let mut formats = Vec::new();
|
|
453
|
-
// 解析原子标识符列表
|
|
454
|
-
let atom_list: Vec<Atom> = parse_atom_list(&data);
|
|
455
|
-
for atom in atom_list {
|
|
456
|
-
if self.inner.ignore_formats.contains(&atom) {
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
let atom_name = ctx.get_atom_name(atom).unwrap_or("Unknown".to_string());
|
|
460
|
-
formats.push(atom_name);
|
|
461
|
-
}
|
|
462
|
-
formats
|
|
463
|
-
})
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
fn has(&self, format: crate::ContentFormat) -> bool {
|
|
467
|
-
let ctx = &self.inner.server;
|
|
468
|
-
let atoms = ctx.atoms;
|
|
469
|
-
let atom_list = self.read(&atoms.TARGETS).map(|data| parse_atom_list(&data));
|
|
470
|
-
match atom_list {
|
|
471
|
-
Ok(formats) => match format {
|
|
472
|
-
ContentFormat::Text => formats.contains(&atoms.UTF8_STRING),
|
|
473
|
-
ContentFormat::Rtf => formats.contains(&atoms.RTF),
|
|
474
|
-
ContentFormat::Html => formats.contains(&atoms.HTML),
|
|
475
|
-
#[cfg(feature = "image")]
|
|
476
|
-
ContentFormat::Image => formats.contains(&atoms.PNG_MIME),
|
|
477
|
-
ContentFormat::Files => formats.contains(&atoms.FILE_LIST),
|
|
478
|
-
ContentFormat::Other(format_name) => {
|
|
479
|
-
let atom = ctx.get_atom(format_name.as_str());
|
|
480
|
-
match atom {
|
|
481
|
-
Ok(atom) => formats.contains(&atom),
|
|
482
|
-
Err(_) => false,
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
},
|
|
486
|
-
Err(_) => false,
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
fn clear(&self) -> Result<()> {
|
|
491
|
-
self.write(vec![])
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
fn get_buffer(&self, format: &str) -> Result<Vec<u8>> {
|
|
495
|
-
let atom = self.inner.server.get_atom(format);
|
|
496
|
-
match atom {
|
|
497
|
-
Ok(atom) => self.read(&atom),
|
|
498
|
-
Err(_) => Err("Invalid format".into()),
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
fn get_text(&self) -> Result<String> {
|
|
503
|
-
let atoms = self.inner.server.atoms;
|
|
504
|
-
let text_data = self.read(&atoms.UTF8_STRING);
|
|
505
|
-
text_data.map_or_else(
|
|
506
|
-
|_| Ok("".to_string()),
|
|
507
|
-
|data| Ok(String::from_utf8_lossy(&data).to_string()),
|
|
508
|
-
)
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
fn get_rich_text(&self) -> Result<String> {
|
|
512
|
-
let atoms = self.inner.server.atoms;
|
|
513
|
-
let rtf_data = self.read(&atoms.RTF);
|
|
514
|
-
rtf_data.map_or_else(
|
|
515
|
-
|_| Ok("".to_string()),
|
|
516
|
-
|data| Ok(String::from_utf8_lossy(&data).to_string()),
|
|
517
|
-
)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
fn get_html(&self) -> Result<String> {
|
|
521
|
-
let atoms = self.inner.server.atoms;
|
|
522
|
-
let html_data = self.read(&atoms.HTML);
|
|
523
|
-
html_data.map_or_else(
|
|
524
|
-
|_| Ok("".to_string()),
|
|
525
|
-
|data| Ok(String::from_utf8_lossy(&data).to_string()),
|
|
526
|
-
)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
#[cfg(feature = "image")]
|
|
530
|
-
fn get_image(&self) -> Result<crate::RustImageData> {
|
|
531
|
-
let atoms = self.inner.server.atoms;
|
|
532
|
-
let image_bytes = self.read(&atoms.PNG_MIME);
|
|
533
|
-
match image_bytes {
|
|
534
|
-
Ok(bytes) => {
|
|
535
|
-
let image = RustImageData::from_bytes(&bytes);
|
|
536
|
-
match image {
|
|
537
|
-
Ok(image) => Ok(image),
|
|
538
|
-
Err(_) => Err("Invalid image data".into()),
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
Err(_) => Err("No image data found".into()),
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
fn get_files(&self) -> Result<Vec<String>> {
|
|
546
|
-
let atoms = self.inner.server.atoms;
|
|
547
|
-
let file_list_data = self.read(&atoms.FILE_LIST);
|
|
548
|
-
file_list_data.map_or_else(
|
|
549
|
-
|_| Ok(vec![]),
|
|
550
|
-
|data| {
|
|
551
|
-
let file_list_str = String::from_utf8_lossy(&data).to_string();
|
|
552
|
-
let mut list = Vec::new();
|
|
553
|
-
for line in file_list_str.lines() {
|
|
554
|
-
if !line.starts_with(FILE_PATH_PREFIX) {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
list.push(line.to_string())
|
|
558
|
-
}
|
|
559
|
-
Ok(list)
|
|
560
|
-
},
|
|
561
|
-
)
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
fn get(&self, formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>> {
|
|
565
|
-
let mut contents = Vec::new();
|
|
566
|
-
for format in formats {
|
|
567
|
-
match format {
|
|
568
|
-
ContentFormat::Text => match self.get_text() {
|
|
569
|
-
Ok(text) => contents.push(ClipboardContent::Text(text)),
|
|
570
|
-
Err(_) => continue,
|
|
571
|
-
},
|
|
572
|
-
ContentFormat::Rtf => match self.get_rich_text() {
|
|
573
|
-
Ok(rtf) => contents.push(ClipboardContent::Rtf(rtf)),
|
|
574
|
-
Err(_) => continue,
|
|
575
|
-
},
|
|
576
|
-
ContentFormat::Html => match self.get_html() {
|
|
577
|
-
Ok(html) => contents.push(ClipboardContent::Html(html)),
|
|
578
|
-
Err(_) => continue,
|
|
579
|
-
},
|
|
580
|
-
#[cfg(feature = "image")]
|
|
581
|
-
ContentFormat::Image => match self.get_image() {
|
|
582
|
-
Ok(image) => contents.push(ClipboardContent::Image(image)),
|
|
583
|
-
Err(_) => continue,
|
|
584
|
-
},
|
|
585
|
-
ContentFormat::Files => match self.get_files() {
|
|
586
|
-
Ok(files) => contents.push(ClipboardContent::Files(files)),
|
|
587
|
-
Err(_) => continue,
|
|
588
|
-
},
|
|
589
|
-
ContentFormat::Other(format_name) => match self.get_buffer(format_name) {
|
|
590
|
-
Ok(buffer) => contents.push(ClipboardContent::Other(format_name.clone(), buffer)),
|
|
591
|
-
Err(_) => continue,
|
|
592
|
-
},
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
Ok(contents)
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()> {
|
|
599
|
-
let atom = self.inner.server_for_write.get_atom(format)?;
|
|
600
|
-
let data = ClipboardData {
|
|
601
|
-
format: atom,
|
|
602
|
-
data: buffer,
|
|
603
|
-
};
|
|
604
|
-
self.write(vec![data])
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
fn set_text(&self, text: String) -> Result<()> {
|
|
608
|
-
let atoms = self.inner.server_for_write.atoms;
|
|
609
|
-
let text_bytes = text.as_bytes().to_vec();
|
|
610
|
-
|
|
611
|
-
let data = ClipboardData {
|
|
612
|
-
format: atoms.UTF8_STRING,
|
|
613
|
-
data: text_bytes,
|
|
614
|
-
};
|
|
615
|
-
self.write(vec![data])
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
fn set_rich_text(&self, text: String) -> Result<()> {
|
|
619
|
-
let atoms = self.inner.server_for_write.atoms;
|
|
620
|
-
let text_bytes = text.as_bytes().to_vec();
|
|
621
|
-
|
|
622
|
-
let data = ClipboardData {
|
|
623
|
-
format: atoms.RTF,
|
|
624
|
-
data: text_bytes,
|
|
625
|
-
};
|
|
626
|
-
self.write(vec![data])
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
fn set_html(&self, html: String) -> Result<()> {
|
|
630
|
-
let atoms = self.inner.server_for_write.atoms;
|
|
631
|
-
let html_bytes = html.as_bytes().to_vec();
|
|
632
|
-
|
|
633
|
-
let data = ClipboardData {
|
|
634
|
-
format: atoms.HTML,
|
|
635
|
-
data: html_bytes,
|
|
636
|
-
};
|
|
637
|
-
self.write(vec![data])
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
#[cfg(feature = "image")]
|
|
641
|
-
fn set_image(&self, image: RustImageData) -> Result<()> {
|
|
642
|
-
let atoms = self.inner.server_for_write.atoms;
|
|
643
|
-
let image_png = image.to_png()?;
|
|
644
|
-
let data = ClipboardData {
|
|
645
|
-
format: atoms.PNG_MIME,
|
|
646
|
-
data: image_png.get_bytes().to_vec(),
|
|
647
|
-
};
|
|
648
|
-
self.write(vec![data])
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
fn set_files(&self, files: Vec<String>) -> Result<()> {
|
|
652
|
-
let atoms = self.inner.server_for_write.atoms;
|
|
653
|
-
let data = file_uri_list_to_clipboard_data(files, atoms);
|
|
654
|
-
self.write(data)
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
fn set(&self, contents: Vec<ClipboardContent>) -> Result<()> {
|
|
658
|
-
let mut data = Vec::new();
|
|
659
|
-
let atoms = self.inner.server_for_write.atoms;
|
|
660
|
-
for content in contents {
|
|
661
|
-
match content {
|
|
662
|
-
ClipboardContent::Text(text) => {
|
|
663
|
-
data.push(ClipboardData {
|
|
664
|
-
format: atoms.UTF8_STRING,
|
|
665
|
-
data: text.as_bytes().to_vec(),
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
ClipboardContent::Rtf(rtf) => {
|
|
669
|
-
data.push(ClipboardData {
|
|
670
|
-
format: atoms.RTF,
|
|
671
|
-
data: rtf.as_bytes().to_vec(),
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
ClipboardContent::Html(html) => {
|
|
675
|
-
data.push(ClipboardData {
|
|
676
|
-
format: atoms.HTML,
|
|
677
|
-
data: html.as_bytes().to_vec(),
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
#[cfg(feature = "image")]
|
|
681
|
-
ClipboardContent::Image(image) => {
|
|
682
|
-
let image_png = image.to_png()?;
|
|
683
|
-
data.push(ClipboardData {
|
|
684
|
-
format: atoms.PNG_MIME,
|
|
685
|
-
data: image_png.get_bytes().to_vec(),
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
ClipboardContent::Files(files) => {
|
|
689
|
-
let data_arr = file_uri_list_to_clipboard_data(files, atoms);
|
|
690
|
-
data.extend(data_arr);
|
|
691
|
-
}
|
|
692
|
-
ClipboardContent::Other(format_name, buffer) => {
|
|
693
|
-
let atom = self.inner.server_for_write.get_atom(&format_name)?;
|
|
694
|
-
data.push(ClipboardData {
|
|
695
|
-
format: atom,
|
|
696
|
-
data: buffer,
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
self.write(data)
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
pub struct ClipboardWatcherContext<T: ClipboardHandler> {
|
|
706
|
-
pub(crate) handlers: Vec<T>,
|
|
707
|
-
pub(crate) stop_signal: Sender<()>,
|
|
708
|
-
stop_receiver: Receiver<()>,
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
unsafe impl<T: ClipboardHandler> Send for ClipboardWatcherContext<T> {}
|
|
712
|
-
|
|
713
|
-
impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
|
|
714
|
-
pub fn new() -> Result<Self> {
|
|
715
|
-
let (tx, rx) = mpsc::channel();
|
|
716
|
-
Ok(Self {
|
|
717
|
-
handlers: Vec::new(),
|
|
718
|
-
stop_signal: tx,
|
|
719
|
-
stop_receiver: rx,
|
|
720
|
-
})
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
|
|
725
|
-
pub(crate) fn start_watch_inner(&mut self) {
|
|
726
|
-
let watch_server = XServerContext::new().expect("Failed to create X server context");
|
|
727
|
-
let screen = watch_server
|
|
728
|
-
.conn
|
|
729
|
-
.setup()
|
|
730
|
-
.roots
|
|
731
|
-
.get(watch_server._screen)
|
|
732
|
-
.expect("Failed to get screen");
|
|
733
|
-
|
|
734
|
-
xfixes::query_version(&watch_server.conn, 5, 0)
|
|
735
|
-
.expect("Failed to query version xfixes is not available");
|
|
736
|
-
let cookie = xfixes::select_selection_input(
|
|
737
|
-
&watch_server.conn,
|
|
738
|
-
screen.root,
|
|
739
|
-
watch_server.atoms.CLIPBOARD,
|
|
740
|
-
xfixes::SelectionEventMask::SET_SELECTION_OWNER
|
|
741
|
-
| xfixes::SelectionEventMask::SELECTION_CLIENT_CLOSE
|
|
742
|
-
| xfixes::SelectionEventMask::SELECTION_WINDOW_DESTROY,
|
|
743
|
-
)
|
|
744
|
-
.expect("Failed to select selection input");
|
|
745
|
-
|
|
746
|
-
cookie.check().unwrap();
|
|
747
|
-
|
|
748
|
-
loop {
|
|
749
|
-
if self
|
|
750
|
-
.stop_receiver
|
|
751
|
-
.recv_timeout(Duration::from_millis(500))
|
|
752
|
-
.is_ok()
|
|
753
|
-
{
|
|
754
|
-
break;
|
|
755
|
-
}
|
|
756
|
-
let event = match watch_server
|
|
757
|
-
.conn
|
|
758
|
-
.poll_for_event()
|
|
759
|
-
.expect("Failed to poll for event")
|
|
760
|
-
{
|
|
761
|
-
Some(event) => event,
|
|
762
|
-
None => {
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
if let Event::XfixesSelectionNotify(_) = event {
|
|
767
|
-
self
|
|
768
|
-
.handlers
|
|
769
|
-
.iter_mut()
|
|
770
|
-
.for_each(|handler| handler.on_clipboard_change());
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
pub(crate) fn get_shutdown_channel(&self) -> WatcherShutdown {
|
|
776
|
-
WatcherShutdown {
|
|
777
|
-
sender: self.stop_signal.clone(),
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
pub struct WatcherShutdown {
|
|
783
|
-
pub(crate) sender: Sender<()>,
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
impl Drop for WatcherShutdown {
|
|
787
|
-
fn drop(&mut self) {
|
|
788
|
-
let _ = self.sender.send(());
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
struct XServerContext {
|
|
793
|
-
conn: RustConnection,
|
|
794
|
-
win_id: u32,
|
|
795
|
-
_screen: usize,
|
|
796
|
-
atoms: Atoms,
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
impl XServerContext {
|
|
800
|
-
fn new() -> Result<Self> {
|
|
801
|
-
let (conn, screen) = x11rb::connect(None)?;
|
|
802
|
-
let win_id = conn.generate_id()?;
|
|
803
|
-
{
|
|
804
|
-
let screen = conn.setup().roots.get(screen).unwrap();
|
|
805
|
-
conn
|
|
806
|
-
.create_window(
|
|
807
|
-
COPY_DEPTH_FROM_PARENT,
|
|
808
|
-
win_id,
|
|
809
|
-
screen.root,
|
|
810
|
-
0,
|
|
811
|
-
0,
|
|
812
|
-
1,
|
|
813
|
-
1,
|
|
814
|
-
0,
|
|
815
|
-
WindowClass::INPUT_OUTPUT,
|
|
816
|
-
screen.root_visual,
|
|
817
|
-
&CreateWindowAux::new()
|
|
818
|
-
.event_mask(EventMask::STRUCTURE_NOTIFY | EventMask::PROPERTY_CHANGE),
|
|
819
|
-
)?
|
|
820
|
-
.check()?;
|
|
821
|
-
}
|
|
822
|
-
let atoms = Atoms::new(&conn)?.reply()?;
|
|
823
|
-
Ok(Self {
|
|
824
|
-
conn,
|
|
825
|
-
win_id,
|
|
826
|
-
_screen: screen,
|
|
827
|
-
atoms,
|
|
828
|
-
})
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
fn get_atom(&self, format: &str) -> Result<Atom> {
|
|
832
|
-
let cookie = self.conn.intern_atom(false, format.as_bytes())?;
|
|
833
|
-
Ok(cookie.reply()?.atom)
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
fn get_atom_name(&self, atom: Atom) -> Result<String> {
|
|
837
|
-
let cookie = self.conn.get_atom_name(atom)?;
|
|
838
|
-
Ok(String::from_utf8_lossy(&cookie.reply()?.name).to_string())
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// 解析原子标识符列表
|
|
843
|
-
fn parse_atom_list(data: &[u8]) -> Vec<Atom> {
|
|
844
|
-
data
|
|
845
|
-
.chunks(4)
|
|
846
|
-
.map(|chunk| {
|
|
847
|
-
let mut bytes = [0u8; 4];
|
|
848
|
-
bytes.copy_from_slice(chunk);
|
|
849
|
-
u32::from_ne_bytes(bytes)
|
|
850
|
-
})
|
|
851
|
-
.collect()
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
fn file_uri_list_to_clipboard_data(file_list: Vec<String>, atoms: Atoms) -> Vec<ClipboardData> {
|
|
855
|
-
let uri_list: Vec<String> = file_list
|
|
856
|
-
.iter()
|
|
857
|
-
.map(|f| {
|
|
858
|
-
if f.starts_with(FILE_PATH_PREFIX) {
|
|
859
|
-
f.to_owned()
|
|
860
|
-
} else {
|
|
861
|
-
format!("{FILE_PATH_PREFIX}{f}")
|
|
862
|
-
}
|
|
863
|
-
})
|
|
864
|
-
.collect();
|
|
865
|
-
// 再构造一个 /home/xxx/xxx 这样的路径
|
|
866
|
-
let uri_str_list: Vec<String> = file_list
|
|
867
|
-
.iter()
|
|
868
|
-
.map(|f| {
|
|
869
|
-
if let Some(stripped) = f.strip_prefix(FILE_PATH_PREFIX) {
|
|
870
|
-
stripped.to_owned()
|
|
871
|
-
} else {
|
|
872
|
-
f.to_owned()
|
|
873
|
-
}
|
|
874
|
-
})
|
|
875
|
-
.collect();
|
|
876
|
-
|
|
877
|
-
let data_text_plain = uri_str_list.join("\r\n");
|
|
878
|
-
let data_text_utf8 = uri_str_list.join("\n");
|
|
879
|
-
let data_text_uri_list = uri_list.join("\r\n");
|
|
880
|
-
let data_gnome_copied_files = ["copy\n", uri_list.join("\n").as_str()].concat();
|
|
881
|
-
|
|
882
|
-
vec![
|
|
883
|
-
ClipboardData {
|
|
884
|
-
format: atoms.TEXT_MIME_UNKNOWN,
|
|
885
|
-
data: data_text_plain.as_bytes().to_vec(),
|
|
886
|
-
},
|
|
887
|
-
ClipboardData {
|
|
888
|
-
format: atoms.UTF8_MIME_0,
|
|
889
|
-
data: data_text_plain.as_bytes().to_vec(),
|
|
890
|
-
},
|
|
891
|
-
ClipboardData {
|
|
892
|
-
format: atoms.STRING,
|
|
893
|
-
data: data_text_utf8.as_bytes().to_vec(),
|
|
894
|
-
},
|
|
895
|
-
ClipboardData {
|
|
896
|
-
format: atoms.TEXT,
|
|
897
|
-
data: data_text_utf8.as_bytes().to_vec(),
|
|
898
|
-
},
|
|
899
|
-
ClipboardData {
|
|
900
|
-
format: atoms.UTF8_STRING,
|
|
901
|
-
data: data_text_utf8.as_bytes().to_vec(),
|
|
902
|
-
},
|
|
903
|
-
ClipboardData {
|
|
904
|
-
format: atoms.FILE_LIST,
|
|
905
|
-
data: data_text_uri_list.as_bytes().to_vec(),
|
|
906
|
-
},
|
|
907
|
-
ClipboardData {
|
|
908
|
-
format: atoms.GNOME_COPY_FILES,
|
|
909
|
-
data: data_gnome_copied_files.as_bytes().to_vec(),
|
|
910
|
-
},
|
|
911
|
-
ClipboardData {
|
|
912
|
-
format: atoms.NAUTILUS_FILE_LIST,
|
|
913
|
-
data: data_gnome_copied_files.as_bytes().to_vec(),
|
|
914
|
-
},
|
|
915
|
-
]
|
|
916
|
-
}
|