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