@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,471 @@
1
+ use crate::clipboard_rs::common::Result;
2
+ #[cfg(feature = "image")]
3
+ use crate::clipboard_rs::common::{RustImage, RustImageData};
4
+ use crate::clipboard_rs::{
5
+ Clipboard, ClipboardContent, ClipboardHandler, ClipboardWatcher, ContentFormat,
6
+ };
7
+ use objc2::rc::Retained;
8
+ use objc2::runtime::AnyObject;
9
+ use objc2::AllocAnyThread;
10
+ use objc2::ClassType;
11
+ use objc2::{rc::autoreleasepool, runtime::ProtocolObject};
12
+ use objc2_app_kit::{
13
+ NSImage, NSPasteboard, NSPasteboardItem, NSPasteboardType, NSPasteboardTypeFileURL,
14
+ NSPasteboardTypeHTML, NSPasteboardTypePNG, NSPasteboardTypeRTF, NSPasteboardTypeString,
15
+ NSPasteboardTypeTIFF,
16
+ };
17
+ use objc2_foundation::{NSArray, NSData, NSString, NSURL};
18
+ use std::ffi::c_void;
19
+ use std::sync::mpsc::{self, Receiver, Sender};
20
+ use std::time::Duration;
21
+ use std::vec;
22
+
23
+ pub struct ClipboardContext {
24
+ pasteboard: Retained<NSPasteboard>,
25
+ }
26
+
27
+ pub struct ClipboardWatcherContext<T: ClipboardHandler> {
28
+ pasteboard: Retained<NSPasteboard>,
29
+ handlers: Vec<T>,
30
+ stop_signal: Sender<()>,
31
+ stop_receiver: Receiver<()>,
32
+ running: bool,
33
+ }
34
+
35
+ unsafe impl<T: ClipboardHandler> Send for ClipboardWatcherContext<T> {}
36
+
37
+ fn general_pasteboard() -> Result<Retained<NSPasteboard>> {
38
+ let pasteboard: *mut AnyObject =
39
+ unsafe { objc2::msg_send![NSPasteboard::class(), generalPasteboard] };
40
+ let pasteboard = unsafe { Retained::retain_autoreleased(pasteboard) }
41
+ .ok_or("NSPasteboard generalPasteboard unavailable")?;
42
+ Ok(unsafe { Retained::cast_unchecked(pasteboard) })
43
+ }
44
+
45
+ impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
46
+ pub fn new() -> Result<Self> {
47
+ let ns_pasteboard = general_pasteboard()?;
48
+ let (tx, rx) = mpsc::channel();
49
+ Ok(ClipboardWatcherContext {
50
+ pasteboard: ns_pasteboard,
51
+ handlers: Vec::new(),
52
+ stop_signal: tx,
53
+ stop_receiver: rx,
54
+ running: false,
55
+ })
56
+ }
57
+ }
58
+
59
+ impl<T: ClipboardHandler> ClipboardWatcher<T> for ClipboardWatcherContext<T> {
60
+ fn add_handler(&mut self, handler: T) -> &mut Self {
61
+ self.handlers.push(handler);
62
+ self
63
+ }
64
+
65
+ fn start_watch(&mut self) {
66
+ if self.running {
67
+ println!("already start watch!");
68
+ return;
69
+ }
70
+ if self.handlers.is_empty() {
71
+ println!("no handler, no need to start watch!");
72
+ return;
73
+ }
74
+ self.running = true;
75
+ let mut last_change_count = self.pasteboard.changeCount();
76
+ loop {
77
+ // if receive stop signal, break loop
78
+ if self
79
+ .stop_receiver
80
+ .recv_timeout(Duration::from_millis(500))
81
+ .is_ok()
82
+ {
83
+ break;
84
+ }
85
+ let change_count = self.pasteboard.changeCount();
86
+ if last_change_count == 0 {
87
+ last_change_count = change_count;
88
+ } else if change_count != last_change_count {
89
+ self
90
+ .handlers
91
+ .iter_mut()
92
+ .for_each(|handler| handler.on_clipboard_change());
93
+ last_change_count = change_count;
94
+ }
95
+ }
96
+ self.running = false;
97
+ }
98
+
99
+ fn get_shutdown_channel(&self) -> WatcherShutdown {
100
+ WatcherShutdown {
101
+ stop_signal: self.stop_signal.clone(),
102
+ }
103
+ }
104
+ }
105
+
106
+ impl ClipboardContext {
107
+ pub fn new() -> Result<ClipboardContext> {
108
+ let ns_pasteboard = general_pasteboard()?;
109
+ let clipboard_ctx = ClipboardContext {
110
+ pasteboard: ns_pasteboard,
111
+ };
112
+ Ok(clipboard_ctx)
113
+ }
114
+
115
+ fn plain(&self, r#type: &NSPasteboardType) -> Result<String> {
116
+ autoreleasepool(|_| {
117
+ let contents = self
118
+ .pasteboard
119
+ .pasteboardItems()
120
+ .ok_or("NSPasteboard#pasteboardItems errored")?;
121
+ for item in contents {
122
+ if let Some(string) = item.stringForType(r#type) {
123
+ return Ok(string.to_string());
124
+ }
125
+ }
126
+ Err("No string found".into())
127
+ })
128
+ }
129
+
130
+ fn set_files(&self, files: &[String]) -> Result<()> {
131
+ autoreleasepool(|_| {
132
+ // Build NSArray<NSURL> and write via writeObjects for better compatibility
133
+ let urls: Vec<Retained<NSURL>> = files
134
+ .iter()
135
+ .filter_map(|file_path| {
136
+ // Normalize to local filesystem path, and verify it exists
137
+ let local_path = if file_path.starts_with("file://") {
138
+ file_path.trim_start_matches("file://").to_string()
139
+ } else {
140
+ file_path.clone()
141
+ };
142
+ if !std::path::Path::new(&local_path).exists() {
143
+ return None;
144
+ }
145
+ let ns_path = NSString::from_str(&local_path);
146
+ // NSURL::fileURLWithPath returns a retained NSURL
147
+ let url = NSURL::fileURLWithPath(&ns_path);
148
+ Some(url)
149
+ })
150
+ .collect();
151
+ if urls.is_empty() {
152
+ return Err("no valid files".into());
153
+ }
154
+ let write_objects = NSArray::from_retained_slice(
155
+ &urls
156
+ .iter()
157
+ .map(|u| ProtocolObject::from_retained(u.clone()))
158
+ .collect::<Vec<_>>(),
159
+ );
160
+ if !self.pasteboard.writeObjects(&write_objects) {
161
+ return Err("writeObjects failed for files".into());
162
+ }
163
+ Ok(())
164
+ })
165
+ }
166
+
167
+ // learn from https://github.com/zed-industries/zed/blob/79c1003b344ee513cf97ee8313c38c7c3f02c916/crates/gpui/src/platform/mac/platform.rs#L793
168
+ fn write_to_clipboard(&self, data: &[ClipboardContent], with_clear: bool) -> Result<()> {
169
+ if with_clear {
170
+ self.pasteboard.clearContents();
171
+ }
172
+ autoreleasepool(|_| {
173
+ // we create one NSPasteboardItem for all representations of the same content
174
+ let item = NSPasteboardItem::new();
175
+ let mut has_content_other_than_files = false;
176
+
177
+ for d in data {
178
+ match d {
179
+ ClipboardContent::Text(text) => {
180
+ item.setString_forType(&NSString::from_str(text), unsafe { NSPasteboardTypeString });
181
+ has_content_other_than_files = true;
182
+ }
183
+ ClipboardContent::Rtf(rtf) => {
184
+ let rtf_data =
185
+ unsafe { NSData::dataWithBytes_length(rtf.as_ptr() as *const c_void, rtf.len()) };
186
+ item.setData_forType(&rtf_data, unsafe { NSPasteboardTypeRTF });
187
+ has_content_other_than_files = true;
188
+ }
189
+ ClipboardContent::Html(html) => {
190
+ item.setString_forType(&NSString::from_str(html), unsafe { NSPasteboardTypeHTML });
191
+ has_content_other_than_files = true;
192
+ }
193
+ #[cfg(feature = "image")]
194
+ ClipboardContent::Image(image) => {
195
+ if let Ok(png_buffer) = image.to_png() {
196
+ let bytes = png_buffer.get_bytes();
197
+ let ns_data =
198
+ unsafe { NSData::dataWithBytes_length(bytes.as_ptr() as *mut c_void, bytes.len()) };
199
+ item.setData_forType(&ns_data, unsafe { NSPasteboardTypePNG });
200
+ has_content_other_than_files = true;
201
+ };
202
+ }
203
+ ClipboardContent::Files(files) => {
204
+ // Files are set seperately
205
+ let _ = self.set_files(files);
206
+ }
207
+ ClipboardContent::Other(format, buffer) => {
208
+ let ns_data =
209
+ unsafe { NSData::dataWithBytes_length(buffer.as_ptr() as *mut c_void, buffer.len()) };
210
+ item.setData_forType(&ns_data, &NSString::from_str(format));
211
+ has_content_other_than_files = true;
212
+ }
213
+ }
214
+ }
215
+ if has_content_other_than_files {
216
+ let write_objects = NSArray::from_retained_slice(&[ProtocolObject::from_retained(item)]);
217
+ if !self.pasteboard.writeObjects(&write_objects) {
218
+ return Err("writeObjects failed");
219
+ }
220
+ }
221
+ Ok(())
222
+ })?;
223
+ Ok(())
224
+ }
225
+ }
226
+
227
+ unsafe impl Send for ClipboardContext {}
228
+
229
+ unsafe impl Sync for ClipboardContext {}
230
+
231
+ impl Clipboard for ClipboardContext {
232
+ fn available_formats(&self) -> Result<Vec<String>> {
233
+ let types = self
234
+ .pasteboard
235
+ .types()
236
+ .ok_or("NSPasteboard#types errored")?;
237
+ let res = types.iter().map(|t| t.to_string()).collect();
238
+ Ok(res)
239
+ }
240
+
241
+ fn has(&self, format: ContentFormat) -> bool {
242
+ match format {
243
+ ContentFormat::Text => {
244
+ let types = NSArray::arrayWithObject(unsafe { NSPasteboardTypeString });
245
+ // https://developer.apple.com/documentation/appkit/nspasteboard/1526078-availabletypefromarray?language=objc
246
+ // The first pasteboard type in types that is available on the pasteboard, or nil if the receiver does not contain any of the types in types.
247
+ // self.clipboard.availableTypeFromArray(types)
248
+ self.pasteboard.availableTypeFromArray(&types).is_some()
249
+ }
250
+ ContentFormat::Rtf => {
251
+ let types = NSArray::arrayWithObject(unsafe { NSPasteboardTypeRTF });
252
+ self.pasteboard.availableTypeFromArray(&types).is_some()
253
+ }
254
+ ContentFormat::Html => {
255
+ // Currently only judge whether there is a public.html format
256
+ let types = NSArray::arrayWithObject(unsafe { NSPasteboardTypeHTML });
257
+ self.pasteboard.availableTypeFromArray(&types).is_some()
258
+ }
259
+ #[cfg(feature = "image")]
260
+ ContentFormat::Image => {
261
+ // Currently only judge whether there is a png format
262
+ let types = NSArray::from_retained_slice(&[
263
+ unsafe { NSPasteboardTypePNG }.to_owned(),
264
+ unsafe { NSPasteboardTypeTIFF }.to_owned(),
265
+ ]);
266
+ self.pasteboard.availableTypeFromArray(&types).is_some()
267
+ }
268
+ ContentFormat::Files => {
269
+ let types = NSArray::arrayWithObject(unsafe { NSPasteboardTypeFileURL });
270
+ self.pasteboard.availableTypeFromArray(&types).is_some()
271
+ }
272
+ ContentFormat::Other(format) => {
273
+ let types = NSArray::from_retained_slice(&[NSString::from_str(&format)]);
274
+ self.pasteboard.availableTypeFromArray(&types).is_some()
275
+ }
276
+ }
277
+ }
278
+
279
+ fn clear(&self) -> Result<()> {
280
+ self.pasteboard.clearContents();
281
+ Ok(())
282
+ }
283
+
284
+ fn get_buffer(&self, format: &str) -> Result<Vec<u8>> {
285
+ if let Some(data) = self.pasteboard.dataForType(&NSString::from_str(format)) {
286
+ return Ok(data.to_vec());
287
+ }
288
+ Err("no data".into())
289
+ }
290
+
291
+ fn get_text(&self) -> Result<String> {
292
+ self.plain(unsafe { NSPasteboardTypeString })
293
+ }
294
+
295
+ fn get_rich_text(&self) -> Result<String> {
296
+ self.plain(unsafe { NSPasteboardTypeRTF })
297
+ }
298
+
299
+ fn get_html(&self) -> Result<String> {
300
+ self.plain(unsafe { NSPasteboardTypeHTML })
301
+ }
302
+
303
+ #[cfg(feature = "image")]
304
+ fn get_image(&self) -> Result<RustImageData> {
305
+ autoreleasepool(|_| {
306
+ let png_data = self.pasteboard.dataForType(unsafe { NSPasteboardTypePNG });
307
+ if let Some(data) = png_data {
308
+ return RustImageData::from_bytes(&data.to_vec());
309
+ };
310
+ // if no png data, read NSImage;
311
+ let ns_image = NSImage::initWithPasteboard(NSImage::alloc(), &self.pasteboard);
312
+ if let Some(image) = ns_image {
313
+ let tiff_data = image.TIFFRepresentation();
314
+ if let Some(data) = tiff_data {
315
+ return RustImageData::from_bytes(&data.to_vec());
316
+ }
317
+ };
318
+ Err("no image data".into())
319
+ })
320
+ }
321
+
322
+ fn get_files(&self) -> Result<Vec<String>> {
323
+ autoreleasepool(|_| {
324
+ let mut res = vec![];
325
+ // 使用 readObjectsForClasses 读取 NSURL(文件 URL)
326
+ // 相当于 Objective-C: [pasteboard readObjectsForClasses:@[[NSURL class]] options:nil]
327
+ let classes = NSArray::arrayWithObject(NSURL::class());
328
+ let objects = unsafe {
329
+ self
330
+ .pasteboard
331
+ .readObjectsForClasses_options(&classes, None)
332
+ };
333
+ if let Some(objects) = objects {
334
+ for any_obj in objects {
335
+ // 尝试将返回对象视为 NSURL
336
+ if let Ok(url) = any_obj.downcast::<NSURL>() {
337
+ // 只接受文件 URL
338
+ if url.isFileURL() {
339
+ // 优先使用 path(去掉 file:// 前缀后的本地路径)
340
+ if let Some(path) = url.path() {
341
+ res.push(path.to_string());
342
+ } else {
343
+ // 兜底使用 absoluteString,再去掉前缀
344
+ let abs = if let Some(s) = url.absoluteString() {
345
+ s.to_string()
346
+ } else {
347
+ String::new()
348
+ };
349
+ let file_path = if abs.starts_with("file://") {
350
+ abs.strip_prefix("file://").unwrap_or(&abs).to_string()
351
+ } else {
352
+ abs
353
+ };
354
+ res.push(file_path);
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+ if res.is_empty() {
361
+ return Err("no files".into());
362
+ }
363
+ Ok(res)
364
+ })
365
+ }
366
+
367
+ fn get(&self, formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>> {
368
+ autoreleasepool(|_| {
369
+ let contents = self
370
+ .pasteboard
371
+ .pasteboardItems()
372
+ .ok_or("NSPasteboard#pasteboardItems errored")?;
373
+ let mut results = Vec::new();
374
+ for format in formats {
375
+ for item in contents.iter() {
376
+ match format {
377
+ ContentFormat::Text => {
378
+ if let Some(string) = item.stringForType(unsafe { NSPasteboardTypeString }) {
379
+ results.push(ClipboardContent::Text(string.to_string()));
380
+ break;
381
+ }
382
+ }
383
+ ContentFormat::Rtf => {
384
+ if let Some(string) = item.stringForType(unsafe { NSPasteboardTypeRTF }) {
385
+ results.push(ClipboardContent::Rtf(string.to_string()));
386
+ break;
387
+ }
388
+ }
389
+ ContentFormat::Html => {
390
+ if let Some(string) = item.stringForType(unsafe { NSPasteboardTypeHTML }) {
391
+ results.push(ClipboardContent::Html(string.to_string()));
392
+ break;
393
+ }
394
+ }
395
+ #[cfg(feature = "image")]
396
+ ContentFormat::Image => {
397
+ if let Ok(image) = self.get_image() {
398
+ results.push(ClipboardContent::Image(image));
399
+ break;
400
+ }
401
+ }
402
+ ContentFormat::Files => {
403
+ if let Ok(files) = self.get_files() {
404
+ results.push(ClipboardContent::Files(files));
405
+ break;
406
+ }
407
+ }
408
+ ContentFormat::Other(format_name) => {
409
+ if let Some(data) = item.dataForType(&NSString::from_str(format_name)) {
410
+ results.push(ClipboardContent::Other(
411
+ format_name.to_string(),
412
+ data.to_vec(),
413
+ ));
414
+ break;
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+ Ok(results)
421
+ })
422
+ }
423
+
424
+ fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()> {
425
+ self.write_to_clipboard(&[ClipboardContent::Other(format.to_owned(), buffer)], true)
426
+ }
427
+
428
+ fn set_text(&self, text: String) -> Result<()> {
429
+ self.write_to_clipboard(&[ClipboardContent::Text(text)], true)
430
+ }
431
+
432
+ fn set_rich_text(&self, text: String) -> Result<()> {
433
+ self.write_to_clipboard(&[ClipboardContent::Rtf(text)], true)
434
+ }
435
+
436
+ fn set_html(&self, html: String) -> Result<()> {
437
+ self.write_to_clipboard(&[ClipboardContent::Html(html)], true)
438
+ }
439
+
440
+ #[cfg(feature = "image")]
441
+ fn set_image(&self, image: RustImageData) -> Result<()> {
442
+ self.write_to_clipboard(&[ClipboardContent::Image(image)], true)
443
+ }
444
+
445
+ fn set_files(&self, files: Vec<String>) -> Result<()> {
446
+ if files.is_empty() {
447
+ return Err("file list is empty".into());
448
+ }
449
+ let _ = self.clear();
450
+ self.set_files(&files)
451
+ }
452
+
453
+ fn set(&self, contents: Vec<ClipboardContent>) -> Result<()> {
454
+ if contents.is_empty() {
455
+ return Err(
456
+ "contents is empty, if you want to clear clipboard, please use clear method".into(),
457
+ );
458
+ }
459
+ self.write_to_clipboard(&contents, true)
460
+ }
461
+ }
462
+
463
+ pub struct WatcherShutdown {
464
+ stop_signal: Sender<()>,
465
+ }
466
+
467
+ impl Drop for WatcherShutdown {
468
+ fn drop(&mut self) {
469
+ let _ = self.stop_signal.send(());
470
+ }
471
+ }