@valni/cli-darwin-x64 0.1.2 → 0.1.3

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 (149) hide show
  1. package/LICENSE +1 -2
  2. package/package.json +1 -1
  3. package/runtime/LICENSE +1 -2
  4. package/runtime/examples/extensions/auto-commit-on-exit.ts +6 -6
  5. package/runtime/examples/extensions/bash-spawn-hook.ts +3 -3
  6. package/runtime/examples/extensions/bookmark.ts +5 -5
  7. package/runtime/examples/extensions/border-status-editor.ts +7 -7
  8. package/runtime/examples/extensions/built-in-tool-renderer.ts +6 -6
  9. package/runtime/examples/extensions/claude-rules.ts +4 -4
  10. package/runtime/examples/extensions/commands.ts +5 -5
  11. package/runtime/examples/extensions/confirm-destructive.ts +3 -3
  12. package/runtime/examples/extensions/custom-footer.ts +2 -2
  13. package/runtime/examples/extensions/custom-header.ts +5 -5
  14. package/runtime/examples/extensions/custom-provider-anthropic/index.ts +4 -4
  15. package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +4 -4
  16. package/runtime/examples/extensions/custom-provider-anthropic/package.json +3 -3
  17. package/runtime/examples/extensions/dirty-repo-guard.ts +7 -7
  18. package/runtime/examples/extensions/doom-overlay/doom/build.sh +4 -4
  19. package/runtime/examples/extensions/doom-overlay/doom/{doomgeneric_pi.c → doomgeneric_valni.c} +1 -1
  20. package/runtime/examples/extensions/doom-overlay/index.ts +3 -3
  21. package/runtime/examples/extensions/dynamic-resources/dynamic.json +0 -1
  22. package/runtime/examples/extensions/dynamic-resources/index.ts +2 -4
  23. package/runtime/examples/extensions/dynamic-tools.ts +4 -4
  24. package/runtime/examples/extensions/entry-renderer.ts +6 -6
  25. package/runtime/examples/extensions/event-bus.ts +8 -8
  26. package/runtime/examples/extensions/file-trigger.ts +3 -3
  27. package/runtime/examples/extensions/git-checkpoint.ts +7 -7
  28. package/runtime/examples/extensions/git-merge-and-resolve.ts +14 -14
  29. package/runtime/examples/extensions/github-issue-autocomplete.ts +6 -6
  30. package/runtime/examples/extensions/hello.ts +2 -2
  31. package/runtime/examples/extensions/hidden-thinking-label.ts +4 -4
  32. package/runtime/examples/extensions/inline-bash.ts +5 -5
  33. package/runtime/examples/extensions/input-transform-streaming.ts +5 -5
  34. package/runtime/examples/extensions/input-transform.ts +5 -5
  35. package/runtime/examples/extensions/interactive-shell.ts +3 -3
  36. package/runtime/examples/extensions/mac-system-theme.ts +5 -5
  37. package/runtime/examples/extensions/message-renderer.ts +4 -4
  38. package/runtime/examples/extensions/minimal-mode.ts +9 -9
  39. package/runtime/examples/extensions/modal-editor.ts +3 -3
  40. package/runtime/examples/extensions/model-status.ts +3 -3
  41. package/runtime/examples/extensions/notify.ts +5 -5
  42. package/runtime/examples/extensions/overlay-qa-tests.ts +20 -20
  43. package/runtime/examples/extensions/overlay-test.ts +3 -3
  44. package/runtime/examples/extensions/permission-gate.ts +2 -2
  45. package/runtime/examples/extensions/pirate.ts +4 -4
  46. package/runtime/examples/extensions/plan-mode/index.ts +21 -21
  47. package/runtime/examples/extensions/preset.ts +26 -26
  48. package/runtime/examples/extensions/project-trust.ts +7 -7
  49. package/runtime/examples/extensions/prompt-customizer.ts +3 -3
  50. package/runtime/examples/extensions/protected-paths.ts +2 -2
  51. package/runtime/examples/extensions/provider-payload.ts +3 -3
  52. package/runtime/examples/extensions/question.ts +2 -2
  53. package/runtime/examples/extensions/questionnaire.ts +2 -2
  54. package/runtime/examples/extensions/rainbow-editor.ts +3 -3
  55. package/runtime/examples/extensions/reload-runtime.ts +4 -4
  56. package/runtime/examples/extensions/rpc-demo.ts +10 -10
  57. package/runtime/examples/extensions/sandbox/index.ts +15 -15
  58. package/runtime/examples/extensions/sandbox/package-lock.json +4 -4
  59. package/runtime/examples/extensions/sandbox/package.json +3 -3
  60. package/runtime/examples/extensions/send-user-message.ts +13 -13
  61. package/runtime/examples/extensions/session-name.ts +4 -4
  62. package/runtime/examples/extensions/shutdown-command.ts +10 -10
  63. package/runtime/examples/extensions/snake.ts +3 -3
  64. package/runtime/examples/extensions/space-invaders.ts +3 -3
  65. package/runtime/examples/extensions/ssh.ts +12 -12
  66. package/runtime/examples/extensions/status-line.ts +4 -4
  67. package/runtime/examples/extensions/structured-output.ts +2 -2
  68. package/runtime/examples/extensions/subagent/index.ts +5 -5
  69. package/runtime/examples/extensions/system-prompt-header.ts +3 -3
  70. package/runtime/examples/extensions/tic-tac-toe.ts +15 -15
  71. package/runtime/examples/extensions/timed-confirm.ts +4 -4
  72. package/runtime/examples/extensions/titlebar-spinner.ts +11 -11
  73. package/runtime/examples/extensions/todo.ts +5 -5
  74. package/runtime/examples/extensions/tool-override.ts +5 -5
  75. package/runtime/examples/extensions/tools.ts +10 -10
  76. package/runtime/examples/extensions/trigger-compact.ts +3 -3
  77. package/runtime/examples/extensions/truncated-tool.ts +3 -3
  78. package/runtime/examples/extensions/widget-placement.ts +2 -2
  79. package/runtime/examples/extensions/with-deps/index.ts +2 -2
  80. package/runtime/examples/extensions/with-deps/package-lock.json +4 -4
  81. package/runtime/examples/extensions/with-deps/package.json +3 -3
  82. package/runtime/examples/extensions/working-indicator.ts +7 -7
  83. package/runtime/examples/extensions/working-message-test.ts +3 -3
  84. package/runtime/examples/sdk/01-minimal.ts +1 -1
  85. package/runtime/examples/sdk/03-custom-prompt.ts +1 -1
  86. package/runtime/examples/sdk/04-skills.ts +1 -1
  87. package/runtime/examples/sdk/05-tools.ts +1 -1
  88. package/runtime/examples/sdk/06-extensions.ts +11 -11
  89. package/runtime/examples/sdk/08-prompt-templates.ts +1 -1
  90. package/runtime/export-html/template.js +3 -3
  91. package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  92. package/runtime/package.json +8 -14
  93. package/runtime/theme/dark.json +0 -1
  94. package/runtime/theme/light.json +0 -1
  95. package/runtime/theme/theme-schema.json +2 -2
  96. package/runtime/valni +2 -2
  97. package/runtime/README.md +0 -674
  98. package/runtime/docs/extensions.md +0 -2910
  99. package/runtime/docs/json.md +0 -82
  100. package/runtime/docs/models.md +0 -540
  101. package/runtime/docs/packages.md +0 -228
  102. package/runtime/docs/rpc.md +0 -1480
  103. package/runtime/docs/sdk.md +0 -1183
  104. package/runtime/docs/session-format.md +0 -422
  105. package/runtime/docs/settings.md +0 -315
  106. package/runtime/docs/shell-aliases.md +0 -13
  107. package/runtime/docs/terminal-setup.md +0 -142
  108. package/runtime/docs/termux.md +0 -127
  109. package/runtime/docs/tmux.md +0 -63
  110. package/runtime/docs/tui.md +0 -927
  111. package/runtime/docs/windows.md +0 -17
  112. package/runtime/examples/README.md +0 -25
  113. package/runtime/examples/extensions/README.md +0 -212
  114. package/runtime/examples/extensions/doom-overlay/README.md +0 -46
  115. package/runtime/examples/extensions/dynamic-resources/SKILL.md +0 -8
  116. package/runtime/examples/extensions/dynamic-resources/dynamic.md +0 -5
  117. package/runtime/examples/extensions/gondolin/index.ts +0 -531
  118. package/runtime/examples/extensions/gondolin/package-lock.json +0 -185
  119. package/runtime/examples/extensions/gondolin/package.json +0 -19
  120. package/runtime/examples/extensions/plan-mode/README.md +0 -66
  121. package/runtime/examples/extensions/subagent/README.md +0 -175
  122. package/runtime/examples/extensions/subagent/agents/planner.md +0 -37
  123. package/runtime/examples/extensions/subagent/agents/reviewer.md +0 -35
  124. package/runtime/examples/extensions/subagent/agents/scout.md +0 -50
  125. package/runtime/examples/extensions/subagent/agents/worker.md +0 -24
  126. package/runtime/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  127. package/runtime/examples/extensions/subagent/prompts/implement.md +0 -10
  128. package/runtime/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
  129. package/runtime/examples/sdk/README.md +0 -140
  130. package/runtime/node_modules/@mariozechner/clipboard/.yarnrc.yml +0 -1
  131. package/runtime/node_modules/@mariozechner/clipboard/Cargo.toml +0 -45
  132. package/runtime/node_modules/@mariozechner/clipboard/README.md +0 -60
  133. package/runtime/node_modules/@mariozechner/clipboard/build.rs +0 -5
  134. package/runtime/node_modules/@mariozechner/clipboard/clipboard.darwin-x64.node +0 -0
  135. package/runtime/node_modules/@mariozechner/clipboard/exp.ts +0 -12
  136. package/runtime/node_modules/@mariozechner/clipboard/index.js +0 -606
  137. package/runtime/node_modules/@mariozechner/clipboard/package.json +0 -56
  138. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/common.rs +0 -307
  139. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/mod.rs +0 -93
  140. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/ios.rs +0 -313
  141. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/macos.rs +0 -471
  142. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/mod.rs +0 -252
  143. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/wayland.rs +0 -431
  144. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/win.rs +0 -712
  145. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/x11.rs +0 -916
  146. package/runtime/node_modules/@mariozechner/clipboard/src/lib.rs +0 -194
  147. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/README.md +0 -3
  148. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/clipboard.darwin-x64.node +0 -0
  149. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/package.json +0 -22
