Skip to main content

taskers_ghostty/
backend.rs

1use crate::bridge::{runtime_bridge_path, runtime_resources_dir};
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum BackendChoice {
8    Auto,
9    Ghostty,
10    Mock,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum BackendAvailability {
16    Ready,
17    Fallback,
18    Unavailable,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct BackendProbe {
23    pub requested: BackendChoice,
24    pub selected: BackendChoice,
25    pub availability: BackendAvailability,
26    pub notes: String,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct SurfaceDescriptor {
31    pub cols: u16,
32    pub rows: u16,
33    pub cwd: Option<String>,
34    pub title: Option<String>,
35}
36
37#[derive(Debug, Error)]
38pub enum AdapterError {
39    #[error("terminal backend is unavailable: {0}")]
40    Unavailable(String),
41    #[error("terminal backend initialization failed: {0}")]
42    Initialization(String),
43}
44
45pub trait TerminalBackend {
46    fn probe(requested: BackendChoice) -> BackendProbe;
47}
48
49pub struct DefaultBackend;
50
51impl TerminalBackend for DefaultBackend {
52    fn probe(requested: BackendChoice) -> BackendProbe {
53        let env_override = std::env::var("TASKERS_TERMINAL_BACKEND").ok();
54        let requested = match env_override.as_deref() {
55            Some("ghostty") => BackendChoice::Ghostty,
56            Some("mock") => BackendChoice::Mock,
57            _ => requested,
58        };
59
60        match requested {
61            BackendChoice::Auto => auto_probe(requested),
62            BackendChoice::Mock => BackendProbe {
63                requested,
64                selected: BackendChoice::Mock,
65                availability: BackendAvailability::Fallback,
66                notes: "Using placeholder terminal surfaces.".into(),
67            },
68            BackendChoice::Ghostty => BackendProbe {
69                requested,
70                selected: BackendChoice::Ghostty,
71                availability: ghostty_availability(),
72                notes: ghostty_notes(),
73            },
74        }
75    }
76}
77
78fn auto_probe(requested: BackendChoice) -> BackendProbe {
79    let availability = ghostty_availability();
80    if matches!(availability, BackendAvailability::Ready) {
81        BackendProbe {
82            requested,
83            selected: BackendChoice::Ghostty,
84            availability,
85            notes: ghostty_notes(),
86        }
87    } else {
88        BackendProbe {
89            requested,
90            selected: BackendChoice::Mock,
91            availability: BackendAvailability::Fallback,
92            notes: "Ghostty bridge unavailable, using placeholder terminal surfaces.".into(),
93        }
94    }
95}
96
97fn ghostty_availability() -> BackendAvailability {
98    #[cfg(all(target_os = "linux", taskers_ghostty_bridge))]
99    {
100        if runtime_bridge_path().is_some() {
101            BackendAvailability::Ready
102        } else {
103            BackendAvailability::Unavailable
104        }
105    }
106
107    #[cfg(not(all(target_os = "linux", taskers_ghostty_bridge)))]
108    {
109        BackendAvailability::Unavailable
110    }
111}
112
113fn ghostty_notes() -> String {
114    let mut notes = String::from("Ghostty GTK bridge compiled in.");
115    if let Some(path) = runtime_bridge_path() {
116        notes.push_str(" Bridge: ");
117        notes.push_str(&path.display().to_string());
118    } else {
119        notes.push_str(" Bridge library not found.");
120    }
121    if let Some(path) = runtime_resources_dir() {
122        notes.push_str(" Resources: ");
123        notes.push_str(&path.display().to_string());
124    }
125    notes
126}
127
128#[cfg(test)]
129mod tests {
130    use super::{BackendAvailability, BackendChoice, DefaultBackend, TerminalBackend};
131
132    #[test]
133    fn auto_probe_matches_runtime_availability() {
134        let probe = DefaultBackend::probe(BackendChoice::Auto);
135        match probe.availability {
136            BackendAvailability::Ready => assert_eq!(probe.selected, BackendChoice::Ghostty),
137            BackendAvailability::Fallback | BackendAvailability::Unavailable => {
138                assert_eq!(probe.selected, BackendChoice::Mock);
139            }
140        }
141    }
142}