1use super::{
6 debug_overlay::vga_debug_init,
7 panic_screen::init_panic_fb_globals,
8 status_line::draw_boot_status_line,
9 types::*,
10 writer::{
11 VgaWriter, DOUBLE_BUFFER_MODE, PRESENTED_FRAMES, VGA_PRESENT_PIXEL_COUNT,
12 VGA_PRESENT_REGION_COUNT,
13 },
14};
15use core::{
16 fmt,
17 sync::atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering},
18};
19use spin::Mutex;
20
21pub(crate) static VGA_AVAILABLE: AtomicBool = AtomicBool::new(false);
22pub(crate) static FPS_LAST_TICK: AtomicU64 = AtomicU64::new(0);
23pub(crate) static FPS_LAST_FRAME_COUNT: AtomicU64 = AtomicU64::new(0);
24static FPS_ESTIMATE: AtomicU64 = AtomicU64::new(0);
25const FPS_REFRESH_PERIOD_TICKS: u64 = 100; pub(crate) static UI_SCALE: AtomicU8 = AtomicU8::new(1);
27const CLIPBOARD_CAP: usize = 8192;
28static CLIPBOARD: Mutex<([u8; CLIPBOARD_CAP], usize)> = Mutex::new(([0u8; CLIPBOARD_CAP], 0));
29
30pub static VGA_WRITER: Mutex<VgaWriter> = Mutex::new(VgaWriter::new());
31
32#[inline]
34pub fn is_available() -> bool {
35 VGA_AVAILABLE.load(Ordering::Relaxed)
36}
37
38pub fn with_writer<R>(f: impl FnOnce(&mut VgaWriter) -> R) -> Option<R> {
42 if !is_available() {
43 return None;
44 }
45 let mut writer = VGA_WRITER.lock();
46 let res = f(&mut writer);
47 Some(res)
48}
49
50pub fn try_with_writer<R>(f: impl FnOnce(&mut VgaWriter) -> R) -> Option<R> {
54 if !is_available() {
55 return None;
56 }
57 let mut writer = VGA_WRITER.try_lock()?;
58 let res = f(&mut writer);
59 Some(res)
60}
61
62pub fn write_text(text: &str) {
64 if !is_available() {
65 crate::arch::x86_64::serial::_print(format_args!("{}", text));
66 return;
67 }
68 let _ = with_writer(|w| {
69 w.write_bytes(text);
70 });
71}
72
73pub fn write_char(ch: char) {
75 if !is_available() {
76 crate::arch::x86_64::serial::_print(format_args!("{}", ch));
77 return;
78 }
79 let mut buf = [0u8; 4];
80 write_text(ch.encode_utf8(&mut buf));
81}
82
83pub(crate) fn current_fps(tick: u64) -> u64 {
85 let last_tick = FPS_LAST_TICK.load(Ordering::Relaxed);
86 let frames = PRESENTED_FRAMES.load(Ordering::Relaxed);
87
88 if last_tick == 0 {
89 let _ = FPS_LAST_TICK.compare_exchange(0, tick, Ordering::Relaxed, Ordering::Relaxed);
90 let _ =
91 FPS_LAST_FRAME_COUNT.compare_exchange(0, frames, Ordering::Relaxed, Ordering::Relaxed);
92 return FPS_ESTIMATE.load(Ordering::Relaxed);
93 }
94
95 let dt = tick.saturating_sub(last_tick);
96 if dt >= FPS_REFRESH_PERIOD_TICKS
97 && FPS_LAST_TICK
98 .compare_exchange(last_tick, tick, Ordering::Relaxed, Ordering::Relaxed)
99 .is_ok()
100 {
101 let last_frames = FPS_LAST_FRAME_COUNT.swap(frames, Ordering::Relaxed);
102 let df = frames.saturating_sub(last_frames);
103 let fps = if dt == 0 {
104 0
105 } else {
106 df.saturating_mul(100) / dt
107 };
108 FPS_ESTIMATE.store(fps, Ordering::Relaxed);
109 }
110
111 FPS_ESTIMATE.load(Ordering::Relaxed)
112}
113
114pub(crate) fn current_ui_scale() -> UiScale {
116 match UI_SCALE.load(Ordering::Relaxed) {
117 1 => UiScale::Compact,
118 3 => UiScale::Large,
119 _ => UiScale::Normal,
120 }
121}
122
123pub fn ui_scale() -> UiScale {
125 current_ui_scale()
126}
127
128pub fn set_ui_scale(scale: UiScale) {
130 UI_SCALE.store(scale as u8, Ordering::Relaxed);
131}
132
133pub fn ui_scale_px(base: usize) -> usize {
135 let factor = current_ui_scale().factor();
136 let denom = UiScale::Normal.factor();
137 base.saturating_mul(factor) / denom
138}
139
140#[allow(clippy::too_many_arguments)]
142pub fn init(
143 fb_addr: u64,
144 fb_width: u32,
145 fb_height: u32,
146 pitch: u32,
147 bpp: u16,
148 red_size: u8,
149 red_shift: u8,
150 green_size: u8,
151 green_shift: u8,
152 blue_size: u8,
153 blue_shift: u8,
154) {
155 if fb_addr == 0 || fb_width == 0 || fb_height == 0 || pitch == 0 {
156 VGA_AVAILABLE.store(false, Ordering::Relaxed);
157 log::info!("Framebuffer console unavailable (no framebuffer)");
158 return;
159 }
160
161 if bpp != 24 && bpp != 32 {
162 VGA_AVAILABLE.store(false, Ordering::Relaxed);
163 log::info!("Framebuffer console unavailable (unsupported bpp={})", bpp);
164 return;
165 }
166
167 let fmt = PixelFormat {
168 bpp,
169 red_size,
170 red_shift,
171 green_size,
172 green_shift,
173 blue_size,
174 blue_shift,
175 };
176
177 let hhdm = crate::memory::hhdm_offset();
184 let fb_virt = if hhdm != 0 && fb_addr < hhdm {
185 crate::memory::phys_to_virt(fb_addr)
186 } else {
187 fb_addr
188 };
189
190 let mut writer = VGA_WRITER.lock();
191 if writer.configure(
192 fb_virt as *mut u8,
193 fb_width as usize,
194 fb_height as usize,
195 pitch as usize,
196 fmt,
197 ) {
198 writer.set_color(Color::LightCyan, Color::Black);
199 writer.clear_with(RgbColor::new(0x12, 0x16, 0x1E));
200 let deco_w = (writer.width() / 3).clamp(120, 300);
202 let deco_h = (writer.height() / 4).clamp(90, 220);
203 let deco_x = writer.width().saturating_sub(deco_w + 24);
204 let deco_y = 24;
205 writer.draw_strata_stack(deco_x, deco_y, deco_w, deco_h);
206 writer.set_rgb_color(
207 RgbColor::new(0xA7, 0xD8, 0xD8),
208 RgbColor::new(0x12, 0x16, 0x1E),
209 );
210 writer.write_bytes("Strat9-OS v0.1.0\n");
211 writer.set_rgb_color(
212 RgbColor::new(0xE2, 0xE8, 0xF0),
213 RgbColor::new(0x12, 0x16, 0x1E),
214 );
215 VGA_AVAILABLE.store(true, Ordering::Relaxed);
216 init_panic_fb_globals(
219 fb_virt,
220 fb_width as usize,
221 fb_height as usize,
222 pitch as usize,
223 bpp,
224 red_shift,
225 green_shift,
226 blue_shift,
227 );
228 vga_debug_init(fb_width as usize, fb_height as usize);
230 log::info!(
231 "Framebuffer console enabled: {}x{} {}bpp pitch={}",
232 fb_width,
233 fb_height,
234 bpp,
235 pitch
236 );
237 drop(writer);
238 draw_boot_status_line(UiTheme::OCEAN_STATUS);
239 } else {
240 writer.enabled = false;
241 VGA_AVAILABLE.store(false, Ordering::Relaxed);
242 log::info!("Framebuffer console unavailable (font parse/init failed)");
243 }
244}
245
246#[doc(hidden)]
250pub fn _print(args: fmt::Arguments) {
251 use core::fmt::Write;
252 if is_available() {
253 let _ = with_writer(|w| {
254 w.write_fmt(args).ok();
255 });
256 return;
257 }
258 crate::arch::x86_64::serial::_print(args);
259}
260
261pub fn width() -> usize {
263 if !is_available() {
264 return 0;
265 }
266 VGA_WRITER.lock().width()
267}
268
269pub fn height() -> usize {
271 if !is_available() {
272 return 0;
273 }
274 VGA_WRITER.lock().height()
275}
276
277pub fn screen_size() -> (usize, usize) {
279 (width(), height())
280}
281
282pub fn ui_layout_screen() -> UiDockLayout {
284 UiDockLayout::from_screen()
285}
286
287pub fn glyph_size() -> (usize, usize) {
289 if !is_available() {
290 return (0, 0);
291 }
292 VGA_WRITER.lock().glyph_size()
293}
294
295pub fn text_cols() -> usize {
297 if !is_available() {
298 return 0;
299 }
300 VGA_WRITER.lock().cols()
301}
302
303pub fn text_rows() -> usize {
305 if !is_available() {
306 return 0;
307 }
308 VGA_WRITER.lock().rows()
309}
310
311pub fn get_text_cursor() -> (usize, usize) {
313 if !is_available() {
314 return (0, 0);
315 }
316 let writer = VGA_WRITER.lock();
317 (writer.col, writer.row)
318}
319
320pub fn set_text_cursor(col: usize, row: usize) {
322 if !is_available() {
323 return;
324 }
325 VGA_WRITER.lock().set_cursor_cell(col, row);
326}
327
328pub fn double_buffer_mode() -> bool {
330 DOUBLE_BUFFER_MODE.load(Ordering::Relaxed)
331}
332
333pub fn set_double_buffer_mode(enabled: bool) {
335 DOUBLE_BUFFER_MODE.store(enabled, Ordering::Relaxed);
336}
337
338pub fn draw_text_cursor(color: RgbColor) {
340 if !is_available() {
341 return;
342 }
343 let mut writer = VGA_WRITER.lock();
344 writer.draw_text_cursor_overlay(color);
345}
346
347pub fn hide_text_cursor() {
349 if !is_available() {
350 return;
351 }
352 let mut writer = VGA_WRITER.lock();
353 writer.hide_text_cursor();
354}
355
356pub fn framebuffer_info() -> FramebufferInfo {
358 if !is_available() {
359 return FramebufferInfo {
360 available: false,
361 width: 0,
362 height: 0,
363 pitch: 0,
364 bpp: 0,
365 red_size: 0,
366 red_shift: 0,
367 green_size: 0,
368 green_shift: 0,
369 blue_size: 0,
370 blue_shift: 0,
371 text_cols: 0,
372 text_rows: 0,
373 glyph_w: 0,
374 glyph_h: 0,
375 double_buffer_mode: false,
376 double_buffer_enabled: false,
377 ui_scale: UiScale::Normal,
378 };
379 }
380 VGA_WRITER.lock().framebuffer_info()
381}
382
383pub fn render_stats() -> RenderStats {
385 RenderStats {
386 presented_frames: PRESENTED_FRAMES.load(Ordering::Relaxed),
387 estimated_fps: FPS_ESTIMATE.load(Ordering::Relaxed),
388 present_region_count: VGA_PRESENT_REGION_COUNT.load(Ordering::Relaxed),
389 present_pixel_count: VGA_PRESENT_PIXEL_COUNT.load(Ordering::Relaxed),
390 }
391}
392
393pub fn set_text_color(fg: RgbColor, bg: RgbColor) {
395 if !is_available() {
396 return;
397 }
398 VGA_WRITER.lock().set_rgb_color(fg, bg);
399}
400
401pub fn set_clip_rect(x: usize, y: usize, width: usize, height: usize) {
403 if !is_available() {
404 return;
405 }
406 VGA_WRITER.lock().set_clip_rect(x, y, width, height);
407}
408
409pub fn reset_clip_rect() {
411 if !is_available() {
412 return;
413 }
414 VGA_WRITER.lock().reset_clip_rect();
415}
416
417pub fn begin_frame() -> bool {
419 if !is_available() {
420 return false;
421 }
422 if !double_buffer_mode() {
423 return false;
424 }
425 VGA_WRITER.lock().enable_double_buffer()
426}
427
428pub fn end_frame() {
430 if !is_available() {
431 return;
432 }
433 let mut writer = VGA_WRITER.lock();
434 writer.disable_double_buffer(true);
437}
438
439pub fn present() {
441 if !is_available() {
442 return;
443 }
444 VGA_WRITER.lock().present();
445}
446
447pub fn draw_pixel(x: usize, y: usize, color: RgbColor) {
449 if !is_available() {
450 return;
451 }
452 VGA_WRITER.lock().draw_pixel(x, y, color);
453}
454
455pub fn draw_pixel_alpha(x: usize, y: usize, color: RgbColor, alpha: u8) {
457 if !is_available() {
458 return;
459 }
460 VGA_WRITER.lock().draw_pixel_alpha(x, y, color, alpha);
461}
462
463pub fn draw_line(x0: isize, y0: isize, x1: isize, y1: isize, color: RgbColor) {
465 if !is_available() {
466 return;
467 }
468 VGA_WRITER.lock().draw_line(x0, y0, x1, y1, color);
469}
470
471pub fn draw_rect(x: usize, y: usize, width: usize, height: usize, color: RgbColor) {
473 if !is_available() {
474 return;
475 }
476 VGA_WRITER.lock().draw_rect(x, y, width, height, color);
477}
478
479pub fn fill_rect(x: usize, y: usize, width: usize, height: usize, color: RgbColor) {
481 if !is_available() {
482 return;
483 }
484 VGA_WRITER.lock().fill_rect(x, y, width, height, color);
485}
486
487pub fn fill_rect_alpha(
489 x: usize,
490 y: usize,
491 width: usize,
492 height: usize,
493 color: RgbColor,
494 alpha: u8,
495) {
496 if !is_available() {
497 return;
498 }
499 VGA_WRITER
500 .lock()
501 .fill_rect_alpha(x, y, width, height, color, alpha);
502}
503
504pub fn blit_rgb(
506 dst_x: usize,
507 dst_y: usize,
508 src_width: usize,
509 src_height: usize,
510 pixels: &[RgbColor],
511) -> bool {
512 if !is_available() {
513 return false;
514 }
515 VGA_WRITER
516 .lock()
517 .blit_rgb(dst_x, dst_y, src_width, src_height, pixels)
518}
519
520pub fn blit_rgb24(
522 dst_x: usize,
523 dst_y: usize,
524 src_width: usize,
525 src_height: usize,
526 bytes: &[u8],
527) -> bool {
528 if !is_available() {
529 return false;
530 }
531 VGA_WRITER
532 .lock()
533 .blit_rgb24(dst_x, dst_y, src_width, src_height, bytes)
534}
535
536pub fn blit_rgba(
538 dst_x: usize,
539 dst_y: usize,
540 src_width: usize,
541 src_height: usize,
542 bytes: &[u8],
543 global_alpha: u8,
544) -> bool {
545 if !is_available() {
546 return false;
547 }
548 VGA_WRITER
549 .lock()
550 .blit_rgba(dst_x, dst_y, src_width, src_height, bytes, global_alpha)
551}
552
553pub fn blit_sprite_rgba(
555 dst_x: usize,
556 dst_y: usize,
557 sprite: SpriteRgba<'_>,
558 global_alpha: u8,
559) -> bool {
560 if !is_available() {
561 return false;
562 }
563 VGA_WRITER
564 .lock()
565 .blit_sprite_rgba(dst_x, dst_y, sprite, global_alpha)
566}
567
568pub fn draw_text_at(pixel_x: usize, pixel_y: usize, text: &str, fg: RgbColor, bg: RgbColor) {
570 if !is_available() {
571 return;
572 }
573 VGA_WRITER
574 .lock()
575 .draw_text_at(pixel_x, pixel_y, text, fg, bg);
576}
577
578pub fn draw_text(pixel_x: usize, pixel_y: usize, text: &str, opts: TextOptions) -> TextMetrics {
580 if !is_available() {
581 return TextMetrics {
582 width: 0,
583 height: 0,
584 lines: 0,
585 };
586 }
587 VGA_WRITER.lock().draw_text(pixel_x, pixel_y, text, opts)
588}
589
590pub fn measure_text(text: &str, max_width: Option<usize>, wrap: bool) -> TextMetrics {
592 if !is_available() {
593 return TextMetrics {
594 width: 0,
595 height: 0,
596 lines: 0,
597 };
598 }
599 VGA_WRITER.lock().measure_text(text, max_width, wrap)
600}
601
602pub fn ui_clear(theme: UiTheme) {
604 let _ = with_writer(|w| w.clear_with(theme.background));
605}
606
607pub fn ui_draw_panel(
609 x: usize,
610 y: usize,
611 width: usize,
612 height: usize,
613 title: &str,
614 body: &str,
615 theme: UiTheme,
616) {
617 let _ = with_writer(|w| {
618 if width < 8 || height < 8 {
619 return;
620 }
621 let (gw, gh) = w.glyph_size();
622 w.fill_rect(x, y, width, height, theme.panel_bg);
623 w.draw_rect(x, y, width, height, theme.panel_border);
624
625 let title_h = gh + 6;
626 w.fill_rect(
627 x.saturating_add(1),
628 y.saturating_add(1),
629 width.saturating_sub(2),
630 title_h,
631 theme.accent,
632 );
633 let title_opts = TextOptions {
634 fg: theme.text,
635 bg: theme.accent,
636 align: TextAlign::Left,
637 wrap: false,
638 max_width: Some(width.saturating_sub(10)),
639 };
640 w.draw_text(x.saturating_add(6), y.saturating_add(3), title, title_opts);
641
642 let body_opts = TextOptions {
643 fg: theme.text,
644 bg: theme.panel_bg,
645 align: TextAlign::Left,
646 wrap: true,
647 max_width: Some(width.saturating_sub(10)),
648 };
649 w.draw_text(
650 x.saturating_add(6),
651 y.saturating_add(title_h + 4),
652 body,
653 body_opts,
654 );
655
656 w.fill_rect(
658 x.saturating_add(1),
659 y.saturating_add(title_h + 1),
660 width.saturating_sub(2),
661 1,
662 theme.panel_border,
663 );
664 let _ = gw;
666 });
667}
668
669pub fn ui_draw_panel_widget(panel: &UiPanel<'_>) {
671 ui_draw_panel(
672 panel.rect.x,
673 panel.rect.y,
674 panel.rect.w,
675 panel.rect.h,
676 panel.title,
677 panel.body,
678 panel.theme,
679 );
680}
681
682pub fn ui_draw_label(label: &UiLabel<'_>) {
684 let _ = with_writer(|w| {
685 w.draw_text(
686 label.rect.x,
687 label.rect.y,
688 label.text,
689 TextOptions {
690 fg: label.fg,
691 bg: label.bg,
692 align: label.align,
693 wrap: false,
694 max_width: Some(label.rect.w),
695 },
696 );
697 });
698}
699
700pub fn ui_draw_progress_bar(bar: UiProgressBar) {
702 let _ = with_writer(|w| {
703 if bar.rect.w < 3 || bar.rect.h < 3 {
704 return;
705 }
706 let value = core::cmp::min(bar.value, 100) as usize;
707 w.fill_rect(bar.rect.x, bar.rect.y, bar.rect.w, bar.rect.h, bar.bg);
708 w.draw_rect(bar.rect.x, bar.rect.y, bar.rect.w, bar.rect.h, bar.border);
709 let inner_w = bar.rect.w.saturating_sub(2);
710 let fill_w = inner_w.saturating_mul(value) / 100;
711 if fill_w > 0 {
712 w.fill_rect(
713 bar.rect.x + 1,
714 bar.rect.y + 1,
715 fill_w,
716 bar.rect.h.saturating_sub(2),
717 bar.fg,
718 );
719 }
720 });
721}
722
723pub fn ui_draw_table(table: &UiTable) {
725 let _ = with_writer(|w| {
726 if table.rect.w < 8 || table.rect.h < 8 {
727 return;
728 }
729 let (_gw, gh) = w.glyph_size();
730 if gh == 0 {
731 return;
732 }
733
734 w.fill_rect(
735 table.rect.x,
736 table.rect.y,
737 table.rect.w,
738 table.rect.h,
739 table.theme.panel_bg,
740 );
741 w.draw_rect(
742 table.rect.x,
743 table.rect.y,
744 table.rect.w,
745 table.rect.h,
746 table.theme.panel_border,
747 );
748
749 let cols = core::cmp::max(1, table.headers.len());
750 let col_w = table.rect.w / cols;
751 let header_h = gh + 2;
752 w.fill_rect(
753 table.rect.x + 1,
754 table.rect.y + 1,
755 table.rect.w.saturating_sub(2),
756 header_h,
757 table.theme.accent,
758 );
759
760 for (i, h) in table.headers.iter().enumerate() {
761 let x = table.rect.x + i * col_w + 2;
762 w.draw_text(
763 x,
764 table.rect.y + 1,
765 h,
766 TextOptions {
767 fg: table.theme.text,
768 bg: table.theme.accent,
769 align: TextAlign::Left,
770 wrap: false,
771 max_width: Some(col_w.saturating_sub(4)),
772 },
773 );
774 }
775
776 let mut y = table.rect.y + header_h + 2;
777 for row in &table.rows {
778 if y + gh > table.rect.y + table.rect.h {
779 break;
780 }
781 for c in 0..cols {
782 if c >= row.len() {
783 continue;
784 }
785 let x = table.rect.x + c * col_w + 2;
786 w.draw_text(
787 x,
788 y,
789 &row[c],
790 TextOptions {
791 fg: table.theme.text,
792 bg: table.theme.panel_bg,
793 align: TextAlign::Left,
794 wrap: false,
795 max_width: Some(col_w.saturating_sub(4)),
796 },
797 );
798 }
799 y += gh;
800 }
801 });
802}
803
804pub fn draw_strata_stack(origin_x: usize, origin_y: usize, layer_w: usize, layer_h: usize) {
806 if !is_available() {
807 return;
808 }
809 VGA_WRITER
810 .lock()
811 .draw_strata_stack(origin_x, origin_y, layer_w, layer_h);
812}
813pub fn scroll_view_up(lines: usize) {
817 if !is_available() {
818 return;
819 }
820 VGA_WRITER.lock().scroll_view_up(lines);
821}
822
823pub fn scroll_view_down(lines: usize) {
825 if !is_available() {
826 return;
827 }
828 VGA_WRITER.lock().scroll_view_down(lines);
829}
830
831pub fn scroll_to_live() {
833 if !is_available() {
834 return;
835 }
836 let _ = try_with_writer(|w| {
837 w.scroll_to_live();
838 });
839}
840
841pub fn set_console_defer_present(defer: bool) {
845 if !is_available() {
846 return;
847 }
848 VGA_WRITER.lock().console_defer_present = defer;
849}
850
851pub fn flush_display() {
854 if !is_available() {
855 return;
856 }
857 VGA_WRITER.lock().flush_display();
858}
859
860pub fn scrollbar_click(px_x: usize, px_y: usize) {
863 if !is_available() {
864 return;
865 }
866 let _ = try_with_writer(|w| {
867 w.scrollbar_click(px_x, px_y);
868 });
869}
870
871pub fn scrollbar_drag_to(px_y: usize) {
873 if !is_available() {
874 return;
875 }
876 let _ = try_with_writer(|w| {
877 w.scrollbar_drag_to(px_y);
878 });
879}
880
881pub fn scrollbar_hit_test(px_x: usize, px_y: usize) -> bool {
883 if !is_available() {
884 return false;
885 }
886 try_with_writer(|w| w.scrollbar_hit_test(px_x, px_y)).unwrap_or(false)
887}
888
889pub fn update_mouse_cursor(x: i32, y: i32) {
891 if !is_available() {
892 return;
893 }
894 let _ = try_with_writer(|w| {
895 w.update_mouse_cursor(x, y);
896 });
897}
898
899pub fn hide_mouse_cursor() {
901 if !is_available() {
902 return;
903 }
904 let _ = try_with_writer(|w| {
905 w.hide_mouse_cursor();
906 });
907}
908
909pub fn start_selection(px: usize, py: usize) {
911 if !is_available() {
912 return;
913 }
914 let _ = try_with_writer(|w| {
915 w.start_selection(px, py);
916 });
917}
918
919pub fn update_selection(px: usize, py: usize) {
921 if !is_available() {
922 return;
923 }
924 let _ = try_with_writer(|w| {
925 w.update_selection(px, py);
926 });
927}
928
929pub fn end_selection() {
931 if !is_available() {
932 return;
933 }
934 let _ = try_with_writer(|w| {
935 w.end_selection();
936 });
937}
938
939pub fn clear_selection() {
941 if !is_available() {
942 return;
943 }
944 let _ = try_with_writer(|w| {
945 w.clear_selection();
946 });
947}
948
949pub fn get_clipboard_text(buf: &mut [u8]) -> usize {
951 if let Some(clip) = CLIPBOARD.try_lock() {
952 let n = clip.1.min(buf.len());
953 buf[..n].copy_from_slice(&clip.0[..n]);
954 n
955 } else {
956 0
957 }
958}