@@ -1,712 +0,0 @@
1
- use std::collections::HashMap;
2
- use std::io::Cursor;
3
- use std::sync::mpsc::{Receiver, Sender};
4
- use std::time::Duration;
5
- use std::{mem, ptr, thread};
6
-
7
- use crate::clipboard_rs::common::{ContentData, Result};
8
- #[cfg(feature = "image")]
9
- use crate::clipboard_rs::common::{RustImage, RustImageData};
10
- use crate::clipboard_rs::{
11
- Clipboard, ClipboardContent, ClipboardHandler, ClipboardWatcher, ContentFormat,
12
- };
13
- use clipboard_win::raw::{set_file_list_with, set_string_with, set_without_clear};
14
- use clipboard_win::types::c_uint;
15
- use clipboard_win::{
16
- formats, get, get_clipboard, options, raw, set_clipboard, Clipboard as ClipboardWin, Monitor,
17
- SysResult,
18
- };
19
- #[cfg(feature = "image")]
20
- use image::codecs::bmp::BmpDecoder;
21
- #[cfg(feature = "image")]
22
- use image::DynamicImage;
23
- use windows::Win32::Foundation::{HANDLE, HWND};
24
- use windows::Win32::Graphics::Gdi::{
25
- CreateDIBitmap, DeleteObject, GetDC, ReleaseDC, BITMAPFILEHEADER, BITMAPINFO, BITMAPINFOHEADER,
26
- BITMAPV5HEADER, CBM_INIT, DIB_RGB_COLORS, HDC, HGDIOBJ,
27
- };
28
- use windows::Win32::System::DataExchange::SetClipboardData;
29
-
30
- pub struct WatcherShutdown {
31
- stop_signal: Sender<()>,
32
- }
33
-
34
- static UNKNOWN_FORMAT: &str = "unknown format";
35
- static CF_RTF: &str = "Rich Text Format";
36
- static CF_HTML: &str = "HTML Format";
37
- static CF_PNG: &str = "PNG";
38
-
39
- pub struct ClipboardContext {
40
- format_map: HashMap<&'static str, c_uint>,
41
- html_format: formats::Html,
42
- }
43
-
44
- pub struct ClipboardWatcherContext<T: ClipboardHandler> {
45
- handlers: Vec<T>,
46
- stop_signal: Sender<()>,
47
- stop_receiver: Receiver<()>,
48
- running: bool,
49
- }
50
-
51
- unsafe impl Send for ClipboardContext {}
52
- unsafe impl Sync for ClipboardContext {}
53
- unsafe impl<T: ClipboardHandler> Send for ClipboardWatcherContext<T> {}
54
- unsafe impl<T: ClipboardHandler> Sync for ClipboardWatcherContext<T> {}
55
-
56
- impl ClipboardContext {
57
- pub fn new() -> Result<ClipboardContext> {
58
- let (format_map, html_format) = {
59
- let cf_html_format = formats::Html::new();
60
- let cf_rtf_uint = clipboard_win::register_format(CF_RTF);
61
- let cf_png_uint = clipboard_win::register_format(CF_PNG);
62
- let mut m: HashMap<&str, c_uint> = HashMap::new();
63
- if let Some(cf_html) = cf_html_format {
64
- m.insert(CF_HTML, cf_html.code());
65
- }
66
- if let Some(cf_rtf) = cf_rtf_uint {
67
- m.insert(CF_RTF, cf_rtf.get());
68
- }
69
- if let Some(cf_png) = cf_png_uint {
70
- m.insert(CF_PNG, cf_png.get());
71
- }
72
- (m, cf_html_format)
73
- };
74
- Ok(ClipboardContext {
75
- format_map,
76
- html_format: html_format.ok_or("register html format error")?,
77
- })
78
- }
79
-
80
- fn get_format(&self, format: &ContentFormat) -> c_uint {
81
- match format {
82
- ContentFormat::Text => formats::CF_UNICODETEXT,
83
- ContentFormat::Rtf => *self.format_map.get(CF_RTF).unwrap(),
84
- ContentFormat::Html => *self.format_map.get(CF_HTML).unwrap(),
85
- #[cfg(feature = "image")]
86
- ContentFormat::Image => formats::CF_DIB,
87
- ContentFormat::Files => formats::CF_HDROP,
88
- ContentFormat::Other(format) => clipboard_win::register_format(format).unwrap().get(),
89
- }
90
- }
91
- }
92
-
93
- impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
94
- pub fn new() -> Result<Self> {
95
- let (tx, rx) = std::sync::mpsc::channel();
96
- Ok(Self {
97
- handlers: Vec::new(),
98
- stop_signal: tx,
99
- stop_receiver: rx,
100
- running: false,
101
- })
102
- }
103
- }
104
-
105
- impl Clipboard for ClipboardContext {
106
- fn available_formats(&self) -> Result<Vec<String>> {
107
- let _clip =
108
- ClipboardWin::new_attempts(10).map_err(|code| format!("Open clipboard error, code = {code}"));
109
- let format_count = clipboard_win::count_formats();
110
- if format_count.is_none() {
111
- return Ok(Vec::new());
112
- }
113
- let mut res = Vec::new();
114
- let enum_formats = clipboard_win::raw::EnumFormats::new();
115
- enum_formats.into_iter().for_each(|format| {
116
- let f_name = raw::format_name_big(format);
117
- match f_name {
118
- Some(name) => res.push(name),
119
- None => {
120
- res.push(UNKNOWN_FORMAT.to_string());
121
- }
122
- }
123
- });
124
- Ok(res)
125
- }
126
-
127
- fn has(&self, format: ContentFormat) -> bool {
128
- match format {
129
- ContentFormat::Text => clipboard_win::is_format_avail(formats::CF_UNICODETEXT),
130
- ContentFormat::Rtf => {
131
- let cf_rtf_uint = self.format_map.get(CF_RTF).unwrap();
132
- clipboard_win::is_format_avail(*cf_rtf_uint)
133
- }
134
- ContentFormat::Html => {
135
- let cf_html_uint = self.format_map.get(CF_HTML).unwrap();
136
- clipboard_win::is_format_avail(*cf_html_uint)
137
- }
138
- #[cfg(feature = "image")]
139
- ContentFormat::Image => {
140
- // Currently only judge whether there is a png format
141
- let cf_png_uint = self.format_map.get(CF_PNG).unwrap();
142
- clipboard_win::is_format_avail(*cf_png_uint)
143
- || clipboard_win::is_format_avail(formats::CF_DIB)
144
- }
145
- ContentFormat::Files => clipboard_win::is_format_avail(formats::CF_HDROP),
146
- ContentFormat::Other(format) => {
147
- let format_uint = clipboard_win::register_format(format.as_str());
148
- if let Some(format_uint) = format_uint {
149
- return clipboard_win::is_format_avail(format_uint.get());
150
- }
151
- false
152
- }
153
- }
154
- }
155
-
156
- fn clear(&self) -> Result<()> {
157
- let _clip =
158
- ClipboardWin::new_attempts(10).map_err(|code| format!("Open clipboard error, code = {code}"));
159
- let res = clipboard_win::empty();
160
- if let Err(e) = res {
161
- return Err(format!("Empty clipboard error, code = {e}").into());
162
- }
163
- Ok(())
164
- }
165
-
166
- fn get_buffer(&self, format: &str) -> Result<Vec<u8>> {
167
- let format_uint = clipboard_win::register_format(format);
168
- if format_uint.is_none() {
169
- return Err("register format error".into());
170
- }
171
- let format_uint = format_uint.unwrap().get();
172
- let buffer = get_clipboard(formats::RawData(format_uint));
173
- match buffer {
174
- Ok(data) => Ok(data),
175
- Err(e) => Err(format!("Get buffer error, code = {e}").into()),
176
- }
177
- }
178
-
179
- fn get_text(&self) -> Result<String> {
180
- let string: SysResult<String> = get_clipboard(formats::Unicode);
181
- match string {
182
- Ok(s) => Ok(s),
183
- Err(e) => Err(format!("Get text error, code = {e}").into()),
184
- }
185
- }
186
-
187
- fn get_rich_text(&self) -> Result<String> {
188
- let rtf_raw_data = self.get_buffer(CF_RTF)?;
189
- Ok(String::from_utf8_lossy(&rtf_raw_data).to_string())
190
- }
191
-
192
- fn get_html(&self) -> Result<String> {
193
- let buffer = get_clipboard(formats::RawData(self.html_format.code()));
194
- match buffer {
195
- Ok(data) => {
196
- let html_res = String::from_utf8(data);
197
- if let Ok(html_full_str) = html_res {
198
- let html = extract_html_from_clipboard_data(html_full_str.as_str());
199
- if let Ok(html) = html {
200
- return Ok(html);
201
- }
202
- }
203
- Err("Get html error".into())
204
- }
205
- Err(e) => Err(format!("Get buffer error, code = {e}").into()),
206
- }
207
- }
208
-
209
- #[cfg(feature = "image")]
210
- fn get_image(&self) -> Result<RustImageData> {
211
- let cf_png_format = self.format_map.get(CF_PNG);
212
- if cf_png_format.is_some() && clipboard_win::is_format_avail(*cf_png_format.unwrap()) {
213
- let image_raw_data = self.get_buffer(CF_PNG)?;
214
- RustImageData::from_bytes(&image_raw_data)
215
- } else if clipboard_win::is_format_avail(formats::CF_DIBV5) {
216
- let res = get_clipboard(formats::RawData(formats::CF_DIBV5));
217
- match res {
218
- Ok(data) => {
219
- let decoder = {
220
- // if data.as_slice().starts_with(b"BM") {
221
- // BmpDecoder::new(Cursor::new(data.as_slice()))
222
- // } else {
223
- BmpDecoder::new_without_file_header(Cursor::new(data.as_slice()))
224
- // }
225
- };
226
- let decoder = decoder.map_err(|e| format!("{e}"))?;
227
- let dynamic_image = DynamicImage::from_decoder(decoder).map_err(|e| format!("{e}"))?;
228
- Ok(RustImageData::from_dynamic_image(dynamic_image))
229
- }
230
- Err(e) => Err(format!("Get image error, code = {e}").into()),
231
- }
232
- } else if clipboard_win::is_format_avail(formats::CF_DIB) {
233
- let res = get_clipboard(formats::Bitmap);
234
- match res {
235
- Ok(data) => RustImageData::from_bytes(&data),
236
- Err(e) => Err(format!("Get image error, code = {e}").into()),
237
- }
238
- } else {
239
- Err("No image data in clipboard".into())
240
- }
241
- }
242
-
243
- fn get_files(&self) -> Result<Vec<String>> {
244
- let files: SysResult<Vec<String>> = get_clipboard(formats::FileList);
245
- match files {
246
- Ok(f) => Ok(f),
247
- Err(e) => Err(format!("Get files error, code = {e}").into()),
248
- }
249
- }
250
-
251
- fn get(&self, formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>> {
252
- let _clip =
253
- ClipboardWin::new_attempts(10).map_err(|code| format!("Open clipboard error, code = {code}"));
254
- let mut res = Vec::new();
255
- for format in formats {
256
- match format {
257
- ContentFormat::Text => {
258
- let r = get(formats::Unicode);
259
- match r {
260
- Ok(txt) => {
261
- res.push(ClipboardContent::Text(txt));
262
- }
263
- Err(_) => continue,
264
- }
265
- }
266
- ContentFormat::Rtf => {
267
- let format_uint = self.get_format(format);
268
- let buffer = get(formats::RawData(format_uint));
269
- match buffer {
270
- Ok(buffer) => {
271
- let rtf = String::from_utf8_lossy(&buffer);
272
- res.push(ClipboardContent::Rtf(rtf.to_string()));
273
- }
274
- Err(_) => continue,
275
- }
276
- }
277
- ContentFormat::Html => {
278
- let html_buffer = get(formats::RawData(self.html_format.code()));
279
- match html_buffer {
280
- Ok(html) => {
281
- let html_res = String::from_utf8(html);
282
- if let Ok(html_full_str) = html_res {
283
- let html = extract_html_from_clipboard_data(html_full_str.as_str());
284
- if let Ok(html) = html {
285
- res.push(ClipboardContent::Html(html));
286
- }
287
- }
288
- }
289
- Err(_) => continue,
290
- }
291
- }
292
- #[cfg(feature = "image")]
293
- ContentFormat::Image => {
294
- let img = self.get_image();
295
- match img {
296
- Ok(img) => {
297
- res.push(ClipboardContent::Image(img));
298
- }
299
- Err(_) => continue,
300
- }
301
- }
302
- ContentFormat::Other(fmt) => {
303
- let format_uint = self.get_format(format);
304
- let buffer = get(formats::RawData(format_uint));
305
- match buffer {
306
- Ok(buffer) => {
307
- res.push(ClipboardContent::Other(fmt.clone(), buffer));
308
- }
309
- Err(_) => continue,
310
- }
311
- }
312
- ContentFormat::Files => {
313
- let files = self.get_files();
314
- match files {
315
- Ok(files) => {
316
- res.push(ClipboardContent::Files(files));
317
- }
318
- Err(_) => continue,
319
- }
320
- }
321
- }
322
- }
323
- Ok(res)
324
- }
325
-
326
- fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()> {
327
- let format_uint = clipboard_win::register_format(format);
328
- if format_uint.is_none() {
329
- return Err("register format error".into());
330
- }
331
- let format_uint = format_uint.unwrap().get();
332
- let res = set_clipboard(formats::RawData(format_uint), buffer);
333
- if res.is_err() {
334
- return Err("set buffer error".into());
335
- }
336
- Ok(())
337
- }
338
-
339
- fn set_text(&self, text: String) -> Result<()> {
340
- let res = set_clipboard(formats::Unicode, text);
341
- res.map_err(|e| format!("set text error, code = {e}").into())
342
- }
343
-
344
- fn set_rich_text(&self, text: String) -> Result<()> {
345
- let res = self.set_buffer(CF_RTF, text.as_bytes().to_vec());
346
- res.map_err(|e| format!("set rich text error, code = {e}").into())
347
- }
348
-
349
- fn set_html(&self, html: String) -> Result<()> {
350
- let cf_html = plain_html_to_cf_html(&html);
351
- let res = set_clipboard(
352
- formats::RawData(self.html_format.code()),
353
- cf_html.as_bytes(),
354
- );
355
- res.map_err(|e| format!("set html error, code = {e}").into())
356
- }
357
-
358
- #[cfg(feature = "image")]
359
- fn set_image(&self, image: RustImageData) -> Result<()> {
360
- let _clip =
361
- ClipboardWin::new_attempts(10).map_err(|code| format!("Open clipboard error, code = {code}"));
362
- let res = clipboard_win::empty();
363
- if let Err(e) = res {
364
- return Err(format!("Empty clipboard error, code = {e}").into());
365
- }
366
- // chromium source code
367
- // @link {https://source.chromium.org/chromium/chromium/src/+/main:ui/base/clipboard/clipboard_win.cc;l=771;drc=2a5aaed0ff3a0895c8551495c2656ed49baf742c;bpv=0;bpt=1}
368
- let cf_png_format = self.format_map.get(CF_PNG);
369
- if let Some(cf_png) = cf_png_format {
370
- let png = image.to_png()?;
371
- if let Err(e) = set_without_clear(*cf_png, png.get_bytes()) {
372
- eprintln!("set png image error, code = {e}");
373
- // continue set bmp image
374
- }
375
- }
376
- // 转换为 BMP 并设置到剪贴板
377
- let bmp = image
378
- .to_bitmap()
379
- .map_err(|e| format!("transform to bitmap error, code = {e}"))?;
380
-
381
- set_bitmap_inner(bmp.get_bytes()).map_err(|e| format!("set image error, code = {e}").into())
382
- }
383
-
384
- fn set_files(&self, files: Vec<String>) -> Result<()> {
385
- let _clip =
386
- ClipboardWin::new_attempts(10).map_err(|code| format!("Open clipboard error, code = {code}"));
387
- let res = set_file_list_with(&files, options::DoClear);
388
- res.map_err(|e| format!("set files error, code = {e}").into())
389
- }
390
-
391
- fn set(&self, contents: Vec<ClipboardContent>) -> Result<()> {
392
- let _clip =
393
- ClipboardWin::new_attempts(10).map_err(|code| format!("Open clipboard error, code = {code}"));
394
- let res = clipboard_win::empty();
395
- if let Err(e) = res {
396
- return Err(format!("Empty clipboard error, code = {e}").into());
397
- }
398
- for content in contents {
399
- match content {
400
- ClipboardContent::Text(txt) => {
401
- let res = set_string_with(txt.as_str(), options::NoClear);
402
- if res.is_err() {
403
- continue;
404
- }
405
- }
406
- ClipboardContent::Html(html) => {
407
- let format_uint_html = self.html_format.code();
408
- let cf_html = plain_html_to_cf_html(&html);
409
- let res = set_without_clear(format_uint_html, cf_html.as_bytes());
410
- if res.is_err() {
411
- continue;
412
- }
413
- }
414
- #[cfg(feature = "image")]
415
- ClipboardContent::Image(img) => {
416
- // set image will clear clipboard
417
- let res = self.set_image(img);
418
- if res.is_err() {
419
- continue;
420
- }
421
- }
422
- ClipboardContent::Rtf(_) | ClipboardContent::Other(_, _) => {
423
- let format_uint = self.get_format(&content.get_format());
424
- let res = set_without_clear(format_uint, content.as_bytes());
425
- if res.is_err() {
426
- continue;
427
- }
428
- }
429
- ClipboardContent::Files(file_list) => {
430
- let res = set_file_list_with(&file_list, options::NoClear);
431
- if res.is_err() {
432
- continue;
433
- }
434
- }
435
- }
436
- }
437
- Ok(())
438
- }
439
- }
440
-
441
- impl<T: ClipboardHandler> ClipboardWatcher<T> for ClipboardWatcherContext<T> {
442
- fn add_handler(&mut self, f: T) -> &mut Self {
443
- self.handlers.push(f);
444
- self
445
- }
446
-
447
- fn start_watch(&mut self) {
448
- if self.running {
449
- println!("already start watch!");
450
- return;
451
- }
452
- if self.handlers.is_empty() {
453
- println!("no handler, no need to start watch!");
454
- return;
455
- }
456
- self.running = true;
457
- let mut monitor = Monitor::new().expect("create monitor error");
458
- let shutdown = monitor.shutdown_channel();
459
- loop {
460
- if self.stop_receiver.try_recv().is_ok() {
461
- break;
462
- }
463
- let msg = monitor.try_recv();
464
- match msg {
465
- Ok(true) => {
466
- self.handlers.iter_mut().for_each(|f| {
467
- f.on_clipboard_change();
468
- });
469
- }
470
- Ok(false) => {
471
- // no change
472
- thread::park_timeout(Duration::from_millis(200));
473
- continue;
474
- }
475
- Err(e) => {
476
- eprintln!("watch error, code = {e}");
477
- break;
478
- }
479
- }
480
- }
481
- drop(shutdown);
482
- self.running = false;
483
- }
484
-
485
- fn get_shutdown_channel(&self) -> WatcherShutdown {
486
- WatcherShutdown {
487
- stop_signal: self.stop_signal.clone(),
488
- }
489
- }
490
- }
491
-
492
- impl Drop for WatcherShutdown {
493
- fn drop(&mut self) {
494
- let _ = self.stop_signal.send(());
495
- }
496
- }
497
-
498
- // 将输入的 UTF-8 字符串转换为宽字符(UTF-16)字符串
499
- // fn utf8_to_utf16(input: &str) -> Vec<u16> {
500
- // let mut vec: Vec<u16> = input.encode_utf16().collect();
501
- // vec.push(0);
502
- // vec
503
- // }
504
-
505
- // https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
506
- // The description header includes the clipboard version number and offsets, indicating where the context and the fragment start and end. The description is a list of ASCII text keywords followed by a string and separated by a colon (:).
507
- // Version: vv version number of the clipboard. Starting version is . As of Windows 10 20H2 this is now .Version:0.9Version:1.0
508
- // StartHTML: Offset (in bytes) from the beginning of the clipboard to the start of the context, or if no context.-1
509
- // EndHTML: Offset (in bytes) from the beginning of the clipboard to the end of the context, or if no context.-1
510
- // StartFragment: Offset (in bytes) from the beginning of the clipboard to the start of the fragment.
511
- // EndFragment: Offset (in bytes) from the beginning of the clipboard to the end of the fragment.
512
- // StartSelection: Optional. Offset (in bytes) from the beginning of the clipboard to the start of the selection.
513
- // EndSelection: Optional. Offset (in bytes) from the beginning of the clipboard to the end of the selection.
514
- // The and keywords are optional and must both be omitted if you do not want the application to generate this information.StartSelectionEndSelection
515
- // Future revisions of the clipboard format may extend the header, for example, since the HTML starts at the offset then multiple and pairs could be added later to support noncontiguous selection of fragments.CF_HTMLStartHTMLStartFragmentEndFragment
516
- // example:
517
- // html=Version:1.0
518
- // StartHTML:000000096
519
- // EndHTML:000000375
520
- // StartFragment:000000096
521
- // EndFragment:000000375
522
- // <html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"></head><body><div style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><pre><span style="color:#9876aa;">sellChannel</span></pre></div></body></html>
523
- // cp from https://github.com/Devolutions/IronRDP/blob/37aa6426dba3272f38a2bb46a513144a326854ee/crates/ironrdp-cliprdr-format/src/html.rs#L91
524
- fn plain_html_to_cf_html(fragment: &str) -> String {
525
- const POS_PLACEHOLDER: &str = "0000000000";
526
-
527
- let mut buffer = String::new();
528
-
529
- let mut write_header = |key: &str, value: &str| {
530
- let size = key.len() + value.len() + ":\r\n".len();
531
- buffer.reserve(size);
532
-
533
- buffer.push_str(key);
534
- buffer.push(':');
535
- let value_pos = buffer.len();
536
- buffer.push_str(value);
537
- buffer.push_str("\r\n");
538
-
539
- value_pos
540
- };
541
-
542
- write_header("Version", "0.9");
543
-
544
- let start_html_header_value_pos = write_header("StartHTML", POS_PLACEHOLDER);
545
- let end_html_header_value_pos = write_header("EndHTML", POS_PLACEHOLDER);
546
- let start_fragment_header_value_pos = write_header("StartFragment", POS_PLACEHOLDER);
547
- let end_fragment_header_value_pos = write_header("EndFragment", POS_PLACEHOLDER);
548
-
549
- let start_html_pos = buffer.len();
550
- if !fragment.starts_with("<html>") {
551
- buffer.push_str("<html>\r\n<body>\r\n<!--StartFragment-->");
552
- }
553
-
554
- let start_fragment_pos = buffer.len();
555
- buffer.push_str(fragment);
556
-
557
- let end_fragment_pos = buffer.len();
558
- if !fragment.ends_with("</html>") {
559
- buffer.push_str("<!--EndFragment-->\r\n</body>\r\n</html>");
560
- }
561
-
562
- let end_html_pos = buffer.len();
563
-
564
- let start_html_pos_value = format!("{start_html_pos:0>10}");
565
- let end_html_pos_value = format!("{end_html_pos:0>10}");
566
- let start_fragment_pos_value = format!("{start_fragment_pos:0>10}");
567
- let end_fragment_pos_value = format!("{end_fragment_pos:0>10}");
568
-
569
- let mut replace_placeholder = |value_begin_idx: usize, header_value: &str| {
570
- let value_end_idx = value_begin_idx + POS_PLACEHOLDER.len();
571
- buffer.replace_range(value_begin_idx..value_end_idx, header_value);
572
- };
573
-
574
- replace_placeholder(start_html_header_value_pos, &start_html_pos_value);
575
- replace_placeholder(end_html_header_value_pos, &end_html_pos_value);
576
- replace_placeholder(start_fragment_header_value_pos, &start_fragment_pos_value);
577
- replace_placeholder(end_fragment_header_value_pos, &end_fragment_pos_value);
578
-
579
- buffer
580
- }
581
-
582
- const SEP: char = ':';
583
- const START_HTML: &str = "StartHTML";
584
- const END_HTML: &str = "EndHTML";
585
-
586
- fn extract_html_from_clipboard_data(data: &str) -> Result<String> {
587
- let mut start_idx = 0usize;
588
- let mut end_idx = data.len();
589
- for line in data.lines() {
590
- let mut split = line.split(SEP);
591
- let key = match split.next() {
592
- Some(key) => key,
593
- None => break,
594
- };
595
- let value = match split.next() {
596
- Some(value) => value,
597
- //Reached HTML
598
- None => break,
599
- };
600
- match key {
601
- START_HTML => match value.trim_start_matches('0').parse() {
602
- Ok(value) => {
603
- start_idx = value;
604
- continue;
605
- }
606
- //Should not really happen
607
- Err(_) => break,
608
- },
609
- END_HTML => match value.trim_start_matches('0').parse() {
610
- Ok(value) => {
611
- end_idx = value;
612
- continue;
613
- }
614
- //Should not really happen
615
- Err(_) => break,
616
- },
617
- _ => continue,
618
- }
619
- }
620
- //Make sure HTML writer didn't screw up offsets of fragment
621
- // Check that start_idx is within bounds
622
- if start_idx > data.len() {
623
- return Err("Invalid HTML offsets: start index exceeds data length".into());
624
- }
625
- // Check that end_idx is within bounds
626
- if end_idx > data.len() {
627
- return Err("Invalid HTML offsets: end index exceeds data length".into());
628
- }
629
- // Check that end_idx >= start_idx
630
- if end_idx < start_idx {
631
- return Err("Invalid HTML offsets: end index before start index".into());
632
- }
633
- Ok(data[start_idx..end_idx].to_string())
634
- }
635
-
636
- fn set_bitmap_inner(data: &[u8]) -> Result<()> {
637
- const FILE_HEADER_LEN: usize = mem::size_of::<BITMAPFILEHEADER>();
638
- const INFO_HEADER_LEN: usize = mem::size_of::<BITMAPV5HEADER>();
639
-
640
- if data.len() <= (FILE_HEADER_LEN + INFO_HEADER_LEN) {
641
- return Err("Invalid bitmap data".into());
642
- }
643
-
644
- let mut file_header = mem::MaybeUninit::<BITMAPFILEHEADER>::uninit();
645
- let mut info_header = mem::MaybeUninit::<BITMAPV5HEADER>::uninit();
646
-
647
- let (file_header, info_header) = unsafe {
648
- ptr::copy_nonoverlapping(
649
- data.as_ptr(),
650
- file_header.as_mut_ptr() as _,
651
- FILE_HEADER_LEN,
652
- );
653
- ptr::copy_nonoverlapping(
654
- data.as_ptr().add(FILE_HEADER_LEN),
655
- info_header.as_mut_ptr() as _,
656
- INFO_HEADER_LEN,
657
- );
658
- (file_header.assume_init(), info_header.assume_init())
659
- };
660
-
661
- if data.len() <= file_header.bfOffBits as usize {
662
- return Err("Invalid bitmap data".into());
663
- }
664
-
665
- let bitmap = &data[file_header.bfOffBits as _..];
666
-
667
- if bitmap.len() < info_header.bV5SizeImage as usize {
668
- return Err("Invalid bitmap data".into());
669
- }
670
-
671
- let dc = DeviceContext::new()?;
672
-
673
- let handle = unsafe {
674
- CreateDIBitmap(
675
- dc.0,
676
- Some(&info_header as *const _ as *const BITMAPINFOHEADER),
677
- CBM_INIT as u32,
678
- Some(bitmap.as_ptr() as _),
679
- Some(&info_header as *const _ as *const BITMAPINFO),
680
- DIB_RGB_COLORS,
681
- )
682
- };
683
-
684
- if handle.is_invalid() {
685
- return Err("Failed to create DIB".into());
686
- }
687
-
688
- if let Err(err) = unsafe { SetClipboardData(formats::CF_BITMAP, Some(HANDLE(handle.0))) } {
689
- let _ = unsafe { DeleteObject(HGDIOBJ(handle.0)) };
690
- Err(err.into())
691
- } else {
692
- Ok(())
693
- }
694
- }
695
-
696
- struct DeviceContext(HDC);
697
-
698
- impl DeviceContext {
699
- fn new() -> Result<Self> {
700
- let dc = unsafe { GetDC(Some(HWND::default())) };
701
- if dc.is_invalid() {
702
- return Err("Failed to get DC".into());
703
- }
704
- Ok(Self(dc))
705
- }
706
- }
707
-
708
- impl Drop for DeviceContext {
709
- fn drop(&mut self) {
710
- unsafe { ReleaseDC(Some(HWND::default()), self.0) };
711
- }
712
- }