@@ -8,9 +8,10 @@ use std::{
88 collections:: { HashMap , HashSet } ,
99 future:: pending,
1010 path:: PathBuf ,
11+ process:: { Command , Stdio } ,
1112 rc:: Rc ,
1213 thread,
13- time:: Duration ,
14+ time:: { Duration , Instant } ,
1415} ;
1516
1617use adw:: prelude:: * ;
@@ -36,6 +37,7 @@ use taskers_domain::{
3637} ;
3738use taskers_ghostty:: {
3839 BackendChoice , BackendProbe , DefaultBackend , GhosttyHost , SurfaceDescriptor , TerminalBackend ,
40+ ensure_runtime_installed,
3941} ;
4042use taskers_runtime:: {
4143 ShellLaunchSpec , default_shell_program, install_shell_integration, validate_shell_program,
@@ -55,6 +57,8 @@ struct Cli {
5557 clean_shell : bool ,
5658 #[ arg( long, default_value_t = false , conflicts_with = "clean_shell" ) ]
5759 raw_shell : bool ,
60+ #[ arg( long, hide = true , default_value_t = false ) ]
61+ internal_ghostty_probe : bool ,
5862}
5963
6064struct StartupContext {
@@ -1462,6 +1466,9 @@ impl UiHandle {
14621466
14631467fn main ( ) -> gtk:: glib:: ExitCode {
14641468 let cli = Cli :: parse ( ) ;
1469+ if cli. internal_ghostty_probe {
1470+ return run_internal_ghostty_probe ( ) ;
1471+ }
14651472 let shell_mode_non_unique = cli. clean_shell || cli. raw_shell ;
14661473 let run_non_unique = shell_mode_non_unique
14671474 || cli. socket . is_some ( )
@@ -1472,6 +1479,14 @@ fn main() -> gtk::glib::ExitCode {
14721479 . session
14731480 . unwrap_or_else ( session_store:: default_session_path) ;
14741481 let config_path = settings_store:: default_config_path ( ) ;
1482+ let ghostty_runtime_toast = match ensure_runtime_installed ( ) {
1483+ Ok ( Some ( runtime) ) => Some ( format ! (
1484+ "Installed Ghostty runtime assets to {}" ,
1485+ runtime. runtime_dir. display( )
1486+ ) ) ,
1487+ Ok ( None ) => None ,
1488+ Err ( error) => Some ( format ! ( "Ghostty runtime bootstrap unavailable: {error}" ) ) ,
1489+ } ;
14751490 let probe = DefaultBackend :: probe ( BackendChoice :: Auto ) ;
14761491 if cli. clean_shell {
14771492 unsafe {
@@ -1523,7 +1538,7 @@ fn main() -> gtk::glib::ExitCode {
15231538 initialize_terminal_backend ( & probe) ;
15241539 let startup_toast = merge_startup_toasts (
15251540 merge_startup_toasts (
1526- shell_integration_toast,
1541+ merge_startup_toasts ( ghostty_runtime_toast , shell_integration_toast) ,
15271542 cli. clean_shell
15281543 . then ( || "Using clean shell startup" . to_string ( ) ) ,
15291544 ) ,
@@ -1657,6 +1672,15 @@ fn initialize_terminal_backend(
16571672 return ( BackendChoice :: Mock , probe. notes . clone ( ) , None , None ) ;
16581673 }
16591674
1675+ if let Err ( error) = probe_ghostty_backend_process ( ) {
1676+ let note = format ! (
1677+ "{} Ghostty self-probe failed; using placeholder terminal surfaces." ,
1678+ probe. notes
1679+ ) ;
1680+ let toast = format ! ( "Ghostty backend unavailable: {error}" ) ;
1681+ return ( BackendChoice :: Mock , note, None , Some ( toast) ) ;
1682+ }
1683+
16601684 match GhosttyHost :: new ( ) {
16611685 Ok ( host) => (
16621686 BackendChoice :: Ghostty ,
@@ -1672,6 +1696,67 @@ fn initialize_terminal_backend(
16721696 }
16731697}
16741698
1699+ fn run_internal_ghostty_probe ( ) -> gtk:: glib:: ExitCode {
1700+ match GhosttyHost :: new ( ) {
1701+ Ok ( host) => {
1702+ let _ = host. tick ( ) ;
1703+ thread:: sleep ( Duration :: from_millis ( 250 ) ) ;
1704+ gtk:: glib:: ExitCode :: SUCCESS
1705+ }
1706+ Err ( error) => {
1707+ eprintln ! ( "ghostty self-probe failed: {error}" ) ;
1708+ gtk:: glib:: ExitCode :: FAILURE
1709+ }
1710+ }
1711+ }
1712+
1713+ fn probe_ghostty_backend_process ( ) -> Result < ( ) , String > {
1714+ let current_exe = std:: env:: current_exe ( )
1715+ . map_err ( |error| format ! ( "failed to resolve current executable: {error}" ) ) ?;
1716+ let mut child = Command :: new ( current_exe)
1717+ . arg ( "--internal-ghostty-probe" )
1718+ . stdin ( Stdio :: null ( ) )
1719+ . stdout ( Stdio :: null ( ) )
1720+ . stderr ( Stdio :: null ( ) )
1721+ . spawn ( )
1722+ . map_err ( |error| format ! ( "failed to launch Ghostty self-probe: {error}" ) ) ?;
1723+
1724+ let deadline = Instant :: now ( ) + Duration :: from_secs ( 5 ) ;
1725+ loop {
1726+ match child. try_wait ( ) {
1727+ Ok ( Some ( status) ) => {
1728+ if status. success ( ) {
1729+ return Ok ( ( ) ) ;
1730+ }
1731+ return Err ( describe_exit_status ( status) ) ;
1732+ }
1733+ Ok ( None ) if Instant :: now ( ) < deadline => thread:: sleep ( Duration :: from_millis ( 50 ) ) ,
1734+ Ok ( None ) => {
1735+ let _ = child. kill ( ) ;
1736+ let _ = child. wait ( ) ;
1737+ return Err ( "Ghostty self-probe timed out" . into ( ) ) ;
1738+ }
1739+ Err ( error) => return Err ( format ! ( "failed to wait for Ghostty self-probe: {error}" ) ) ,
1740+ }
1741+ }
1742+ }
1743+
1744+ fn describe_exit_status ( status : std:: process:: ExitStatus ) -> String {
1745+ #[ cfg( unix) ]
1746+ {
1747+ use std:: os:: unix:: process:: ExitStatusExt ;
1748+
1749+ if let Some ( signal) = status. signal ( ) {
1750+ return format ! ( "Ghostty self-probe crashed with signal {signal}" ) ;
1751+ }
1752+ }
1753+
1754+ match status. code ( ) {
1755+ Some ( code) => format ! ( "Ghostty self-probe exited with status {code}" ) ,
1756+ None => "Ghostty self-probe exited unsuccessfully" . into ( ) ,
1757+ }
1758+ }
1759+
16751760fn merge_startup_toasts ( first : Option < String > , second : Option < String > ) -> Option < String > {
16761761 match ( first, second) {
16771762 ( Some ( first) , Some ( second) ) => Some ( format ! ( "{first}\n {second}" ) ) ,
0 commit comments