@valni/cli-darwin-x64 0.0.0-stage → 0.1.2

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.
Files changed (236) hide show
  1. package/LICENSE +53 -0
  2. package/package.json +18 -5
  3. package/runtime/LICENSE +53 -0
  4. package/runtime/README.md +674 -0
  5. package/runtime/assets/clankolas.png +0 -0
  6. package/runtime/docs/extensions.md +2910 -0
  7. package/runtime/docs/json.md +82 -0
  8. package/runtime/docs/models.md +540 -0
  9. package/runtime/docs/packages.md +228 -0
  10. package/runtime/docs/rpc.md +1480 -0
  11. package/runtime/docs/sdk.md +1183 -0
  12. package/runtime/docs/session-format.md +422 -0
  13. package/runtime/docs/settings.md +315 -0
  14. package/runtime/docs/shell-aliases.md +13 -0
  15. package/runtime/docs/terminal-setup.md +142 -0
  16. package/runtime/docs/termux.md +127 -0
  17. package/runtime/docs/tmux.md +63 -0
  18. package/runtime/docs/tui.md +927 -0
  19. package/runtime/docs/windows.md +17 -0
  20. package/runtime/examples/README.md +25 -0
  21. package/runtime/examples/extensions/README.md +212 -0
  22. package/runtime/examples/extensions/auto-commit-on-exit.ts +49 -0
  23. package/runtime/examples/extensions/bash-spawn-hook.ts +30 -0
  24. package/runtime/examples/extensions/bookmark.ts +50 -0
  25. package/runtime/examples/extensions/border-status-editor.ts +145 -0
  26. package/runtime/examples/extensions/built-in-tool-renderer.ts +249 -0
  27. package/runtime/examples/extensions/claude-rules.ts +86 -0
  28. package/runtime/examples/extensions/commands.ts +72 -0
  29. package/runtime/examples/extensions/confirm-destructive.ts +59 -0
  30. package/runtime/examples/extensions/custom-footer.ts +64 -0
  31. package/runtime/examples/extensions/custom-header.ts +73 -0
  32. package/runtime/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  33. package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  34. package/runtime/examples/extensions/custom-provider-anthropic/package.json +19 -0
  35. package/runtime/examples/extensions/dirty-repo-guard.ts +56 -0
  36. package/runtime/examples/extensions/doom-overlay/README.md +46 -0
  37. package/runtime/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  38. package/runtime/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  39. package/runtime/examples/extensions/doom-overlay/doom/build.sh +152 -0
  40. package/runtime/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  41. package/runtime/examples/extensions/doom-overlay/doom-component.ts +132 -0
  42. package/runtime/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  43. package/runtime/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  44. package/runtime/examples/extensions/doom-overlay/index.ts +74 -0
  45. package/runtime/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  46. package/runtime/examples/extensions/dynamic-resources/SKILL.md +8 -0
  47. package/runtime/examples/extensions/dynamic-resources/dynamic.json +79 -0
  48. package/runtime/examples/extensions/dynamic-resources/dynamic.md +5 -0
  49. package/runtime/examples/extensions/dynamic-resources/index.ts +15 -0
  50. package/runtime/examples/extensions/dynamic-tools.ts +74 -0
  51. package/runtime/examples/extensions/entry-renderer.ts +41 -0
  52. package/runtime/examples/extensions/event-bus.ts +43 -0
  53. package/runtime/examples/extensions/file-trigger.ts +41 -0
  54. package/runtime/examples/extensions/git-checkpoint.ts +53 -0
  55. package/runtime/examples/extensions/git-merge-and-resolve.ts +115 -0
  56. package/runtime/examples/extensions/github-issue-autocomplete.ts +185 -0
  57. package/runtime/examples/extensions/gondolin/index.ts +531 -0
  58. package/runtime/examples/extensions/gondolin/package-lock.json +185 -0
  59. package/runtime/examples/extensions/gondolin/package.json +19 -0
  60. package/runtime/examples/extensions/hello.ts +26 -0
  61. package/runtime/examples/extensions/hidden-thinking-label.ts +53 -0
  62. package/runtime/examples/extensions/inline-bash.ts +94 -0
  63. package/runtime/examples/extensions/input-transform-streaming.ts +39 -0
  64. package/runtime/examples/extensions/input-transform.ts +43 -0
  65. package/runtime/examples/extensions/interactive-shell.ts +196 -0
  66. package/runtime/examples/extensions/mac-system-theme.ts +47 -0
  67. package/runtime/examples/extensions/message-renderer.ts +59 -0
  68. package/runtime/examples/extensions/minimal-mode.ts +426 -0
  69. package/runtime/examples/extensions/modal-editor.ts +85 -0
  70. package/runtime/examples/extensions/model-status.ts +31 -0
  71. package/runtime/examples/extensions/notify.ts +55 -0
  72. package/runtime/examples/extensions/overlay-qa-tests.ts +1450 -0
  73. package/runtime/examples/extensions/overlay-test.ts +153 -0
  74. package/runtime/examples/extensions/permission-gate.ts +34 -0
  75. package/runtime/examples/extensions/pirate.ts +47 -0
  76. package/runtime/examples/extensions/plan-mode/README.md +66 -0
  77. package/runtime/examples/extensions/plan-mode/index.ts +390 -0
  78. package/runtime/examples/extensions/plan-mode/utils.ts +168 -0
  79. package/runtime/examples/extensions/preset.ts +436 -0
  80. package/runtime/examples/extensions/project-trust.ts +64 -0
  81. package/runtime/examples/extensions/prompt-customizer.ts +97 -0
  82. package/runtime/examples/extensions/protected-paths.ts +30 -0
  83. package/runtime/examples/extensions/provider-payload.ts +18 -0
  84. package/runtime/examples/extensions/question.ts +278 -0
  85. package/runtime/examples/extensions/questionnaire.ts +440 -0
  86. package/runtime/examples/extensions/rainbow-editor.ts +88 -0
  87. package/runtime/examples/extensions/reload-runtime.ts +37 -0
  88. package/runtime/examples/extensions/rpc-demo.ts +118 -0
  89. package/runtime/examples/extensions/sandbox/index.ts +321 -0
  90. package/runtime/examples/extensions/sandbox/package-lock.json +92 -0
  91. package/runtime/examples/extensions/sandbox/package.json +19 -0
  92. package/runtime/examples/extensions/send-user-message.ts +97 -0
  93. package/runtime/examples/extensions/session-name.ts +27 -0
  94. package/runtime/examples/extensions/shutdown-command.ts +63 -0
  95. package/runtime/examples/extensions/snake.ts +343 -0
  96. package/runtime/examples/extensions/space-invaders.ts +560 -0
  97. package/runtime/examples/extensions/ssh.ts +220 -0
  98. package/runtime/examples/extensions/status-line.ts +32 -0
  99. package/runtime/examples/extensions/structured-output.ts +65 -0
  100. package/runtime/examples/extensions/subagent/README.md +175 -0
  101. package/runtime/examples/extensions/subagent/agents/planner.md +37 -0
  102. package/runtime/examples/extensions/subagent/agents/reviewer.md +35 -0
  103. package/runtime/examples/extensions/subagent/agents/scout.md +50 -0
  104. package/runtime/examples/extensions/subagent/agents/worker.md +24 -0
  105. package/runtime/examples/extensions/subagent/agents.ts +126 -0
  106. package/runtime/examples/extensions/subagent/index.ts +1009 -0
  107. package/runtime/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  108. package/runtime/examples/extensions/subagent/prompts/implement.md +10 -0
  109. package/runtime/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  110. package/runtime/examples/extensions/system-prompt-header.ts +17 -0
  111. package/runtime/examples/extensions/tic-tac-toe.ts +1008 -0
  112. package/runtime/examples/extensions/timed-confirm.ts +70 -0
  113. package/runtime/examples/extensions/titlebar-spinner.ts +58 -0
  114. package/runtime/examples/extensions/todo.ts +297 -0
  115. package/runtime/examples/extensions/tool-override.ts +144 -0
  116. package/runtime/examples/extensions/tools.ts +146 -0
  117. package/runtime/examples/extensions/trigger-compact.ts +50 -0
  118. package/runtime/examples/extensions/truncated-tool.ts +195 -0
  119. package/runtime/examples/extensions/widget-placement.ts +9 -0
  120. package/runtime/examples/extensions/with-deps/index.ts +32 -0
  121. package/runtime/examples/extensions/with-deps/package-lock.json +31 -0
  122. package/runtime/examples/extensions/with-deps/package.json +22 -0
  123. package/runtime/examples/extensions/working-indicator.ts +123 -0
  124. package/runtime/examples/extensions/working-message-test.ts +25 -0
  125. package/runtime/examples/rpc-extension-ui.ts +632 -0
  126. package/runtime/examples/sdk/01-minimal.ts +26 -0
  127. package/runtime/examples/sdk/02-custom-model.ts +43 -0
  128. package/runtime/examples/sdk/03-custom-prompt.ts +70 -0
  129. package/runtime/examples/sdk/04-skills.ts +55 -0
  130. package/runtime/examples/sdk/05-tools.ts +48 -0
  131. package/runtime/examples/sdk/06-extensions.ts +94 -0
  132. package/runtime/examples/sdk/07-context-files.ts +42 -0
  133. package/runtime/examples/sdk/08-prompt-templates.ts +51 -0
  134. package/runtime/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  135. package/runtime/examples/sdk/10-settings.ts +53 -0
  136. package/runtime/examples/sdk/11-sessions.ts +52 -0
  137. package/runtime/examples/sdk/12-full-control.ts +71 -0
  138. package/runtime/examples/sdk/13-session-runtime.ts +67 -0
  139. package/runtime/examples/sdk/README.md +140 -0
  140. package/runtime/export-html/ansi-to-html.js +249 -0
  141. package/runtime/export-html/index.js +226 -0
  142. package/runtime/export-html/template.css +1066 -0
  143. package/runtime/export-html/template.html +55 -0
  144. package/runtime/export-html/template.js +1864 -0
  145. package/runtime/export-html/tool-renderer.js +108 -0
  146. package/runtime/export-html/vendor/highlight.min.js +1213 -0
  147. package/runtime/export-html/vendor/marked.min.js +78 -0
  148. package/runtime/extensions/pi-switchboard/.github/workflows/ci.yml +51 -0
  149. package/runtime/extensions/pi-switchboard/LICENSE +22 -0
  150. package/runtime/extensions/pi-switchboard/README.md +85 -0
  151. package/runtime/extensions/pi-switchboard/extensions/switchboard/catalog.ts +183 -0
  152. package/runtime/extensions/pi-switchboard/extensions/switchboard/config.ts +97 -0
  153. package/runtime/extensions/pi-switchboard/extensions/switchboard/connections.ts +386 -0
  154. package/runtime/extensions/pi-switchboard/extensions/switchboard/constants.ts +12 -0
  155. package/runtime/extensions/pi-switchboard/extensions/switchboard/device-auth.ts +127 -0
  156. package/runtime/extensions/pi-switchboard/extensions/switchboard/envelope.ts +199 -0
  157. package/runtime/extensions/pi-switchboard/extensions/switchboard/errors.ts +180 -0
  158. package/runtime/extensions/pi-switchboard/extensions/switchboard/index.ts +125 -0
  159. package/runtime/extensions/pi-switchboard/extensions/switchboard/participant.ts +17 -0
  160. package/runtime/extensions/pi-switchboard/extensions/switchboard/sessionStream.ts +131 -0
  161. package/runtime/extensions/pi-switchboard/extensions/switchboard/toolProxy.ts +136 -0
  162. package/runtime/extensions/pi-switchboard/extensions/switchboard/tools.ts +16 -0
  163. package/runtime/extensions/pi-switchboard/extensions/switchboard/types.ts +37 -0
  164. package/runtime/extensions/pi-switchboard/package.json +30 -0
  165. package/runtime/extensions/pi-switchboard/tsconfig.json +13 -0
  166. package/runtime/extensions/switchboard-local/.github/workflows/ci.yml +80 -0
  167. package/runtime/extensions/switchboard-local/.github/workflows/release.yml +46 -0
  168. package/runtime/extensions/switchboard-local/README.md +98 -0
  169. package/runtime/extensions/switchboard-local/extensions/local/engine.ts +28 -0
  170. package/runtime/extensions/switchboard-local/extensions/local/engines/gguf.ts +324 -0
  171. package/runtime/extensions/switchboard-local/extensions/local/engines/llamaBuild.ts +62 -0
  172. package/runtime/extensions/switchboard-local/extensions/local/engines/mlx.ts +245 -0
  173. package/runtime/extensions/switchboard-local/extensions/local/engines/select.ts +38 -0
  174. package/runtime/extensions/switchboard-local/extensions/local/engines/support.ts +151 -0
  175. package/runtime/extensions/switchboard-local/extensions/local/hf.ts +106 -0
  176. package/runtime/extensions/switchboard-local/extensions/local/index.ts +67 -0
  177. package/runtime/extensions/switchboard-local/extensions/local/nativeStream.ts +602 -0
  178. package/runtime/extensions/switchboard-local/extensions/local/paths.ts +44 -0
  179. package/runtime/extensions/switchboard-local/extensions/local/provider.ts +57 -0
  180. package/runtime/extensions/switchboard-local/extensions/local/store.ts +82 -0
  181. package/runtime/extensions/switchboard-local/extensions/local/ui.ts +60 -0
  182. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/apiTypes.js +74 -0
  183. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/client.js +292 -0
  184. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/deviceAuth.js +155 -0
  185. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/errors.js +95 -0
  186. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/index.js +8 -0
  187. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/messages.js +253 -0
  188. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/models.js +19 -0
  189. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/native.js +886 -0
  190. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/wire.js +85 -0
  191. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/package.json +39 -0
  192. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/package.json +29 -0
  193. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/anthropic.ts +127 -0
  194. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/google.ts +111 -0
  195. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/index.ts +8 -0
  196. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.test.ts +25 -0
  197. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.ts +57 -0
  198. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiGeneric.ts +103 -0
  199. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiPro.ts +89 -0
  200. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/profile.ts +120 -0
  201. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.test.ts +82 -0
  202. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.ts +74 -0
  203. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.google.test.ts +137 -0
  204. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.openai.test.ts +241 -0
  205. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.test.ts +174 -0
  206. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.ts +438 -0
  207. package/runtime/extensions/switchboard-local/package.json +34 -0
  208. package/runtime/extensions/switchboard-local/tsconfig.json +13 -0
  209. package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  210. package/runtime/node_modules/@mariozechner/clipboard/.yarnrc.yml +1 -0
  211. package/runtime/node_modules/@mariozechner/clipboard/Cargo.toml +45 -0
  212. package/runtime/node_modules/@mariozechner/clipboard/README.md +60 -0
  213. package/runtime/node_modules/@mariozechner/clipboard/build.rs +5 -0
  214. package/runtime/node_modules/@mariozechner/clipboard/clipboard.darwin-x64.node +0 -0
  215. package/runtime/node_modules/@mariozechner/clipboard/exp.ts +12 -0
  216. package/runtime/node_modules/@mariozechner/clipboard/index.js +606 -0
  217. package/runtime/node_modules/@mariozechner/clipboard/package.json +56 -0
  218. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/common.rs +307 -0
  219. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/mod.rs +93 -0
  220. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/ios.rs +313 -0
  221. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/macos.rs +471 -0
  222. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/mod.rs +252 -0
  223. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/wayland.rs +431 -0
  224. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/win.rs +712 -0
  225. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/x11.rs +916 -0
  226. package/runtime/node_modules/@mariozechner/clipboard/src/lib.rs +194 -0
  227. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/README.md +3 -0
  228. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/clipboard.darwin-x64.node +0 -0
  229. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/package.json +22 -0
  230. package/runtime/package.json +104 -0
  231. package/runtime/photon_rs_bg.wasm +0 -0
  232. package/runtime/theme/dark.json +87 -0
  233. package/runtime/theme/light.json +86 -0
  234. package/runtime/theme/theme-schema.json +340 -0
  235. package/runtime/valni +4 -0
  236. package/README.md +0 -3
@@ -0,0 +1,916 @@
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
+ }