@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,307 @@
1
+ #[cfg(feature = "image")]
2
+ use image::imageops::FilterType;
3
+ #[cfg(feature = "image")]
4
+ use image::{ColorType, DynamicImage, GenericImageView, ImageFormat, RgbaImage};
5
+ use std::error::Error;
6
+ use std::io::Cursor;
7
+ pub type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync + 'static>>;
8
+
9
+ pub trait ContentData {
10
+ fn get_format(&self) -> ContentFormat;
11
+
12
+ fn as_bytes(&self) -> &[u8];
13
+
14
+ fn as_str(&self) -> Result<&str>;
15
+ }
16
+
17
+ pub trait ClipboardHandler {
18
+ fn on_clipboard_change(&mut self);
19
+ }
20
+
21
+ pub enum ClipboardContent {
22
+ Text(String),
23
+ Rtf(String),
24
+ Html(String),
25
+ #[cfg(feature = "image")]
26
+ Image(RustImageData),
27
+ Files(Vec<String>),
28
+ Other(String, Vec<u8>),
29
+ }
30
+
31
+ impl ContentData for ClipboardContent {
32
+ fn get_format(&self) -> ContentFormat {
33
+ match self {
34
+ ClipboardContent::Text(_) => ContentFormat::Text,
35
+ ClipboardContent::Rtf(_) => ContentFormat::Rtf,
36
+ ClipboardContent::Html(_) => ContentFormat::Html,
37
+ #[cfg(feature = "image")]
38
+ ClipboardContent::Image(_) => ContentFormat::Image,
39
+ ClipboardContent::Files(_) => ContentFormat::Files,
40
+ ClipboardContent::Other(format, _) => ContentFormat::Other(format.clone()),
41
+ }
42
+ }
43
+
44
+ fn as_bytes(&self) -> &[u8] {
45
+ match self {
46
+ ClipboardContent::Text(data) => data.as_bytes(),
47
+ ClipboardContent::Rtf(data) => data.as_bytes(),
48
+ ClipboardContent::Html(data) => data.as_bytes(),
49
+ // dynamic image is not supported to as bytes
50
+ #[cfg(feature = "image")]
51
+ ClipboardContent::Image(_) => &[],
52
+ ClipboardContent::Files(data) => {
53
+ // use first file path as data
54
+ if let Some(path) = data.first() {
55
+ path.as_bytes()
56
+ } else {
57
+ &[]
58
+ }
59
+ }
60
+ ClipboardContent::Other(_, data) => data.as_slice(),
61
+ }
62
+ }
63
+
64
+ fn as_str(&self) -> Result<&str> {
65
+ match self {
66
+ ClipboardContent::Text(data) => Ok(data),
67
+ ClipboardContent::Rtf(data) => Ok(data),
68
+ ClipboardContent::Html(data) => Ok(data),
69
+ #[cfg(feature = "image")]
70
+ ClipboardContent::Image(_) => Err("can't convert image to string".into()),
71
+ ClipboardContent::Files(data) => {
72
+ // use first file path as data
73
+ if let Some(path) = data.first() {
74
+ Ok(path)
75
+ } else {
76
+ Err("content is empty".into())
77
+ }
78
+ }
79
+ ClipboardContent::Other(_, data) => std::str::from_utf8(data).map_err(|e| e.into()),
80
+ }
81
+ }
82
+ }
83
+
84
+ #[derive(Clone)]
85
+ pub enum ContentFormat {
86
+ Text,
87
+ Rtf,
88
+ Html,
89
+ #[cfg(feature = "image")]
90
+ Image,
91
+ Files,
92
+ Other(String),
93
+ }
94
+
95
+ #[cfg(feature = "image")]
96
+ pub struct RustImageData {
97
+ width: u32,
98
+ height: u32,
99
+ data: Option<DynamicImage>,
100
+ }
101
+
102
+ /// 此处的 `RustImageBuffer` 已经是带有图片格式的字节流,例如 png,jpeg;
103
+ #[cfg(feature = "image")]
104
+ pub struct RustImageBuffer(Vec<u8>);
105
+
106
+ #[cfg(feature = "image")]
107
+ pub trait RustImage: Sized {
108
+ /// create an empty image
109
+ fn empty() -> Self;
110
+
111
+ fn is_empty(&self) -> bool;
112
+
113
+ /// Read image from file path
114
+ fn from_path(path: &str) -> Result<Self>;
115
+
116
+ /// Create a new image from a byte slice
117
+ fn from_bytes(bytes: &[u8]) -> Result<Self>;
118
+
119
+ fn from_dynamic_image(image: DynamicImage) -> Self;
120
+
121
+ /// width and height
122
+ fn get_size(&self) -> (u32, u32);
123
+
124
+ /// Scale this image down to fit within a specific size.
125
+ /// Returns a new image. The image's aspect ratio is preserved.
126
+ /// The image is scaled to the maximum possible size that fits
127
+ /// within the bounds specified by `nwidth` and `nheight`.
128
+ ///
129
+ /// This method uses a fast integer algorithm where each source
130
+ /// pixel contributes to exactly one target pixel.
131
+ /// May give aliasing artifacts if new size is close to old size.
132
+ fn thumbnail(&self, width: u32, height: u32) -> Result<Self>;
133
+
134
+ /// en: Adjust the size of the image without retaining the aspect ratio
135
+ /// zh: 调整图片大小,不保留长宽比
136
+ fn resize(&self, width: u32, height: u32, filter: FilterType) -> Result<Self>;
137
+
138
+ fn encode_image(
139
+ &self,
140
+ target_color_type: ColorType,
141
+ format: ImageFormat,
142
+ ) -> Result<RustImageBuffer>;
143
+
144
+ fn to_jpeg(&self) -> Result<RustImageBuffer>;
145
+
146
+ /// en: Convert to png format, the returned image is a new image, and the data itself will not be modified
147
+ /// zh: 转为 png 格式,返回的为新的图片,本身数据不会修改
148
+ fn to_png(&self) -> Result<RustImageBuffer>;
149
+
150
+ #[cfg(target_os = "windows")]
151
+ fn to_bitmap(&self) -> Result<RustImageBuffer>;
152
+
153
+ fn save_to_path(&self, path: &str) -> Result<()>;
154
+
155
+ fn get_dynamic_image(&self) -> Result<DynamicImage>;
156
+
157
+ fn to_rgba8(&self) -> Result<RgbaImage>;
158
+ }
159
+
160
+ #[cfg(feature = "image")]
161
+ impl RustImage for RustImageData {
162
+ fn empty() -> Self {
163
+ RustImageData {
164
+ width: 0,
165
+ height: 0,
166
+ data: None,
167
+ }
168
+ }
169
+
170
+ fn is_empty(&self) -> bool {
171
+ self.data.is_none()
172
+ }
173
+
174
+ fn from_path(path: &str) -> Result<Self> {
175
+ let image = image::open(path)?;
176
+ let (width, height) = image.dimensions();
177
+ Ok(RustImageData {
178
+ width,
179
+ height,
180
+ data: Some(image),
181
+ })
182
+ }
183
+
184
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
185
+ let image = image::load_from_memory(bytes)?;
186
+ let (width, height) = image.dimensions();
187
+ Ok(RustImageData {
188
+ width,
189
+ height,
190
+ data: Some(image),
191
+ })
192
+ }
193
+
194
+ fn from_dynamic_image(image: DynamicImage) -> Self {
195
+ let (width, height) = image.dimensions();
196
+ RustImageData {
197
+ width,
198
+ height,
199
+ data: Some(image),
200
+ }
201
+ }
202
+
203
+ fn get_size(&self) -> (u32, u32) {
204
+ (self.width, self.height)
205
+ }
206
+
207
+ fn thumbnail(&self, width: u32, height: u32) -> Result<Self> {
208
+ match &self.data {
209
+ Some(image) => {
210
+ let resized = image.thumbnail(width, height);
211
+ Ok(RustImageData {
212
+ width: resized.width(),
213
+ height: resized.height(),
214
+ data: Some(resized),
215
+ })
216
+ }
217
+ None => Err("image is empty".into()),
218
+ }
219
+ }
220
+
221
+ fn resize(&self, width: u32, height: u32, filter: FilterType) -> Result<Self> {
222
+ match &self.data {
223
+ Some(image) => {
224
+ let resized = image.resize_exact(width, height, filter);
225
+ Ok(RustImageData {
226
+ width: resized.width(),
227
+ height: resized.height(),
228
+ data: Some(resized),
229
+ })
230
+ }
231
+ None => Err("image is empty".into()),
232
+ }
233
+ }
234
+
235
+ fn save_to_path(&self, path: &str) -> Result<()> {
236
+ match &self.data {
237
+ Some(image) => {
238
+ image.save(path)?;
239
+ Ok(())
240
+ }
241
+ None => Err("image is empty".into()),
242
+ }
243
+ }
244
+
245
+ fn get_dynamic_image(&self) -> Result<DynamicImage> {
246
+ match &self.data {
247
+ Some(image) => Ok(image.clone()),
248
+ None => Err("image is empty".into()),
249
+ }
250
+ }
251
+
252
+ fn to_rgba8(&self) -> Result<RgbaImage> {
253
+ match &self.data {
254
+ Some(image) => Ok(image.to_rgba8()),
255
+ None => Err("image is empty".into()),
256
+ }
257
+ }
258
+
259
+ // 私有辅助函数,处理图像格式转换和编码
260
+ fn encode_image(
261
+ &self,
262
+ target_color_type: ColorType,
263
+ format: ImageFormat,
264
+ ) -> Result<RustImageBuffer> {
265
+ let image = self.data.as_ref().ok_or("image is empty")?;
266
+
267
+ let mut bytes = Vec::new();
268
+ match (image.color(), target_color_type) {
269
+ (ColorType::Rgba8, ColorType::Rgb8) => image
270
+ .to_rgb8()
271
+ .write_to(&mut Cursor::new(&mut bytes), format)?,
272
+ (_, ColorType::Rgba8) => image
273
+ .to_rgba8()
274
+ .write_to(&mut Cursor::new(&mut bytes), format)?,
275
+ _ => image.write_to(&mut Cursor::new(&mut bytes), format)?,
276
+ };
277
+ Ok(RustImageBuffer(bytes))
278
+ }
279
+
280
+ fn to_jpeg(&self) -> Result<RustImageBuffer> {
281
+ // JPEG 需要 RGB 格式(不支持 alpha 通道)
282
+ self.encode_image(ColorType::Rgb8, ImageFormat::Jpeg)
283
+ }
284
+
285
+ fn to_png(&self) -> Result<RustImageBuffer> {
286
+ // PNG 使用 RGBA 格式以支持透明度
287
+ self.encode_image(ColorType::Rgba8, ImageFormat::Png)
288
+ }
289
+
290
+ #[cfg(target_os = "windows")]
291
+ fn to_bitmap(&self) -> Result<RustImageBuffer> {
292
+ // BMP 使用 RGBA 格式
293
+ self.encode_image(ColorType::Rgba8, ImageFormat::Bmp)
294
+ }
295
+ }
296
+
297
+ #[cfg(feature = "image")]
298
+ impl RustImageBuffer {
299
+ pub fn get_bytes(&self) -> &[u8] {
300
+ &self.0
301
+ }
302
+
303
+ pub fn save_to_path(&self, path: &str) -> Result<()> {
304
+ std::fs::write(path, &self.0)?;
305
+ Ok(())
306
+ }
307
+ }
@@ -0,0 +1,93 @@
1
+ pub mod common;
2
+ mod platform;
3
+ #[cfg(feature = "image")]
4
+ pub use common::RustImageData;
5
+ pub use common::{ClipboardContent, ClipboardHandler, ContentFormat, Result};
6
+ #[cfg(feature = "image")]
7
+ pub use image::imageops::FilterType;
8
+ #[cfg(target_os = "linux")]
9
+ pub use platform::ClipboardContextX11Options;
10
+ pub use platform::{ClipboardContext, ClipboardWatcherContext, WatcherShutdown};
11
+
12
+ pub trait Clipboard: Send {
13
+ /// zh: 获得剪切板当前内容的所有格式
14
+ /// en: Get all formats of the current content in the clipboard
15
+ fn available_formats(&self) -> Result<Vec<String>>;
16
+
17
+ fn has(&self, format: ContentFormat) -> bool;
18
+
19
+ /// zh: 清空剪切板
20
+ /// en: clear clipboard
21
+ fn clear(&self) -> Result<()>;
22
+
23
+ /// zh: 获得指定格式的数据,以字节数组形式返回
24
+ /// en: Get the data in the specified format in the clipboard as a byte array
25
+ fn get_buffer(&self, format: &str) -> Result<Vec<u8>>;
26
+
27
+ /// zh: 仅获得无格式纯文本,以字符串形式返回
28
+ /// en: Get plain text content in the clipboard as string
29
+ fn get_text(&self) -> Result<String>;
30
+
31
+ /// zh: 获得剪贴板中的富文本内容,以字符串形式返回
32
+ /// en: Get the rich text content in the clipboard as string
33
+ fn get_rich_text(&self) -> Result<String>;
34
+
35
+ /// zh: 获得剪贴板中的html内容,以字符串形式返回
36
+ /// en: Get the html format content in the clipboard as string
37
+ fn get_html(&self) -> Result<String>;
38
+
39
+ #[cfg(feature = "image")]
40
+ fn get_image(&self) -> Result<RustImageData>;
41
+
42
+ fn get_files(&self) -> Result<Vec<String>>;
43
+
44
+ fn get(&self, formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>>;
45
+
46
+ fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()>;
47
+
48
+ fn set_text(&self, text: String) -> Result<()>;
49
+
50
+ fn set_rich_text(&self, text: String) -> Result<()>;
51
+
52
+ fn set_html(&self, html: String) -> Result<()>;
53
+
54
+ #[cfg(feature = "image")]
55
+ fn set_image(&self, image: RustImageData) -> Result<()>;
56
+
57
+ fn set_files(&self, files: Vec<String>) -> Result<()>;
58
+
59
+ /// set image will clear clipboard
60
+ fn set(&self, contents: Vec<ClipboardContent>) -> Result<()>;
61
+ }
62
+
63
+ pub trait ClipboardWatcher<T: ClipboardHandler>: Send {
64
+ /// zh: 添加一个剪切板变化处理器,可以添加多个处理器,处理器需要实现 [`ClipboardHandler`] 这个trait
65
+ /// en: Add a clipboard change handler, you can add multiple handlers, the handler needs to implement the trait [`ClipboardHandler`]
66
+ fn add_handler(&mut self, handler: T) -> &mut Self;
67
+
68
+ /// zh: 开始监视剪切板变化,这是一个阻塞方法,直到监视结束,或者调用了stop方法,所以建议在单独的线程中调用
69
+ /// en: Start monitoring clipboard changes, this is a blocking method, until the monitoring ends, or the stop method is called, so it is recommended to call it in a separate thread
70
+ fn start_watch(&mut self);
71
+
72
+ /// zh: 获得停止监视的通道,可以通过这个通道停止监视
73
+ /// en: Get the channel to stop monitoring, you can stop monitoring through this channel
74
+ fn get_shutdown_channel(&self) -> WatcherShutdown;
75
+ }
76
+
77
+ #[cfg(not(all(
78
+ unix,
79
+ not(any(
80
+ target_os = "macos",
81
+ target_os = "ios",
82
+ target_os = "android",
83
+ target_os = "emscripten"
84
+ ))
85
+ )))]
86
+ impl WatcherShutdown {
87
+ /// zh: 停止监视
88
+ ///
89
+ /// en: stop watching
90
+ pub fn stop(self) {
91
+ drop(self);
92
+ }
93
+ }
@@ -0,0 +1,313 @@
1
+ use crate::clipboard_rs::{
2
+ common::Result, Clipboard, ClipboardContent, ClipboardHandler, ClipboardWatcher, ContentFormat,
3
+ };
4
+ #[cfg(feature = "image")]
5
+ use crate::clipboard_rs::{common::RustImage, RustImageData};
6
+ use objc2::{rc::Retained, runtime::ProtocolObject};
7
+ use objc2_foundation::{ns_string, NSArray, NSData, NSDictionary, NSString};
8
+ use objc2_ui_kit::UIPasteboard;
9
+ #[cfg(feature = "image")]
10
+ use objc2_ui_kit::{UIImage, UIImagePNGRepresentation};
11
+ use std::{
12
+ sync::mpsc::{self, Receiver, Sender},
13
+ time::Duration,
14
+ };
15
+
16
+ pub struct ClipboardContext {
17
+ clipboard: Retained<UIPasteboard>,
18
+ }
19
+ pub struct ClipboardWatcherContext<T: ClipboardHandler> {
20
+ clipboard: Retained<UIPasteboard>,
21
+ handlers: Vec<T>,
22
+ running: bool,
23
+ stop_signal: Sender<()>,
24
+ stop_receiver: Receiver<()>,
25
+ }
26
+
27
+ impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
28
+ pub fn new() -> Result<Self> {
29
+ let clipboard = unsafe { UIPasteboard::generalPasteboard() };
30
+ let (tx, rx) = mpsc::channel();
31
+ Ok(Self {
32
+ clipboard,
33
+ handlers: Vec::new(),
34
+ running: false,
35
+ stop_signal: tx,
36
+ stop_receiver: rx,
37
+ })
38
+ }
39
+ }
40
+
41
+ unsafe impl<T: ClipboardHandler> Send for ClipboardWatcherContext<T> {}
42
+
43
+ impl<T: ClipboardHandler> ClipboardWatcher<T> for ClipboardWatcherContext<T> {
44
+ fn add_handler(&mut self, handler: T) -> &mut Self {
45
+ self.handlers.push(handler);
46
+ self
47
+ }
48
+
49
+ fn start_watch(&mut self) {
50
+ if self.running {
51
+ println!("already start watch!");
52
+ return;
53
+ }
54
+ if self.handlers.is_empty() {
55
+ println!("no handler, no need to start watch!");
56
+ return;
57
+ }
58
+ self.running = true;
59
+ let mut last_change_count = unsafe { self.clipboard.changeCount() };
60
+ loop {
61
+ // if receive stop signal, break loop
62
+ if self
63
+ .stop_receiver
64
+ .recv_timeout(Duration::from_millis(500))
65
+ .is_ok()
66
+ {
67
+ break;
68
+ }
69
+ let change_count = unsafe { self.clipboard.changeCount() };
70
+ if last_change_count == 0 {
71
+ last_change_count = change_count;
72
+ } else if change_count != last_change_count {
73
+ self
74
+ .handlers
75
+ .iter_mut()
76
+ .for_each(|handler| handler.on_clipboard_change());
77
+ last_change_count = change_count;
78
+ }
79
+ }
80
+ self.running = false;
81
+ }
82
+
83
+ fn get_shutdown_channel(&self) -> WatcherShutdown {
84
+ WatcherShutdown {
85
+ stop_signal: self.stop_signal.clone(),
86
+ }
87
+ }
88
+ }
89
+
90
+ pub struct WatcherShutdown {
91
+ stop_signal: Sender<()>,
92
+ }
93
+
94
+ impl Drop for WatcherShutdown {
95
+ fn drop(&mut self) {
96
+ let _ = self.stop_signal.send(());
97
+ }
98
+ }
99
+
100
+ impl ClipboardContext {
101
+ pub fn new() -> Result<Self> {
102
+ let clipboard = unsafe { UIPasteboard::generalPasteboard() };
103
+ Ok(Self { clipboard })
104
+ }
105
+
106
+ fn write_to_clipboard(&self, data: &[ClipboardContent]) -> Result<()> {
107
+ let items = data
108
+ .iter()
109
+ .map(|content| match content {
110
+ ClipboardContent::Text(text) => {
111
+ let ns_text = NSString::from_str(text);
112
+ let pair = unsafe {
113
+ NSDictionary::dictionaryWithObject_forKey(
114
+ ns_text.as_ref(),
115
+ ProtocolObject::from_ref(ns_string!("public.utf8-plain-text")),
116
+ )
117
+ };
118
+ Some(pair)
119
+ }
120
+ ClipboardContent::Rtf(rtf) => {
121
+ let ns_rtf = NSString::from_str(rtf);
122
+ let pair = unsafe {
123
+ NSDictionary::dictionaryWithObject_forKey(
124
+ ns_rtf.as_ref(),
125
+ ProtocolObject::from_ref(ns_string!("public.rtf")),
126
+ )
127
+ };
128
+ Some(pair)
129
+ }
130
+ ClipboardContent::Html(html) => {
131
+ let ns_html = NSString::from_str(html);
132
+ let pair = unsafe {
133
+ NSDictionary::dictionaryWithObject_forKey(
134
+ ns_html.as_ref(),
135
+ ProtocolObject::from_ref(ns_string!("public.html")),
136
+ )
137
+ };
138
+ Some(pair)
139
+ }
140
+ #[cfg(feature = "image")]
141
+ ClipboardContent::Image(image) => {
142
+ let png = image.to_png().unwrap();
143
+ let ns_data = NSData::with_bytes(png.get_bytes());
144
+ let image = unsafe { UIImage::imageWithData(&ns_data) };
145
+ if let Some(image) = image {
146
+ let pair = unsafe {
147
+ NSDictionary::dictionaryWithObject_forKey(
148
+ image.as_ref(),
149
+ ProtocolObject::from_ref(ns_string!("public.png")),
150
+ )
151
+ };
152
+ Some(pair)
153
+ } else {
154
+ None
155
+ }
156
+ }
157
+ _ => None,
158
+ })
159
+ .filter_map(|item| item)
160
+ .collect::<Vec<_>>();
161
+ unsafe {
162
+ self
163
+ .clipboard
164
+ .setItems(&NSArray::from_retained_slice(&items))
165
+ };
166
+ Ok(())
167
+ }
168
+ }
169
+
170
+ impl Clipboard for ClipboardContext {
171
+ fn available_formats(&self) -> Result<Vec<String>> {
172
+ let formats = unsafe { self.clipboard.pasteboardTypes() };
173
+ Ok(formats.iter().map(|f| f.to_string()).collect())
174
+ }
175
+
176
+ fn has(&self, format: ContentFormat) -> bool {
177
+ match format {
178
+ ContentFormat::Text => unsafe { self.clipboard.hasStrings() },
179
+ #[cfg(feature = "image")]
180
+ ContentFormat::Image => unsafe { self.clipboard.hasImages() },
181
+ ContentFormat::Rtf => unsafe {
182
+ self
183
+ .clipboard
184
+ .containsPasteboardTypes(&NSArray::from_slice(&[ns_string!("public.rtf")]))
185
+ },
186
+ ContentFormat::Html => unsafe {
187
+ self
188
+ .clipboard
189
+ .containsPasteboardTypes(&NSArray::from_slice(&[ns_string!("public.html")]))
190
+ },
191
+ ContentFormat::Files => false,
192
+ ContentFormat::Other(format) => unsafe {
193
+ self
194
+ .clipboard
195
+ .containsPasteboardTypes(&NSArray::from_retained_slice(&[NSString::from_str(
196
+ &format,
197
+ )]))
198
+ },
199
+ }
200
+ }
201
+
202
+ fn clear(&self) -> Result<()> {
203
+ unsafe { self.clipboard.setItems(&NSArray::from_slice(&[])) };
204
+ Ok(())
205
+ }
206
+
207
+ fn get_buffer(&self, format: &str) -> Result<Vec<u8>> {
208
+ let ns_format = NSString::from_str(format);
209
+ let data = unsafe { self.clipboard.dataForPasteboardType(&ns_format) };
210
+ if let Some(data) = data {
211
+ Ok(data.to_vec())
212
+ } else {
213
+ Err("No data found".into())
214
+ }
215
+ }
216
+
217
+ fn get_text(&self) -> Result<String> {
218
+ let text = unsafe { self.clipboard.string() };
219
+ if let Some(text) = text {
220
+ Ok(text.to_string())
221
+ } else {
222
+ Err("No text found".into())
223
+ }
224
+ }
225
+
226
+ fn get_rich_text(&self) -> Result<String> {
227
+ let buffer = self.get_buffer("public.rtf")?;
228
+ Ok(String::from_utf8_lossy(&buffer).to_string())
229
+ }
230
+
231
+ fn get_html(&self) -> Result<String> {
232
+ let buffer = self.get_buffer("public.html")?;
233
+ Ok(String::from_utf8_lossy(&buffer).to_string())
234
+ }
235
+
236
+ #[cfg(feature = "image")]
237
+ fn get_image(&self) -> Result<RustImageData> {
238
+ let image = unsafe { self.clipboard.image() };
239
+ if let Some(image) = image {
240
+ let data = unsafe { UIImagePNGRepresentation(&image) };
241
+ if let Some(data) = data {
242
+ let bytes = unsafe { data.as_bytes_unchecked() };
243
+ Ok(RustImageData::from_bytes(bytes)?)
244
+ } else {
245
+ Err("No image data found".into())
246
+ }
247
+ } else {
248
+ Err("No image found".into())
249
+ }
250
+ }
251
+
252
+ fn get_files(&self) -> Result<Vec<String>> {
253
+ Err("Not supported".into())
254
+ }
255
+
256
+ fn get(&self, _formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>> {
257
+ Err("Not supported".into())
258
+ }
259
+
260
+ fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()> {
261
+ let ns_format = NSString::from_str(format);
262
+ let ns_data = NSData::with_bytes(&buffer);
263
+ unsafe {
264
+ self
265
+ .clipboard
266
+ .setData_forPasteboardType(&ns_data, &ns_format)
267
+ }
268
+ Ok(())
269
+ }
270
+
271
+ fn set_text(&self, text: String) -> Result<()> {
272
+ unsafe {
273
+ self
274
+ .clipboard
275
+ .setString(Some(&NSString::from_str(text.as_str())))
276
+ };
277
+ Ok(())
278
+ }
279
+
280
+ fn set_rich_text(&self, _text: String) -> Result<()> {
281
+ Err("Not supported".into())
282
+ }
283
+
284
+ fn set_html(&self, _html: String) -> Result<()> {
285
+ Err("Not supported".into())
286
+ }
287
+
288
+ #[cfg(feature = "image")]
289
+ fn set_image(&self, image: RustImageData) -> Result<()> {
290
+ if image.is_empty() {
291
+ Err("Image is empty".into())
292
+ } else {
293
+ let png = image.to_png()?;
294
+ let ns_data = NSData::with_bytes(png.get_bytes());
295
+ let image = unsafe { UIImage::imageWithData(&ns_data) };
296
+ if let Some(image) = image {
297
+ unsafe { self.clipboard.setImage(Some(&image)) };
298
+ Ok(())
299
+ } else {
300
+ Err("Failed to create image".into())
301
+ }
302
+ }
303
+ }
304
+
305
+ fn set_files(&self, _files: Vec<String>) -> Result<()> {
306
+ Err("Not supported".into())
307
+ }
308
+
309
+ fn set(&self, contents: Vec<ClipboardContent>) -> Result<()> {
310
+ self.write_to_clipboard(&contents)?;
311
+ Ok(())
312
+ }
313
+ }