1#![no_std]
2use bytemuck::{Pod, Zeroable};
6
7pub mod terminal_state;
9pub use terminal_state::TerminalStateReader;
10
11pub mod zones;
13pub use zones::{CommandBlock, SemanticZone, ZoneTracker, ZoneType};
14
15pub const SHMEM_PATH: &str = "/scarab_shm_v1";
18
19pub const SHMEM_PATH_ENV: &str = "SCARAB_SHMEM_PATH";
22
23pub const IMAGE_SHMEM_PATH_ENV: &str = "SCARAB_IMAGE_SHMEM_PATH";
25pub const GRID_WIDTH: usize = 200;
26pub const GRID_HEIGHT: usize = 100;
27pub const BUFFER_SIZE: usize = GRID_WIDTH * GRID_HEIGHT;
28
29#[repr(C)]
30#[derive(Copy, Clone, Pod, Zeroable)]
31pub struct Cell {
32 pub char_codepoint: u32,
33 pub fg: u32, pub bg: u32, pub flags: u8, pub _padding: [u8; 3], }
38
39impl Default for Cell {
40 fn default() -> Self {
41 Self {
42 char_codepoint: b' ' as u32,
43 fg: 0xFFA8DF5A, bg: 0xFF0D1208, flags: 0,
46 _padding: [0; 3],
47 }
48 }
49}
50
51#[repr(C)]
53#[derive(Copy, Clone)]
54pub struct SharedState {
55 pub sequence_number: u64, pub dirty_flag: u8,
57 pub error_mode: u8, pub cursor_x: u16,
59 pub cursor_y: u16,
60 pub _padding2: [u8; 2], pub cells: [Cell; BUFFER_SIZE],
64}
65
66unsafe impl Pod for SharedState {}
68unsafe impl Zeroable for SharedState {}
69
70pub const MAX_IMAGES: usize = 64;
73
74pub const IMAGE_BUFFER_SIZE: usize = 16 * 1024 * 1024;
76
77pub const IMAGE_SHMEM_PATH: &str = "/scarab_img_shm_v1";
80
81#[repr(C)]
83#[derive(Copy, Clone)]
84pub struct SharedImagePlacement {
85 pub image_id: u64,
87 pub x: u16,
89 pub y: u16,
91 pub width_cells: u16,
93 pub height_cells: u16,
95 pub pixel_width: u32,
97 pub pixel_height: u32,
99 pub blob_offset: u32,
101 pub blob_size: u32,
103 pub format: u8,
105 pub flags: u8,
107 pub _padding: [u8; 6],
109}
110
111unsafe impl Pod for SharedImagePlacement {}
113unsafe impl Zeroable for SharedImagePlacement {}
114
115impl SharedImagePlacement {
116 pub const fn is_valid(&self) -> bool {
118 (self.flags & 0x01) != 0
119 }
120
121 pub fn set_valid(&mut self) {
123 self.flags |= 0x01;
124 }
125
126 pub fn set_invalid(&mut self) {
128 self.flags &= !0x01;
129 }
130}
131
132#[repr(C)]
134#[derive(Copy, Clone)]
135pub struct SharedImageBuffer {
136 pub sequence_number: u64,
138 pub count: u32,
140 pub next_blob_offset: u32,
142 pub placements: [SharedImagePlacement; MAX_IMAGES],
144 pub blob_data: [u8; IMAGE_BUFFER_SIZE],
146}
147
148unsafe impl Pod for SharedImageBuffer {}
150unsafe impl Zeroable for SharedImageBuffer {}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
154#[archive(check_bytes)]
155pub enum LogLevel {
156 Error,
157 Warn,
158 Info,
159 Debug,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
164#[archive(check_bytes)]
165pub enum NotifyLevel {
166 Error,
167 Warning,
168 Info,
169 Success,
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
174#[archive(check_bytes)]
175pub enum SplitDirection {
176 Horizontal,
177 Vertical,
178}
179
180#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
182#[archive(check_bytes)]
183pub enum MenuActionType {
184 Command { command: alloc::string::String },
185 Remote { id: alloc::string::String },
186}
187
188#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
190#[archive(check_bytes)]
191pub enum NavFocusableAction {
192 OpenUrl(alloc::string::String),
194 OpenFile(alloc::string::String),
196 Custom(alloc::string::String),
198}
199
200#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
203#[archive(check_bytes)]
204pub enum ControlMessage {
205 Resize {
206 cols: u16,
207 rows: u16,
208 },
209 Input {
210 data: alloc::vec::Vec<u8>,
211 },
212 LoadPlugin {
213 path: alloc::string::String,
214 },
215 Ping {
216 timestamp: u64,
217 },
218 Disconnect {
219 client_id: u64,
220 },
221
222 SessionCreate {
224 name: alloc::string::String,
225 },
226 SessionDelete {
227 id: alloc::string::String,
228 },
229 SessionList,
230 SessionAttach {
231 id: alloc::string::String,
232 },
233 SessionDetach {
234 id: alloc::string::String,
235 },
236 SessionRename {
237 id: alloc::string::String,
238 new_name: alloc::string::String,
239 },
240
241 TabCreate {
243 title: Option<alloc::string::String>,
244 },
245 TabClose {
246 tab_id: u64,
247 },
248 TabSwitch {
249 tab_id: u64,
250 },
251 TabRename {
252 tab_id: u64,
253 new_title: alloc::string::String,
254 },
255 TabList,
256
257 PaneSplit {
259 pane_id: u64,
260 direction: SplitDirection,
261 },
262 PaneClose {
263 pane_id: u64,
264 },
265 PaneFocus {
266 pane_id: u64,
267 },
268 PaneResize {
269 pane_id: u64,
270 width: u16,
271 height: u16,
272 },
273 PaneFocusNext,
275 PaneFocusPrev,
277
278 TabNext,
281 TabPrev,
283
284 MouseClick {
287 col: u16,
288 row: u16,
289 button: u8,
290 },
291
292 CommandSelected {
294 id: alloc::string::String,
295 },
296
297 PluginListRequest,
299 PluginEnable {
300 name: alloc::string::String,
301 },
302 PluginDisable {
303 name: alloc::string::String,
304 },
305 PluginReload {
306 name: alloc::string::String,
307 },
308
309 PluginMenuRequest {
311 plugin_name: alloc::string::String,
312 },
313 PluginMenuExecute {
314 plugin_name: alloc::string::String,
315 action: MenuActionType,
316 },
317
318 PluginLog {
320 plugin_name: alloc::string::String,
321 level: LogLevel,
322 message: alloc::string::String,
323 },
324 PluginNotify {
325 title: alloc::string::String,
326 body: alloc::string::String,
327 level: NotifyLevel,
328 },
329
330 NavEnterHintMode {
332 plugin_name: alloc::string::String,
333 },
334 NavExitMode {
335 plugin_name: alloc::string::String,
336 },
337 NavRegisterFocusable {
338 plugin_name: alloc::string::String,
339 x: u16,
340 y: u16,
341 width: u16,
342 height: u16,
343 label: alloc::string::String,
344 action: NavFocusableAction,
345 },
346 NavUnregisterFocusable {
347 plugin_name: alloc::string::String,
348 focusable_id: u64,
349 },
350
351 ZonesRequest,
354
355 CopyLastOutput,
357
358 SelectZone {
360 zone_id: u64,
361 },
362
363 ExtractZoneText {
365 zone_id: u64,
366 },
367}
368
369#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
371#[archive(check_bytes)]
372pub enum SessionResponse {
373 Created {
374 id: alloc::string::String,
375 name: alloc::string::String,
376 },
377 Deleted {
378 id: alloc::string::String,
379 },
380 List {
381 sessions: alloc::vec::Vec<SessionInfo>,
382 },
383 Attached {
384 id: alloc::string::String,
385 },
386 Detached {
387 id: alloc::string::String,
388 },
389 Renamed {
390 id: alloc::string::String,
391 new_name: alloc::string::String,
392 },
393 Error {
394 message: alloc::string::String,
395 },
396}
397
398#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
399#[archive(check_bytes)]
400pub struct SessionInfo {
401 pub id: alloc::string::String,
402 pub name: alloc::string::String,
403 pub created_at: u64,
404 pub last_attached: u64,
405 pub attached_clients: u32,
406}
407
408#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
410#[archive(check_bytes)]
411pub struct TabInfo {
412 pub id: u64,
413 pub title: alloc::string::String,
414 pub session_id: Option<alloc::string::String>,
415 pub is_active: bool,
416 pub pane_count: u32,
417}
418
419#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
421#[archive(check_bytes)]
422pub struct PaneInfo {
423 pub id: u64,
424 pub x: u16,
425 pub y: u16,
426 pub width: u16,
427 pub height: u16,
428 pub is_focused: bool,
429}
430
431#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
433#[archive(check_bytes)]
434pub struct PluginInspectorInfo {
435 pub name: alloc::string::String,
436 pub version: alloc::string::String,
437 pub description: alloc::string::String,
438 pub author: alloc::string::String,
439 pub homepage: Option<alloc::string::String>,
440 pub api_version: alloc::string::String,
441 pub min_scarab_version: alloc::string::String,
442 pub enabled: bool,
443 pub failure_count: u32,
444 pub emoji: Option<alloc::string::String>,
446 pub color: Option<alloc::string::String>,
448 pub verification: PluginVerificationStatus,
450}
451
452#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
454#[archive(check_bytes)]
455pub enum PluginVerificationStatus {
456 Verified {
458 key_fingerprint: alloc::string::String,
459 signature_timestamp: u64,
460 },
461 ChecksumOnly { checksum: alloc::string::String },
463 Unverified { warning: alloc::string::String },
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
469#[archive(check_bytes)]
470pub enum StatusBarSide {
471 Left,
472 Right,
473}
474
475#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
478#[archive(check_bytes)]
479pub enum StatusRenderItem {
480 Text(alloc::string::String),
481 Icon(alloc::string::String),
482 Foreground { r: u8, g: u8, b: u8 },
483 Background { r: u8, g: u8, b: u8 },
484 Bold,
485 Italic,
486 ResetAttributes,
487 Spacer,
488 Padding(u8),
489 Separator(alloc::string::String),
490}
491
492#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
494#[archive(check_bytes)]
495pub enum DaemonMessage {
496 Session(SessionResponse),
498
499 TabCreated {
501 tab: TabInfo,
502 },
503 TabClosed {
504 tab_id: u64,
505 },
506 TabSwitched {
507 tab_id: u64,
508 },
509 TabListResponse {
510 tabs: alloc::vec::Vec<TabInfo>,
511 },
512
513 PaneCreated {
515 pane: PaneInfo,
516 },
517 PaneClosed {
518 pane_id: u64,
519 },
520 PaneFocused {
521 pane_id: u64,
522 },
523 PaneLayoutUpdate {
524 panes: alloc::vec::Vec<PaneInfo>,
525 },
526
527 StatusBarUpdate {
529 window_id: u64,
530 side: StatusBarSide,
531 items: alloc::vec::Vec<StatusRenderItem>,
532 },
533
534 DrawOverlay {
536 id: u64, x: u16,
538 y: u16,
539 text: alloc::string::String,
540 style: OverlayStyle,
541 },
542 ClearOverlays {
543 id: Option<u64>, },
545 ShowModal {
546 title: alloc::string::String,
547 items: alloc::vec::Vec<ModalItem>,
548 },
549 HideModal,
550
551 PluginList {
553 plugins: alloc::vec::Vec<PluginInspectorInfo>,
554 },
555 PluginStatusChanged {
556 name: alloc::string::String,
557 enabled: bool,
558 },
559 PluginError {
560 name: alloc::string::String,
561 error: alloc::string::String,
562 },
563
564 PluginLog {
566 plugin_name: alloc::string::String,
567 level: LogLevel,
568 message: alloc::string::String,
569 },
570 PluginNotification {
571 title: alloc::string::String,
572 body: alloc::string::String,
573 level: NotifyLevel,
574 },
575
576 PluginMenuResponse {
578 plugin_name: alloc::string::String,
579 menu_json: alloc::string::String, },
581 PluginMenuError {
582 plugin_name: alloc::string::String,
583 error: alloc::string::String,
584 },
585
586 ThemeUpdate {
588 theme_json: alloc::string::String, },
590
591 PromptMarkersUpdate {
593 markers: alloc::vec::Vec<PromptMarkerInfo>,
595 },
596
597 SemanticZonesUpdate {
599 zones: alloc::vec::Vec<SemanticZone>,
601 },
602
603 CommandBlocksUpdate {
605 blocks: alloc::vec::Vec<CommandBlock>,
607 },
608
609 ZoneTextExtracted {
611 zone_id: u64,
612 text: alloc::string::String,
613 },
614
615 Event(EventMessage),
617
618 NavFocusableRegistered {
620 plugin_name: alloc::string::String,
621 focusable_id: u64,
622 },
623 NavFocusableUnregistered {
624 plugin_name: alloc::string::String,
625 focusable_id: u64,
626 },
627 NavModeEntered {
628 plugin_name: alloc::string::String,
629 },
630 NavModeExited {
631 plugin_name: alloc::string::String,
632 },
633 NavRegisterFocusable {
635 plugin_name: alloc::string::String,
636 x: u16,
637 y: u16,
638 width: u16,
639 height: u16,
640 label: alloc::string::String,
641 action: NavFocusableAction,
642 },
643 NavUnregisterFocusable {
645 plugin_name: alloc::string::String,
646 focusable_id: u64,
647 },
648 SpawnOverlay {
650 plugin_name: alloc::string::String,
651 overlay_id: u64,
652 x: u16,
653 y: u16,
654 content: alloc::string::String,
655 style: OverlayStyle,
656 },
657 RemoveOverlay {
659 plugin_name: alloc::string::String,
660 overlay_id: u64,
661 },
662 AddStatusItem {
664 plugin_name: alloc::string::String,
665 item_id: u64,
666 label: alloc::string::String,
667 content: alloc::string::String,
668 priority: i32,
669 },
670 RemoveStatusItem {
672 plugin_name: alloc::string::String,
673 item_id: u64,
674 },
675 PromptJump {
677 plugin_name: alloc::string::String,
678 direction: PromptJumpDirection,
679 },
680 ThemeApply {
682 theme_name: alloc::string::String,
683 },
684 PaletteColorSet {
686 color_name: alloc::string::String,
687 value: alloc::string::String,
688 },
689 ThemeInfoResponse {
691 plugin_name: alloc::string::String,
692 theme_name: alloc::string::String,
693 },
694}
695
696#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
698#[archive(check_bytes)]
699pub enum PromptJumpDirection {
700 Up,
701 Down,
702 First,
703 Last,
704}
705
706#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
708#[archive(check_bytes)]
709pub struct EventMessage {
710 pub event_type: alloc::string::String,
712 pub window_id: Option<u64>,
714 pub pane_id: Option<u64>,
716 pub tab_id: Option<u64>,
718 pub data: alloc::vec::Vec<u8>,
720 pub timestamp_micros: u64,
722}
723
724#[derive(Debug, Clone, Copy, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
725#[archive(check_bytes)]
726pub struct OverlayStyle {
727 pub fg: u32, pub bg: u32, pub z_index: f32,
730}
731
732impl Default for OverlayStyle {
733 fn default() -> Self {
734 Self {
735 fg: 0xFFFFFFFF, bg: 0xFF0000FF, z_index: 100.0,
738 }
739 }
740}
741
742#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
743#[archive(check_bytes)]
744pub struct ModalItem {
745 pub id: alloc::string::String,
746 pub label: alloc::string::String,
747 pub description: Option<alloc::string::String>,
748}
749
750pub const SOCKET_PATH: &str = "/tmp/scarab-daemon.sock";
752pub const MAX_MESSAGE_SIZE: usize = 8192;
753pub const MAX_CLIENTS: usize = 16;
754pub const RECONNECT_DELAY_MS: u64 = 100;
755pub const MAX_RECONNECT_ATTEMPTS: u32 = 10;
756
757#[derive(Debug, Clone, Copy)]
766#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Resource))]
767pub struct TerminalMetrics {
768 pub cell_width: f32,
770 pub cell_height: f32,
772 pub columns: u16,
774 pub rows: u16,
776}
777
778impl Default for TerminalMetrics {
779 fn default() -> Self {
780 Self {
781 cell_width: 9.0, cell_height: 18.0, columns: 80,
784 rows: 24,
785 }
786 }
787}
788
789impl TerminalMetrics {
790 pub fn new(font_size: f32, line_height_multiplier: f32, columns: u16, rows: u16) -> Self {
792 Self {
793 cell_width: font_size * 0.6, cell_height: font_size * line_height_multiplier,
795 columns,
796 rows,
797 }
798 }
799
800 pub fn screen_to_grid(&self, screen_x: f32, screen_y: f32) -> (u16, u16) {
809 let col = (screen_x / self.cell_width).floor() as i32;
810 let row = (screen_y / self.cell_height).floor() as i32;
811
812 let col = col.max(0).min((self.columns - 1) as i32) as u16;
814 let row = row.max(0).min((self.rows - 1) as i32) as u16;
815
816 (col, row)
817 }
818
819 pub fn grid_to_screen(&self, col: u16, row: u16) -> (f32, f32) {
824 let x = col as f32 * self.cell_width;
825 let y = row as f32 * self.cell_height;
826 (x, y)
827 }
828
829 pub fn screen_size(&self) -> (f32, f32) {
831 (
832 self.columns as f32 * self.cell_width,
833 self.rows as f32 * self.cell_height,
834 )
835 }
836}
837
838#[derive(Debug, Clone, Copy, PartialEq, Eq)]
840#[repr(u8)]
841pub enum ImageFormat {
842 Png = 0,
844 Jpeg = 1,
846 Gif = 2,
848 Rgba = 3,
850}
851
852#[derive(Debug, Clone)]
857pub struct ImagePlacement {
858 pub id: u64,
860 pub x: u16,
862 pub y: u16,
864 pub width_cells: u16,
866 pub height_cells: u16,
868 pub shm_offset: usize,
870 pub shm_size: usize,
872 pub format: ImageFormat,
874}
875
876#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
885#[archive(check_bytes)]
886pub struct PromptMarkerInfo {
887 pub marker_type: u8,
893 pub line: u32,
895 pub exit_code: Option<i32>,
897 pub timestamp_micros: u64,
899}
900
901impl PromptMarkerInfo {
902 pub fn prompt_start(line: u32, timestamp_micros: u64) -> Self {
904 Self {
905 marker_type: 0,
906 line,
907 exit_code: None,
908 timestamp_micros,
909 }
910 }
911
912 pub fn command_start(line: u32, timestamp_micros: u64) -> Self {
914 Self {
915 marker_type: 1,
916 line,
917 exit_code: None,
918 timestamp_micros,
919 }
920 }
921
922 pub fn command_executed(line: u32, timestamp_micros: u64) -> Self {
924 Self {
925 marker_type: 2,
926 line,
927 exit_code: None,
928 timestamp_micros,
929 }
930 }
931
932 pub fn command_finished(line: u32, exit_code: i32, timestamp_micros: u64) -> Self {
934 Self {
935 marker_type: 3,
936 line,
937 exit_code: Some(exit_code),
938 timestamp_micros,
939 }
940 }
941
942 pub fn is_prompt_start(&self) -> bool {
944 self.marker_type == 0
945 }
946
947 pub fn is_command_start(&self) -> bool {
949 self.marker_type == 1
950 }
951
952 pub fn is_command_executed(&self) -> bool {
954 self.marker_type == 2
955 }
956
957 pub fn is_command_finished(&self) -> bool {
959 self.marker_type == 3
960 }
961}
962
963extern crate alloc;