@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,252 @@
1
+ #[cfg(target_os = "ios")]
2
+ mod ios;
3
+ #[cfg(target_os = "ios")]
4
+ pub use ios::{ClipboardContext, ClipboardWatcherContext, WatcherShutdown};
5
+ #[cfg(target_os = "macos")]
6
+ mod macos;
7
+ #[cfg(target_os = "macos")]
8
+ pub use macos::{ClipboardContext, ClipboardWatcherContext, WatcherShutdown};
9
+ #[cfg(target_os = "windows")]
10
+ mod win;
11
+ #[cfg(target_os = "windows")]
12
+ pub use win::{ClipboardContext, ClipboardWatcherContext, WatcherShutdown};
13
+
14
+ // Linux: runtime detection between Wayland and X11
15
+ #[cfg(all(
16
+ unix,
17
+ not(any(
18
+ target_os = "macos",
19
+ target_os = "ios",
20
+ target_os = "android",
21
+ target_os = "emscripten"
22
+ ))
23
+ ))]
24
+ mod x11;
25
+
26
+ #[cfg(all(
27
+ unix,
28
+ not(any(
29
+ target_os = "macos",
30
+ target_os = "ios",
31
+ target_os = "android",
32
+ target_os = "emscripten"
33
+ )),
34
+ feature = "wayland"
35
+ ))]
36
+ mod wayland;
37
+
38
+ #[cfg(all(
39
+ unix,
40
+ not(any(
41
+ target_os = "macos",
42
+ target_os = "ios",
43
+ target_os = "android",
44
+ target_os = "emscripten"
45
+ ))
46
+ ))]
47
+ pub use x11::ClipboardContextX11Options;
48
+
49
+ #[cfg(all(
50
+ unix,
51
+ not(any(
52
+ target_os = "macos",
53
+ target_os = "ios",
54
+ target_os = "android",
55
+ target_os = "emscripten"
56
+ ))
57
+ ))]
58
+ mod linux_clipboard {
59
+ use crate::clipboard_rs::{
60
+ common::Result, Clipboard, ClipboardContent, ClipboardHandler, ContentFormat,
61
+ };
62
+ #[cfg(feature = "image")]
63
+ use crate::RustImageData;
64
+
65
+ pub enum ClipboardContext {
66
+ X11(super::x11::ClipboardContext),
67
+ #[cfg(feature = "wayland")]
68
+ Wayland(super::wayland::ClipboardContext),
69
+ }
70
+
71
+ impl ClipboardContext {
72
+ pub fn new() -> Result<Self> {
73
+ #[cfg(feature = "wayland")]
74
+ {
75
+ if std::env::var_os("WAYLAND_DISPLAY").is_some() {
76
+ match super::wayland::ClipboardContext::new() {
77
+ Ok(ctx) => return Ok(Self::Wayland(ctx)),
78
+ Err(e) => {
79
+ eprintln!("Wayland clipboard init failed, falling back to X11: {}", e);
80
+ }
81
+ }
82
+ }
83
+ }
84
+ Ok(Self::X11(super::x11::ClipboardContext::new()?))
85
+ }
86
+ }
87
+
88
+ macro_rules! dispatch {
89
+ ($self:expr, $method:ident $(, $arg:expr)*) => {
90
+ match $self {
91
+ ClipboardContext::X11(ctx) => ctx.$method($($arg),*),
92
+ #[cfg(feature = "wayland")]
93
+ ClipboardContext::Wayland(ctx) => ctx.$method($($arg),*),
94
+ }
95
+ };
96
+ }
97
+
98
+ impl Clipboard for ClipboardContext {
99
+ fn available_formats(&self) -> Result<Vec<String>> {
100
+ dispatch!(self, available_formats)
101
+ }
102
+
103
+ fn has(&self, format: ContentFormat) -> bool {
104
+ dispatch!(self, has, format)
105
+ }
106
+
107
+ fn clear(&self) -> Result<()> {
108
+ dispatch!(self, clear)
109
+ }
110
+
111
+ fn get_buffer(&self, format: &str) -> Result<Vec<u8>> {
112
+ dispatch!(self, get_buffer, format)
113
+ }
114
+
115
+ fn get_text(&self) -> Result<String> {
116
+ dispatch!(self, get_text)
117
+ }
118
+
119
+ fn get_rich_text(&self) -> Result<String> {
120
+ dispatch!(self, get_rich_text)
121
+ }
122
+
123
+ fn get_html(&self) -> Result<String> {
124
+ dispatch!(self, get_html)
125
+ }
126
+
127
+ #[cfg(feature = "image")]
128
+ fn get_image(&self) -> Result<RustImageData> {
129
+ dispatch!(self, get_image)
130
+ }
131
+
132
+ fn get_files(&self) -> Result<Vec<String>> {
133
+ dispatch!(self, get_files)
134
+ }
135
+
136
+ fn get(&self, formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>> {
137
+ dispatch!(self, get, formats)
138
+ }
139
+
140
+ fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()> {
141
+ dispatch!(self, set_buffer, format, buffer)
142
+ }
143
+
144
+ fn set_text(&self, text: String) -> Result<()> {
145
+ dispatch!(self, set_text, text)
146
+ }
147
+
148
+ fn set_rich_text(&self, text: String) -> Result<()> {
149
+ dispatch!(self, set_rich_text, text)
150
+ }
151
+
152
+ fn set_html(&self, html: String) -> Result<()> {
153
+ dispatch!(self, set_html, html)
154
+ }
155
+
156
+ #[cfg(feature = "image")]
157
+ fn set_image(&self, image: RustImageData) -> Result<()> {
158
+ dispatch!(self, set_image, image)
159
+ }
160
+
161
+ fn set_files(&self, files: Vec<String>) -> Result<()> {
162
+ dispatch!(self, set_files, files)
163
+ }
164
+
165
+ fn set(&self, contents: Vec<ClipboardContent>) -> Result<()> {
166
+ dispatch!(self, set, contents)
167
+ }
168
+ }
169
+
170
+ unsafe impl Send for ClipboardContext {}
171
+
172
+ pub enum ClipboardWatcherContext<T: ClipboardHandler> {
173
+ X11(super::x11::ClipboardWatcherContext<T>),
174
+ #[cfg(feature = "wayland")]
175
+ Wayland(super::wayland::ClipboardWatcherContext<T>),
176
+ }
177
+
178
+ impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
179
+ pub fn new() -> Result<Self> {
180
+ #[cfg(feature = "wayland")]
181
+ {
182
+ if std::env::var_os("WAYLAND_DISPLAY").is_some() {
183
+ match super::wayland::ClipboardWatcherContext::new() {
184
+ Ok(ctx) => return Ok(Self::Wayland(ctx)),
185
+ Err(e) => {
186
+ eprintln!("Wayland watcher init failed, falling back to X11: {}", e);
187
+ }
188
+ }
189
+ }
190
+ }
191
+ Ok(Self::X11(super::x11::ClipboardWatcherContext::new()?))
192
+ }
193
+ }
194
+
195
+ impl<T: ClipboardHandler + Send> crate::ClipboardWatcher<T> for ClipboardWatcherContext<T> {
196
+ fn add_handler(&mut self, f: T) -> &mut Self {
197
+ match self {
198
+ Self::X11(ctx) => {
199
+ ctx.handlers.push(f);
200
+ }
201
+ #[cfg(feature = "wayland")]
202
+ Self::Wayland(ctx) => {
203
+ ctx.handlers.push(f);
204
+ }
205
+ }
206
+ self
207
+ }
208
+
209
+ fn start_watch(&mut self) {
210
+ match self {
211
+ Self::X11(ctx) => ctx.start_watch_inner(),
212
+ #[cfg(feature = "wayland")]
213
+ Self::Wayland(ctx) => ctx.start_watch_inner(),
214
+ }
215
+ }
216
+
217
+ fn get_shutdown_channel(&self) -> WatcherShutdown {
218
+ match self {
219
+ Self::X11(ctx) => WatcherShutdown {
220
+ _shutdown: Box::new(ctx.get_shutdown_channel()),
221
+ },
222
+ #[cfg(feature = "wayland")]
223
+ Self::Wayland(ctx) => WatcherShutdown {
224
+ _shutdown: Box::new(ctx.get_shutdown_channel()),
225
+ },
226
+ }
227
+ }
228
+ }
229
+
230
+ unsafe impl<T: ClipboardHandler + Send> Send for ClipboardWatcherContext<T> {}
231
+
232
+ pub struct WatcherShutdown {
233
+ _shutdown: Box<dyn std::any::Any + Send>,
234
+ }
235
+
236
+ impl WatcherShutdown {
237
+ pub fn stop(self) {
238
+ drop(self);
239
+ }
240
+ }
241
+ }
242
+
243
+ #[cfg(all(
244
+ unix,
245
+ not(any(
246
+ target_os = "macos",
247
+ target_os = "ios",
248
+ target_os = "android",
249
+ target_os = "emscripten"
250
+ ))
251
+ ))]
252
+ pub use linux_clipboard::{ClipboardContext, ClipboardWatcherContext, WatcherShutdown};
@@ -0,0 +1,431 @@
1
+ use crate::clipboard_rs::{
2
+ common::Result, Clipboard, ClipboardContent, ClipboardHandler, ContentFormat,
3
+ };
4
+
5
+ #[cfg(feature = "image")]
6
+ use crate::clipboard_rs::{common::RustImage, RustImageData};
7
+
8
+ use std::io::Read;
9
+ use std::sync::mpsc::{self, Receiver, Sender};
10
+ use std::time::Duration;
11
+ use wl_clipboard_rs::{
12
+ copy::{self, MimeSource, MimeType, Options, Source},
13
+ paste::{self, get_contents, get_mime_types, ClipboardType, Seat},
14
+ utils::is_primary_selection_supported,
15
+ };
16
+
17
+ const MIME_HTML: &str = "text/html";
18
+ const MIME_RTF: &str = "text/rtf";
19
+ #[cfg(feature = "image")]
20
+ const MIME_PNG: &str = "image/png";
21
+ const MIME_URI_LIST: &str = "text/uri-list";
22
+ const FILE_PATH_PREFIX: &str = "file://";
23
+
24
+ fn read_wayland_clipboard(mime: paste::MimeType) -> Result<Vec<u8>> {
25
+ let result = get_contents(ClipboardType::Regular, Seat::Unspecified, mime);
26
+ match result {
27
+ Ok((mut pipe, _)) => {
28
+ let mut buffer = vec![];
29
+ pipe
30
+ .read_to_end(&mut buffer)
31
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })?;
32
+ Ok(buffer)
33
+ }
34
+ Err(paste::Error::ClipboardEmpty) | Err(paste::Error::NoMimeType) => {
35
+ Err("Clipboard is empty or content type not available".into())
36
+ }
37
+ Err(e) => Err(e.to_string().into()),
38
+ }
39
+ }
40
+
41
+ fn write_wayland_clipboard(sources: Vec<MimeSource>) -> Result<()> {
42
+ let mut opts = Options::new();
43
+ opts.foreground(false);
44
+ opts.clipboard(copy::ClipboardType::Regular);
45
+ opts
46
+ .copy_multi(sources)
47
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })?;
48
+ Ok(())
49
+ }
50
+
51
+ fn get_offered_mime_types() -> Result<std::collections::HashSet<String>> {
52
+ get_mime_types(ClipboardType::Regular, Seat::Unspecified)
53
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })
54
+ }
55
+
56
+ pub struct ClipboardContext;
57
+
58
+ impl ClipboardContext {
59
+ pub fn new() -> Result<Self> {
60
+ match is_primary_selection_supported() {
61
+ Ok(_) => Ok(Self),
62
+ Err(e) => Err(e.to_string().into()),
63
+ }
64
+ }
65
+ }
66
+
67
+ impl Clipboard for ClipboardContext {
68
+ fn available_formats(&self) -> Result<Vec<String>> {
69
+ let mime_types = get_offered_mime_types()?;
70
+ Ok(mime_types.into_iter().collect())
71
+ }
72
+
73
+ fn has(&self, format: ContentFormat) -> bool {
74
+ #[allow(unreachable_patterns)]
75
+ let mime_to_check = match format {
76
+ ContentFormat::Text => "text/plain",
77
+ ContentFormat::Html => MIME_HTML,
78
+ ContentFormat::Rtf => MIME_RTF,
79
+ #[cfg(feature = "image")]
80
+ ContentFormat::Image => MIME_PNG,
81
+ ContentFormat::Files => MIME_URI_LIST,
82
+ ContentFormat::Other(ref s) => s.as_str(),
83
+ #[cfg(not(feature = "image"))]
84
+ _ => return false,
85
+ };
86
+ if let Ok(mime_types) = get_offered_mime_types() {
87
+ // For text, also check text/plain;charset=utf-8
88
+ if mime_to_check == "text/plain" {
89
+ mime_types.iter().any(|m| m.starts_with("text/plain"))
90
+ } else {
91
+ mime_types.contains(mime_to_check)
92
+ }
93
+ } else {
94
+ false
95
+ }
96
+ }
97
+
98
+ fn clear(&self) -> Result<()> {
99
+ copy::clear(copy::ClipboardType::Regular, copy::Seat::All)
100
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })?;
101
+ Ok(())
102
+ }
103
+
104
+ fn get_buffer(&self, format: &str) -> Result<Vec<u8>> {
105
+ read_wayland_clipboard(paste::MimeType::Specific(format))
106
+ }
107
+
108
+ fn get_text(&self) -> Result<String> {
109
+ let bytes = read_wayland_clipboard(paste::MimeType::Text)?;
110
+ String::from_utf8(bytes)
111
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })
112
+ }
113
+
114
+ fn get_rich_text(&self) -> Result<String> {
115
+ let bytes = read_wayland_clipboard(paste::MimeType::Specific(MIME_RTF))?;
116
+ String::from_utf8(bytes)
117
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })
118
+ }
119
+
120
+ fn get_html(&self) -> Result<String> {
121
+ let bytes = read_wayland_clipboard(paste::MimeType::Specific(MIME_HTML))?;
122
+ String::from_utf8(bytes)
123
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })
124
+ }
125
+
126
+ #[cfg(feature = "image")]
127
+ fn get_image(&self) -> Result<RustImageData> {
128
+ let bytes = read_wayland_clipboard(paste::MimeType::Specific(MIME_PNG))?;
129
+ RustImageData::from_bytes(&bytes)
130
+ }
131
+
132
+ fn get_files(&self) -> Result<Vec<String>> {
133
+ let bytes = read_wayland_clipboard(paste::MimeType::Specific(MIME_URI_LIST))?;
134
+ let text = String::from_utf8(bytes)
135
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })?;
136
+ Ok(
137
+ text
138
+ .lines()
139
+ .map(|line| line.trim_end_matches('\r'))
140
+ .filter(|line| {
141
+ !line.starts_with('#') && !line.is_empty() && line.starts_with(FILE_PATH_PREFIX)
142
+ })
143
+ .map(|line| line.to_string())
144
+ .collect(),
145
+ )
146
+ }
147
+
148
+ fn get(&self, formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>> {
149
+ let mut contents = Vec::new();
150
+ for format in formats {
151
+ #[allow(unreachable_patterns)]
152
+ match format {
153
+ ContentFormat::Text => {
154
+ if let Ok(text) = self.get_text() {
155
+ contents.push(ClipboardContent::Text(text));
156
+ }
157
+ }
158
+ ContentFormat::Html => {
159
+ if let Ok(html) = self.get_html() {
160
+ contents.push(ClipboardContent::Html(html));
161
+ }
162
+ }
163
+ ContentFormat::Rtf => {
164
+ if let Ok(rtf) = self.get_rich_text() {
165
+ contents.push(ClipboardContent::Rtf(rtf));
166
+ }
167
+ }
168
+ #[cfg(feature = "image")]
169
+ ContentFormat::Image => {
170
+ if let Ok(image) = self.get_image() {
171
+ contents.push(ClipboardContent::Image(image));
172
+ }
173
+ }
174
+ ContentFormat::Files => {
175
+ if let Ok(files) = self.get_files() {
176
+ contents.push(ClipboardContent::Files(files));
177
+ }
178
+ }
179
+ ContentFormat::Other(mime) => {
180
+ if let Ok(data) = self.get_buffer(mime) {
181
+ contents.push(ClipboardContent::Other(mime.clone(), data));
182
+ }
183
+ }
184
+ #[cfg(not(feature = "image"))]
185
+ _ => {}
186
+ }
187
+ }
188
+ Ok(contents)
189
+ }
190
+
191
+ fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()> {
192
+ write_wayland_clipboard(vec![MimeSource {
193
+ source: Source::Bytes(buffer.into_boxed_slice()),
194
+ mime_type: MimeType::Specific(format.to_string()),
195
+ }])
196
+ }
197
+
198
+ fn set_text(&self, text: String) -> Result<()> {
199
+ write_wayland_clipboard(vec![MimeSource {
200
+ source: Source::Bytes(text.into_bytes().into_boxed_slice()),
201
+ mime_type: MimeType::Text,
202
+ }])
203
+ }
204
+
205
+ fn set_rich_text(&self, text: String) -> Result<()> {
206
+ write_wayland_clipboard(vec![MimeSource {
207
+ source: Source::Bytes(text.into_bytes().into_boxed_slice()),
208
+ mime_type: MimeType::Specific(MIME_RTF.to_string()),
209
+ }])
210
+ }
211
+
212
+ fn set_html(&self, html: String) -> Result<()> {
213
+ write_wayland_clipboard(vec![MimeSource {
214
+ source: Source::Bytes(html.into_bytes().into_boxed_slice()),
215
+ mime_type: MimeType::Specific(MIME_HTML.to_string()),
216
+ }])
217
+ }
218
+
219
+ #[cfg(feature = "image")]
220
+ fn set_image(&self, image: RustImageData) -> Result<()> {
221
+ let bytes = image.to_png()?;
222
+ write_wayland_clipboard(vec![MimeSource {
223
+ source: Source::Bytes(bytes.get_bytes().into()),
224
+ mime_type: MimeType::Specific(MIME_PNG.to_string()),
225
+ }])
226
+ }
227
+
228
+ fn set_files(&self, files: Vec<String>) -> Result<()> {
229
+ // Normalize paths to file:// URIs and use CRLF for text/uri-list
230
+ let uri_list: Vec<String> = files
231
+ .into_iter()
232
+ .map(|f| {
233
+ if f.starts_with(FILE_PATH_PREFIX) {
234
+ f
235
+ } else {
236
+ format!("{FILE_PATH_PREFIX}{f}")
237
+ }
238
+ })
239
+ .collect();
240
+ let data = uri_list.join("\r\n");
241
+ write_wayland_clipboard(vec![MimeSource {
242
+ source: Source::Bytes(data.into_bytes().into_boxed_slice()),
243
+ mime_type: MimeType::Specific(MIME_URI_LIST.to_string()),
244
+ }])
245
+ }
246
+
247
+ fn set(&self, contents: Vec<ClipboardContent>) -> Result<()> {
248
+ let mut sources = Vec::new();
249
+ for content in contents {
250
+ #[allow(unreachable_patterns)]
251
+ match content {
252
+ ClipboardContent::Text(text) => {
253
+ sources.push(MimeSource {
254
+ source: Source::Bytes(text.into_bytes().into_boxed_slice()),
255
+ mime_type: MimeType::Text,
256
+ });
257
+ }
258
+ ClipboardContent::Html(html) => {
259
+ sources.push(MimeSource {
260
+ source: Source::Bytes(html.into_bytes().into_boxed_slice()),
261
+ mime_type: MimeType::Specific(MIME_HTML.to_string()),
262
+ });
263
+ }
264
+ ClipboardContent::Rtf(rtf) => {
265
+ sources.push(MimeSource {
266
+ source: Source::Bytes(rtf.into_bytes().into_boxed_slice()),
267
+ mime_type: MimeType::Specific(MIME_RTF.to_string()),
268
+ });
269
+ }
270
+ #[cfg(feature = "image")]
271
+ ClipboardContent::Image(image) => {
272
+ let bytes = image.to_png()?;
273
+ sources.push(MimeSource {
274
+ source: Source::Bytes(bytes.get_bytes().into()),
275
+ mime_type: MimeType::Specific(MIME_PNG.to_string()),
276
+ });
277
+ }
278
+ ClipboardContent::Files(files) => {
279
+ let uri_list: Vec<String> = files
280
+ .into_iter()
281
+ .map(|f| {
282
+ if f.starts_with(FILE_PATH_PREFIX) {
283
+ f
284
+ } else {
285
+ format!("{FILE_PATH_PREFIX}{f}")
286
+ }
287
+ })
288
+ .collect();
289
+ let data = uri_list.join("\r\n");
290
+ sources.push(MimeSource {
291
+ source: Source::Bytes(data.into_bytes().into_boxed_slice()),
292
+ mime_type: MimeType::Specific(MIME_URI_LIST.to_string()),
293
+ });
294
+ }
295
+ ClipboardContent::Other(mime, data) => {
296
+ sources.push(MimeSource {
297
+ source: Source::Bytes(data.into_boxed_slice()),
298
+ mime_type: MimeType::Specific(mime),
299
+ });
300
+ }
301
+ #[cfg(not(feature = "image"))]
302
+ _ => {}
303
+ }
304
+ }
305
+ write_wayland_clipboard(sources)
306
+ }
307
+ }
308
+
309
+ unsafe impl Send for ClipboardContext {}
310
+
311
+ // Polling-based clipboard watcher for Wayland
312
+ pub struct ClipboardWatcherContext<T: ClipboardHandler> {
313
+ pub(crate) handlers: Vec<T>,
314
+ pub(crate) stop_signal: Sender<()>,
315
+ stop_receiver: Receiver<()>,
316
+ }
317
+
318
+ unsafe impl<T: ClipboardHandler + Send> Send for ClipboardWatcherContext<T> {}
319
+
320
+ impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
321
+ pub fn new() -> Result<Self> {
322
+ // Verify data-control protocol is available (same check as ClipboardContext)
323
+ is_primary_selection_supported()
324
+ .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.to_string().into() })?;
325
+ let (tx, rx) = mpsc::channel();
326
+ Ok(Self {
327
+ handlers: Vec::new(),
328
+ stop_signal: tx,
329
+ stop_receiver: rx,
330
+ })
331
+ }
332
+ }
333
+
334
+ impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
335
+ pub(crate) fn get_shutdown_channel(&self) -> WatcherShutdown {
336
+ WatcherShutdown {
337
+ sender: self.stop_signal.clone(),
338
+ }
339
+ }
340
+
341
+ pub(crate) fn start_watch_inner(&mut self) {
342
+ let mut last_mime_types: Vec<String> = Vec::new();
343
+ let mut last_text = String::new();
344
+
345
+ // Get initial clipboard state
346
+ if let Ok(types) = get_offered_mime_types() {
347
+ last_mime_types = {
348
+ let mut v: Vec<String> = types.into_iter().collect();
349
+ v.sort();
350
+ v
351
+ };
352
+ }
353
+ if let Ok(text) = read_wayland_clipboard(paste::MimeType::Text) {
354
+ if let Ok(s) = String::from_utf8(text) {
355
+ last_text = s;
356
+ }
357
+ }
358
+
359
+ loop {
360
+ if self
361
+ .stop_receiver
362
+ .recv_timeout(Duration::from_millis(500))
363
+ .is_ok()
364
+ {
365
+ break;
366
+ }
367
+
368
+ let changed = if let Ok(types) = get_offered_mime_types() {
369
+ let mut current_types: Vec<String> = types.into_iter().collect();
370
+ current_types.sort();
371
+
372
+ if current_types != last_mime_types {
373
+ // MIME types changed, clipboard definitely changed
374
+ last_mime_types = current_types;
375
+ // Update text cache too
376
+ if let Ok(bytes) = read_wayland_clipboard(paste::MimeType::Text) {
377
+ if let Ok(s) = String::from_utf8(bytes) {
378
+ last_text = s;
379
+ }
380
+ } else {
381
+ last_text.clear();
382
+ }
383
+ true
384
+ } else if let Ok(bytes) = read_wayland_clipboard(paste::MimeType::Text) {
385
+ // Same MIME types, check if text content changed
386
+ if let Ok(current) = String::from_utf8(bytes) {
387
+ if current != last_text {
388
+ last_text = current;
389
+ true
390
+ } else {
391
+ false
392
+ }
393
+ } else {
394
+ false
395
+ }
396
+ } else if !last_text.is_empty() {
397
+ last_text.clear();
398
+ true
399
+ } else {
400
+ false
401
+ }
402
+ } else {
403
+ // No clipboard content available
404
+ if !last_mime_types.is_empty() {
405
+ last_mime_types.clear();
406
+ last_text.clear();
407
+ true
408
+ } else {
409
+ false
410
+ }
411
+ };
412
+
413
+ if changed {
414
+ self
415
+ .handlers
416
+ .iter_mut()
417
+ .for_each(|handler| handler.on_clipboard_change());
418
+ }
419
+ }
420
+ }
421
+ }
422
+
423
+ pub struct WatcherShutdown {
424
+ pub(crate) sender: Sender<()>,
425
+ }
426
+
427
+ impl Drop for WatcherShutdown {
428
+ fn drop(&mut self) {
429
+ let _ = self.sender.send(());
430
+ }
431
+ }