From c3839060ccfce4a3a50b492eaaab99aeb158c410 Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Wed, 13 May 2026 10:17:36 +0200 Subject: [PATCH] =?UTF-8?q?theme,=20event=5Floop:=20window=5Fcontrols.bar?= =?UTF-8?q?=5Fbg=20slot=20+=20graceful=20exit=20on=20compositor=20disconne?= =?UTF-8?q?ct=20`WindowControlsSpec`=20gains=20`bar=5Fbg`,=20the=20backgro?= =?UTF-8?q?und=20fill=20of=20the=20SSD=20title-bar=20strip=20the=20close?= =?UTF-8?q?=20/=20maximize=20/=20minimize=20controls=20sit=20on.=20Forge?= =?UTF-8?q?=20used=20to=20paint=20that=20strip=20with=20`palette.surface`?= =?UTF-8?q?=20clamped=20to=20alpha=201.0=20=E2=80=94=20a=20hack=20on=20a?= =?UTF-8?q?=20translucent=20panel=20token.=20With=20a=20dedicated=20slot?= =?UTF-8?q?=20the=20theme=20decides=20directly:=20`@off-white`=20in=20ligh?= =?UTF-8?q?t=20and=20a=20new=20`@window-bar-dark`=20shade=20in=20dark.=20S?= =?UTF-8?q?chema=20gets=20the=20matching=20`Option`=20field=20(def?= =?UTF-8?q?aulting=20to=20`palette.surface`=20to=20keep=20existing=20theme?= =?UTF-8?q?s=20rendering=20the=20same)=20and=20the=20fallback=20`WindowCon?= =?UTF-8?q?trolsSpec`=20seeds=20`Color::WHITE`=20so=20a=20missing=20theme?= =?UTF-8?q?=20still=20draws=20something=20readable.=20`try=5Frun`'s=20disp?= =?UTF-8?q?atch=20loop=20previously=20`.expect("dispatch")`-ed=20every=20c?= =?UTF-8?q?alloop=20error.=20When=20the=20compositor=20closes=20the=20wayl?= =?UTF-8?q?and=20socket=20=E2=80=94=20`wl=5Fdisplay.error`,=20forge=20cras?= =?UTF-8?q?hing,=20the=20user=20logging=20out=20=E2=80=94=20the=20loop=20s?= =?UTF-8?q?aw=20a=20`BrokenPipe`=20/=20`ConnectionReset`=20and=20panicked,?= =?UTF-8?q?=20which=20polluted=20the=20user's=20stderr=20with=20a=20backtr?= =?UTF-8?q?ace=20for=20what=20is=20just=20"the=20session=20ended".=20The?= =?UTF-8?q?=20match=20now=20treats=20those=20two=20`IoError`=20kinds=20as?= =?UTF-8?q?=20a=20clean=20shutdown:=20it=20prints=20`Connection::protocol?= =?UTF-8?q?=5Ferror()`=20(the=20typed=20`wl=5Fdisplay.error`=20if=20the=20?= =?UTF-8?q?server=20sent=20one),=20sets=20`exit=5Frequested=20=3D=20true`,?= =?UTF-8?q?=20and=20lets=20the=20loop=20drain=20out=20normally.=20Any=20ot?= =?UTF-8?q?her=20dispatch=20error=20keeps=20panicking=20=E2=80=94=20those?= =?UTF-8?q?=20are=20still=20genuine=20bugs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/event_loop/mod.rs | 34 ++++++++++++++++++++++++++++++---- src/theme/mod.rs | 3 +++ src/theme/schema.rs | 3 +++ themes/default/theme.json | 3 +++ 4 files changed, 39 insertions(+), 4 deletions(-) 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",