diff --git a/src/event_loop/mod.rs b/src/event_loop/mod.rs index b945dc6..e086efd 100644 --- a/src/event_loop/mod.rs +++ b/src/event_loop/mod.rs @@ -48,9 +48,11 @@ use std::collections::HashSet; /// Every variant maps to a fatal failure during init: the Wayland /// connection, the calloop event loop, or one of the protocol bindings /// the runtime cannot operate without (`wl_compositor`, `wl_shm`, -/// `xdg_wm_base`). Once init succeeds, runtime errors during the dispatch -/// loop still panic — they are non-recoverable from the caller's point -/// of view since the surface is already on screen. +/// `xdg_wm_base`). Once init succeeds, the compositor disconnecting +/// (`BrokenPipe` / `ConnectionReset`) is treated as a clean exit; any +/// other runtime error during the dispatch loop still panics, since the +/// surface is already on screen and the state machine cannot be unwound +/// from this entry point. /// /// Embedders that want a software-rendered fallback or that need to /// degrade gracefully should call [`crate::try_run`] and match on the @@ -373,7 +375,31 @@ pub( crate ) fn try_run( app: A ) -> Result<(), RunError> // cap the wait at its deadline so a perfectly still press still // fires on time. let timeout = data.next_long_press_wakeup(); - event_loop.dispatch( timeout, &mut data ).expect( "dispatch" ); + match event_loop.dispatch( timeout, &mut data ) + { + Ok( () ) => {}, + Err( calloop::Error::IoError( ref e ) ) + if matches!( e.kind(), std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::ConnectionReset ) => + { + // On a fatal `wl_display.error` the compositor closes the + // socket; the backend has the typed error stashed but the + // dispatch result only carries the resulting `BrokenPipe`. + if let Some( pe ) = conn.protocol_error() + { + eprintln!( + "ltk: wayland protocol error — interface={} object_id={} code={}: {}", + pe.object_interface, pe.object_id, pe.code, pe.message + ); + } + else + { + eprintln!( "ltk: wayland connection lost ({e}); exiting" ); + } + data.exit_requested = true; + continue; + } + Err( e ) => panic!( "dispatch: {e:?}" ), + } // Any surface whose press has now crossed `long_press_duration` // emits its stored message and flips into drag mode for the rest // of the gesture. diff --git a/src/theme/mod.rs b/src/theme/mod.rs index a495cd0..0995255 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -162,6 +162,8 @@ pub struct LauncherSpec #[ derive( Debug, Clone, Copy ) ] pub struct WindowControlsSpec { + /// Background fill of the SSD title bar strip the controls sit on. + pub bar_bg: Color, /// Symbol colour for minimize / maximize / restore / close glyphs. pub icon: Color, /// Button background while hovered. @@ -972,6 +974,7 @@ pub ( super ) fn default_window_controls( palette: Palette ) -> WindowControlsSp { WindowControlsSpec { + bar_bg: Color { a: 1.0, ..palette.surface }, icon: palette.icon, hover_bg: palette.surface_alt, pressed_bg: palette.divider, diff --git a/src/theme/schema.rs b/src/theme/schema.rs index 46618cd..a7de459 100644 --- a/src/theme/schema.rs +++ b/src/theme/schema.rs @@ -740,6 +740,7 @@ struct RawLauncher #[ serde( deny_unknown_fields ) ] struct RawWindowControls { + #[ serde( default ) ] bar_bg: Option, #[ serde( default ) ] icon: Option, #[ serde( default ) ] hover_bg: Option, #[ serde( default ) ] pressed_bg: Option, @@ -814,6 +815,7 @@ fn window_controls_from_raw Some( p ) => default_window_controls( *p ), None => WindowControlsSpec { + bar_bg: Color::WHITE, icon: Color::BLACK, hover_bg: Color::rgba( 0.0, 0.0, 0.0, 0.08 ), pressed_bg: Color::rgba( 0.0, 0.0, 0.0, 0.12 ), @@ -824,6 +826,7 @@ fn window_controls_from_raw }; Ok( WindowControlsSpec { + bar_bg: parse_opt( raw.bar_bg.as_deref(), fallback.bar_bg )?, icon: parse_opt( raw.icon.as_deref(), fallback.icon )?, hover_bg: parse_opt( raw.hover_bg.as_deref(), fallback.hover_bg )?, pressed_bg: parse_opt( raw.pressed_bg.as_deref(), fallback.pressed_bg )?, diff --git a/themes/default/theme.json b/themes/default/theme.json index 58279c6..1442a7c 100644 --- a/themes/default/theme.json +++ b/themes/default/theme.json @@ -21,6 +21,7 @@ "navy": "#0A032E", "indigo": "#221A5A", "midnight": "#181045", + "window-bar-dark": "#2B2838", "white": "#FFFFFF", "off-white": "#EAE9EB", "sky": "#2AA7F7", @@ -93,6 +94,7 @@ "light": { "launcher": { "background": "@white/E6", "border_radius": 24.0 }, "window_controls": { + "bar_bg": "@off-white", "icon": "@slate", "hover_bg": "@navy/14", "pressed_bg": "@navy/24", @@ -206,6 +208,7 @@ "dark": { "launcher": { "background": "@indigo/E6", "border_radius": 24.0 }, "window_controls": { + "bar_bg": "@window-bar-dark", "icon": "@silver", "hover_bg": "@white/18", "pressed_bg": "@white/26",