#![no_std]
use bytemuck::{Pod, Zeroable};
pub mod terminal_state;
pub use terminal_state::TerminalStateReader;
pub mod zones;
pub use zones::{CommandBlock, SemanticZone, ZoneTracker, ZoneType};
pub const SHMEM_PATH: &str = "/scarab_shm_v1";
pub const SHMEM_PATH_ENV: &str = "SCARAB_SHMEM_PATH";
pub const IMAGE_SHMEM_PATH_ENV: &str = "SCARAB_IMAGE_SHMEM_PATH";
pub const GRID_WIDTH: usize = 200;
pub const GRID_HEIGHT: usize = 100;
pub const BUFFER_SIZE: usize = GRID_WIDTH * GRID_HEIGHT;
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct Cell {
pub char_codepoint: u32,
pub fg: u32, pub bg: u32, pub flags: u8, pub _padding: [u8; 3], }
impl Default for Cell {
fn default() -> Self {
Self {
char_codepoint: b' ' as u32,
fg: 0xFFA8DF5A, bg: 0xFF0D1208, flags: 0,
_padding: [0; 3],
}
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct SharedState {
pub sequence_number: u64, pub dirty_flag: u8,
pub error_mode: u8, pub cursor_x: u16,
pub cursor_y: u16,
pub _padding2: [u8; 2], pub cells: [Cell; BUFFER_SIZE],
}
unsafe impl Pod for SharedState {}
unsafe impl Zeroable for SharedState {}
pub const MAX_IMAGES: usize = 64;
pub const IMAGE_BUFFER_SIZE: usize = 16 * 1024 * 1024;
pub const IMAGE_SHMEM_PATH: &str = "/scarab_img_shm_v1";
#[repr(C)]
#[derive(Copy, Clone)]
pub struct SharedImagePlacement {
pub image_id: u64,
pub x: u16,
pub y: u16,
pub width_cells: u16,
pub height_cells: u16,
pub pixel_width: u32,
pub pixel_height: u32,
pub blob_offset: u32,
pub blob_size: u32,
pub format: u8,
pub flags: u8,
pub _padding: [u8; 6],
}
unsafe impl Pod for SharedImagePlacement {}
unsafe impl Zeroable for SharedImagePlacement {}
impl SharedImagePlacement {
pub const fn is_valid(&self) -> bool {
(self.flags & 0x01) != 0
}
pub fn set_valid(&mut self) {
self.flags |= 0x01;
}
pub fn set_invalid(&mut self) {
self.flags &= !0x01;
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct SharedImageBuffer {
pub sequence_number: u64,
pub count: u32,
pub next_blob_offset: u32,
pub placements: [SharedImagePlacement; MAX_IMAGES],
pub blob_data: [u8; IMAGE_BUFFER_SIZE],
}
unsafe impl Pod for SharedImageBuffer {}
unsafe impl Zeroable for SharedImageBuffer {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum NotifyLevel {
Error,
Warning,
Info,
Success,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum SplitDirection {
Horizontal,
Vertical,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum MenuActionType {
Command { command: alloc::string::String },
Remote { id: alloc::string::String },
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum NavFocusableAction {
OpenUrl(alloc::string::String),
OpenFile(alloc::string::String),
Custom(alloc::string::String),
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum ControlMessage {
Resize {
cols: u16,
rows: u16,
},
Input {
data: alloc::vec::Vec<u8>,
},
LoadPlugin {
path: alloc::string::String,
},
Ping {
timestamp: u64,
},
Disconnect {
client_id: u64,
},
SessionCreate {
name: alloc::string::String,
},
SessionDelete {
id: alloc::string::String,
},
SessionList,
SessionAttach {
id: alloc::string::String,
},
SessionDetach {
id: alloc::string::String,
},
SessionRename {
id: alloc::string::String,
new_name: alloc::string::String,
},
TabCreate {
title: Option<alloc::string::String>,
},
TabClose {
tab_id: u64,
},
TabSwitch {
tab_id: u64,
},
TabRename {
tab_id: u64,
new_title: alloc::string::String,
},
TabList,
PaneSplit {
pane_id: u64,
direction: SplitDirection,
},
PaneClose {
pane_id: u64,
},
PaneFocus {
pane_id: u64,
},
PaneResize {
pane_id: u64,
width: u16,
height: u16,
},
PaneFocusNext,
PaneFocusPrev,
TabNext,
TabPrev,
MouseClick {
col: u16,
row: u16,
button: u8,
},
CommandSelected {
id: alloc::string::String,
},
PluginListRequest,
PluginEnable {
name: alloc::string::String,
},
PluginDisable {
name: alloc::string::String,
},
PluginReload {
name: alloc::string::String,
},
PluginMenuRequest {
plugin_name: alloc::string::String,
},
PluginMenuExecute {
plugin_name: alloc::string::String,
action: MenuActionType,
},
PluginLog {
plugin_name: alloc::string::String,
level: LogLevel,
message: alloc::string::String,
},
PluginNotify {
title: alloc::string::String,
body: alloc::string::String,
level: NotifyLevel,
},
NavEnterHintMode {
plugin_name: alloc::string::String,
},
NavExitMode {
plugin_name: alloc::string::String,
},
NavRegisterFocusable {
plugin_name: alloc::string::String,
x: u16,
y: u16,
width: u16,
height: u16,
label: alloc::string::String,
action: NavFocusableAction,
},
NavUnregisterFocusable {
plugin_name: alloc::string::String,
focusable_id: u64,
},
ZonesRequest,
CopyLastOutput,
SelectZone {
zone_id: u64,
},
ExtractZoneText {
zone_id: u64,
},
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum SessionResponse {
Created {
id: alloc::string::String,
name: alloc::string::String,
},
Deleted {
id: alloc::string::String,
},
List {
sessions: alloc::vec::Vec<SessionInfo>,
},
Attached {
id: alloc::string::String,
},
Detached {
id: alloc::string::String,
},
Renamed {
id: alloc::string::String,
new_name: alloc::string::String,
},
Error {
message: alloc::string::String,
},
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct SessionInfo {
pub id: alloc::string::String,
pub name: alloc::string::String,
pub created_at: u64,
pub last_attached: u64,
pub attached_clients: u32,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct TabInfo {
pub id: u64,
pub title: alloc::string::String,
pub session_id: Option<alloc::string::String>,
pub is_active: bool,
pub pane_count: u32,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct PaneInfo {
pub id: u64,
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
pub is_focused: bool,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct PluginInspectorInfo {
pub name: alloc::string::String,
pub version: alloc::string::String,
pub description: alloc::string::String,
pub author: alloc::string::String,
pub homepage: Option<alloc::string::String>,
pub api_version: alloc::string::String,
pub min_scarab_version: alloc::string::String,
pub enabled: bool,
pub failure_count: u32,
pub emoji: Option<alloc::string::String>,
pub color: Option<alloc::string::String>,
pub verification: PluginVerificationStatus,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum PluginVerificationStatus {
Verified {
key_fingerprint: alloc::string::String,
signature_timestamp: u64,
},
ChecksumOnly { checksum: alloc::string::String },
Unverified { warning: alloc::string::String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum StatusBarSide {
Left,
Right,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum StatusRenderItem {
Text(alloc::string::String),
Icon(alloc::string::String),
Foreground { r: u8, g: u8, b: u8 },
Background { r: u8, g: u8, b: u8 },
Bold,
Italic,
ResetAttributes,
Spacer,
Padding(u8),
Separator(alloc::string::String),
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum DaemonMessage {
Session(SessionResponse),
TabCreated {
tab: TabInfo,
},
TabClosed {
tab_id: u64,
},
TabSwitched {
tab_id: u64,
},
TabListResponse {
tabs: alloc::vec::Vec<TabInfo>,
},
PaneCreated {
pane: PaneInfo,
},
PaneClosed {
pane_id: u64,
},
PaneFocused {
pane_id: u64,
},
PaneLayoutUpdate {
panes: alloc::vec::Vec<PaneInfo>,
},
StatusBarUpdate {
window_id: u64,
side: StatusBarSide,
items: alloc::vec::Vec<StatusRenderItem>,
},
DrawOverlay {
id: u64, x: u16,
y: u16,
text: alloc::string::String,
style: OverlayStyle,
},
ClearOverlays {
id: Option<u64>, },
ShowModal {
title: alloc::string::String,
items: alloc::vec::Vec<ModalItem>,
},
HideModal,
PluginList {
plugins: alloc::vec::Vec<PluginInspectorInfo>,
},
PluginStatusChanged {
name: alloc::string::String,
enabled: bool,
},
PluginError {
name: alloc::string::String,
error: alloc::string::String,
},
PluginLog {
plugin_name: alloc::string::String,
level: LogLevel,
message: alloc::string::String,
},
PluginNotification {
title: alloc::string::String,
body: alloc::string::String,
level: NotifyLevel,
},
PluginMenuResponse {
plugin_name: alloc::string::String,
menu_json: alloc::string::String, },
PluginMenuError {
plugin_name: alloc::string::String,
error: alloc::string::String,
},
ThemeUpdate {
theme_json: alloc::string::String, },
PromptMarkersUpdate {
markers: alloc::vec::Vec<PromptMarkerInfo>,
},
SemanticZonesUpdate {
zones: alloc::vec::Vec<SemanticZone>,
},
CommandBlocksUpdate {
blocks: alloc::vec::Vec<CommandBlock>,
},
ZoneTextExtracted {
zone_id: u64,
text: alloc::string::String,
},
Event(EventMessage),
NavFocusableRegistered {
plugin_name: alloc::string::String,
focusable_id: u64,
},
NavFocusableUnregistered {
plugin_name: alloc::string::String,
focusable_id: u64,
},
NavModeEntered {
plugin_name: alloc::string::String,
},
NavModeExited {
plugin_name: alloc::string::String,
},
NavRegisterFocusable {
plugin_name: alloc::string::String,
x: u16,
y: u16,
width: u16,
height: u16,
label: alloc::string::String,
action: NavFocusableAction,
},
NavUnregisterFocusable {
plugin_name: alloc::string::String,
focusable_id: u64,
},
SpawnOverlay {
plugin_name: alloc::string::String,
overlay_id: u64,
x: u16,
y: u16,
content: alloc::string::String,
style: OverlayStyle,
},
RemoveOverlay {
plugin_name: alloc::string::String,
overlay_id: u64,
},
AddStatusItem {
plugin_name: alloc::string::String,
item_id: u64,
label: alloc::string::String,
content: alloc::string::String,
priority: i32,
},
RemoveStatusItem {
plugin_name: alloc::string::String,
item_id: u64,
},
PromptJump {
plugin_name: alloc::string::String,
direction: PromptJumpDirection,
},
ThemeApply {
theme_name: alloc::string::String,
},
PaletteColorSet {
color_name: alloc::string::String,
value: alloc::string::String,
},
ThemeInfoResponse {
plugin_name: alloc::string::String,
theme_name: alloc::string::String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub enum PromptJumpDirection {
Up,
Down,
First,
Last,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct EventMessage {
pub event_type: alloc::string::String,
pub window_id: Option<u64>,
pub pane_id: Option<u64>,
pub tab_id: Option<u64>,
pub data: alloc::vec::Vec<u8>,
pub timestamp_micros: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct OverlayStyle {
pub fg: u32, pub bg: u32, pub z_index: f32,
}
impl Default for OverlayStyle {
fn default() -> Self {
Self {
fg: 0xFFFFFFFF, bg: 0xFF0000FF, z_index: 100.0,
}
}
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct ModalItem {
pub id: alloc::string::String,
pub label: alloc::string::String,
pub description: Option<alloc::string::String>,
}
pub const SOCKET_PATH: &str = "/tmp/scarab-daemon.sock";
pub const MAX_MESSAGE_SIZE: usize = 8192;
pub const MAX_CLIENTS: usize = 16;
pub const RECONNECT_DELAY_MS: u64 = 100;
pub const MAX_RECONNECT_ATTEMPTS: u32 = 10;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Resource))]
pub struct TerminalMetrics {
pub cell_width: f32,
pub cell_height: f32,
pub columns: u16,
pub rows: u16,
}
impl Default for TerminalMetrics {
fn default() -> Self {
Self {
cell_width: 9.0, cell_height: 18.0, columns: 80,
rows: 24,
}
}
}
impl TerminalMetrics {
pub fn new(font_size: f32, line_height_multiplier: f32, columns: u16, rows: u16) -> Self {
Self {
cell_width: font_size * 0.6, cell_height: font_size * line_height_multiplier,
columns,
rows,
}
}
pub fn screen_to_grid(&self, screen_x: f32, screen_y: f32) -> (u16, u16) {
let col = (screen_x / self.cell_width).floor() as i32;
let row = (screen_y / self.cell_height).floor() as i32;
let col = col.max(0).min((self.columns - 1) as i32) as u16;
let row = row.max(0).min((self.rows - 1) as i32) as u16;
(col, row)
}
pub fn grid_to_screen(&self, col: u16, row: u16) -> (f32, f32) {
let x = col as f32 * self.cell_width;
let y = row as f32 * self.cell_height;
(x, y)
}
pub fn screen_size(&self) -> (f32, f32) {
(
self.columns as f32 * self.cell_width,
self.rows as f32 * self.cell_height,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ImageFormat {
Png = 0,
Jpeg = 1,
Gif = 2,
Rgba = 3,
}
#[derive(Debug, Clone)]
pub struct ImagePlacement {
pub id: u64,
pub x: u16,
pub y: u16,
pub width_cells: u16,
pub height_cells: u16,
pub shm_offset: usize,
pub shm_size: usize,
pub format: ImageFormat,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[archive(check_bytes)]
pub struct PromptMarkerInfo {
pub marker_type: u8,
pub line: u32,
pub exit_code: Option<i32>,
pub timestamp_micros: u64,
}
impl PromptMarkerInfo {
pub fn prompt_start(line: u32, timestamp_micros: u64) -> Self {
Self {
marker_type: 0,
line,
exit_code: None,
timestamp_micros,
}
}
pub fn command_start(line: u32, timestamp_micros: u64) -> Self {
Self {
marker_type: 1,
line,
exit_code: None,
timestamp_micros,
}
}
pub fn command_executed(line: u32, timestamp_micros: u64) -> Self {
Self {
marker_type: 2,
line,
exit_code: None,
timestamp_micros,
}
}
pub fn command_finished(line: u32, exit_code: i32, timestamp_micros: u64) -> Self {
Self {
marker_type: 3,
line,
exit_code: Some(exit_code),
timestamp_micros,
}
}
pub fn is_prompt_start(&self) -> bool {
self.marker_type == 0
}
pub fn is_command_start(&self) -> bool {
self.marker_type == 1
}
pub fn is_command_executed(&self) -> bool {
self.marker_type == 2
}
pub fn is_command_finished(&self) -> bool {
self.marker_type == 3
}
}
extern crate alloc;