improv(sctk): setup error handling

This commit is contained in:
Ashley Wulber 2025-06-12 10:42:59 -04:00
parent 9668aeba1f
commit e4f8120eed
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820

View file

@ -50,6 +50,7 @@ use std::{
}; };
use tracing::error; use tracing::error;
use wayland_backend::client::Backend; use wayland_backend::client::Backend;
use wayland_client::globals::GlobalError;
use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle}; use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle};
use self::state::SctkState; use self::state::SctkState;
@ -65,6 +66,14 @@ pub struct SctkEventLoop {
pub(crate) state: SctkState, pub(crate) state: SctkState,
} }
pub enum Error {
Connect(ConnectError),
Calloop(calloop::Error),
Global(GlobalError),
NoDisplayHandle,
NoWaylandDisplay,
}
impl SctkEventLoop { impl SctkEventLoop {
pub(crate) fn new( pub(crate) fn new(
winit_event_sender: mpsc::UnboundedSender<Control>, winit_event_sender: mpsc::UnboundedSender<Control>,
@ -76,12 +85,13 @@ impl SctkEventLoop {
> { > {
let Ok(dh) = display.display_handle() else { let Ok(dh) = display.display_handle() else {
log::error!("Failed to get display handle"); log::error!("Failed to get display handle");
return Err(Box::new(())); return Err(Box::new(Error::NoDisplayHandle));
}; };
let raw_window_handle::RawDisplayHandle::Wayland(wayland_dh) = let raw_window_handle::RawDisplayHandle::Wayland(wayland_dh) =
dh.as_raw() dh.as_raw()
else { else {
panic!("Invalid wayland display handle."); log::error!("Display handle is not Wayland");
return Err(Box::new(Error::NoWaylandDisplay));
}; };
let backend = unsafe { let backend = unsafe {
@ -90,18 +100,19 @@ impl SctkEventLoop {
let connection = Connection::from_backend(backend); let connection = Connection::from_backend(backend);
let (action_tx, action_rx) = calloop::channel::channel(); let (action_tx, action_rx) = calloop::channel::channel();
let res = std::thread::spawn(move || { let res: std::thread::JoinHandle<Result<(), Error>> =
let _display = connection.display(); std::thread::spawn(move || {
let (globals, event_queue) = let _display = connection.display();
registry_queue_init(&connection).unwrap(); let (globals, event_queue) =
let event_loop = registry_queue_init(&connection).map_err(Error::Global)?;
calloop::EventLoop::<SctkState>::try_new().unwrap(); let event_loop = calloop::EventLoop::<SctkState>::try_new()
let loop_handle = event_loop.handle(); .map_err(Error::Calloop)?;
let loop_handle = event_loop.handle();
let qh = event_queue.handle(); let qh = event_queue.handle();
let registry_state = RegistryState::new(&globals); let registry_state = RegistryState::new(&globals);
_ = loop_handle _ = loop_handle
.insert_source(action_rx, |event, _, state| { .insert_source(action_rx, |event, _, state| {
match event { match event {
calloop::channel::Event::Msg(e) => match e { calloop::channel::Event::Msg(e) => match e {
@ -231,249 +242,265 @@ impl SctkEventLoop {
} }
}) })
.unwrap(); .unwrap();
let wayland_source = let wayland_source =
WaylandSource::new(connection.clone(), event_queue); WaylandSource::new(connection.clone(), event_queue);
let wayland_dispatcher = calloop::Dispatcher::new( let wayland_dispatcher = calloop::Dispatcher::new(
wayland_source, wayland_source,
|_, queue, winit_state| queue.dispatch_pending(winit_state), |_, queue, winit_state| queue.dispatch_pending(winit_state),
);
let _wayland_source_dispatcher = event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())
.unwrap();
let (viewporter_state, fractional_scaling_manager) =
match FractionalScalingManager::new(&globals, &qh) {
Ok(m) => {
let viewporter_state: Option<ViewporterState> =
match ViewporterState::new(&globals, &qh) {
Ok(s) => Some(s),
Err(e) => {
error!(
"Failed to initialize viewporter: {}",
e
);
None
}
};
(viewporter_state, Some(m))
}
Err(e) => {
error!(
"Failed to initialize fractional scaling manager: {}",
e
);
(None, None)
}
};
let mut state = Self {
event_loop,
state: SctkState {
connection,
seat_state: SeatState::new(&globals, &qh),
output_state: OutputState::new(&globals, &qh),
compositor_state: CompositorState::bind(&globals, &qh)
.expect("wl_compositor is not available"),
shm_state: Shm::bind(&globals, &qh)
.expect("wl_shm is not available"),
xdg_shell_state: XdgShell::bind(&globals, &qh)
.expect("xdg shell is not available"),
layer_shell: LayerShell::bind(&globals, &qh).ok(),
activation_state: ActivationState::bind(&globals, &qh).ok(),
session_lock_state: SessionLockState::new(&globals, &qh),
session_lock: None,
overlap_notify: OverlapNotifyV1::bind(&globals, &qh).ok(),
toplevel_info: ToplevelInfoState::try_new(
&registry_state,
&qh,
),
toplevel_manager: ToplevelManagerState::try_new(
&registry_state,
&qh,
),
registry_state,
queue_handle: qh,
loop_handle,
_cursor_surface: None,
_multipool: None,
outputs: Vec::new(),
seats: Vec::new(),
windows: Vec::new(),
layer_surfaces: Vec::new(),
popups: Vec::new(),
lock_surfaces: Vec::new(),
subsurfaces: Vec::new(),
_kbd_focus: None,
touch_points: HashMap::new(),
sctk_events: Vec::new(),
frame_status: HashMap::new(),
fractional_scaling_manager,
viewporter_state,
compositor_updates: Default::default(),
events_sender: winit_event_sender,
proxy,
id_map: Default::default(),
to_commit: HashMap::new(),
destroyed: HashSet::new(),
pending_popup: Default::default(),
activation_token_ctr: 0,
token_senders: HashMap::new(),
overlap_notifications: HashMap::new(),
subsurface_state: None,
},
_features: Default::default(),
};
let wl_compositor = state
.state
.registry_state
.bind_one(&state.state.queue_handle, 1..=6, GlobalData)
.unwrap();
let wl_subcompositor = state.state.registry_state.bind_one(
&state.state.queue_handle,
1..=1,
GlobalData,
);
let wp_viewporter = state.state.registry_state.bind_one(
&state.state.queue_handle,
1..=1,
GlobalData,
);
let wl_shm = state
.state
.registry_state
.bind_one(&state.state.queue_handle, 1..=1, GlobalData)
.unwrap();
let wp_dmabuf = state
.state
.registry_state
.bind_one(&state.state.queue_handle, 2..=4, GlobalData)
.ok();
let wp_alpha_modifier = state
.state
.registry_state
.bind_one(&state.state.queue_handle, 1..=1, ())
.ok();
if let (Ok(wl_subcompositor), Ok(wp_viewporter)) =
(wl_subcompositor, wp_viewporter)
{
let subsurface_state = SubsurfaceState {
wl_compositor,
wl_subcompositor,
wp_viewporter,
wl_shm,
wp_dmabuf,
wp_alpha_modifier,
qh: state.state.queue_handle.clone(),
buffers: HashMap::new(),
unmapped_subsurfaces: Vec::new(),
new_iced_subsurfaces: Vec::new(),
};
state.state.subsurface_state = Some(subsurface_state.clone());
state::send_event(
&state.state.events_sender,
&state.state.proxy,
SctkEvent::Subcompositor(subsurface_state),
); );
} else {
log::warn!("Subsurfaces not supported.")
}
log::info!("SCTK setup complete."); let _wayland_source_dispatcher = event_loop
loop { .handle()
match state .register_dispatcher(wayland_dispatcher.clone())
.unwrap();
let (viewporter_state, fractional_scaling_manager) =
match FractionalScalingManager::new(&globals, &qh) {
Ok(m) => {
let viewporter_state: Option<ViewporterState> =
match ViewporterState::new(&globals, &qh) {
Ok(s) => Some(s),
Err(e) => {
error!(
"Failed to initialize viewporter: {}",
e
);
None
}
};
(viewporter_state, Some(m))
}
Err(e) => {
error!(
"Failed to initialize fractional scaling manager: {}",
e
);
(None, None)
}
};
let mut state = Self {
event_loop,
state: SctkState {
connection,
seat_state: SeatState::new(&globals, &qh),
output_state: OutputState::new(&globals, &qh),
compositor_state: CompositorState::bind(&globals, &qh)
.expect("wl_compositor is not available"),
shm_state: Shm::bind(&globals, &qh)
.expect("wl_shm is not available"),
xdg_shell_state: XdgShell::bind(&globals, &qh)
.expect("xdg shell is not available"),
layer_shell: LayerShell::bind(&globals, &qh).ok(),
activation_state: ActivationState::bind(&globals, &qh)
.ok(),
session_lock_state: SessionLockState::new(
&globals, &qh,
),
session_lock: None,
overlap_notify: OverlapNotifyV1::bind(&globals, &qh)
.ok(),
toplevel_info: ToplevelInfoState::try_new(
&registry_state,
&qh,
),
toplevel_manager: ToplevelManagerState::try_new(
&registry_state,
&qh,
),
registry_state,
queue_handle: qh,
loop_handle,
_cursor_surface: None,
_multipool: None,
outputs: Vec::new(),
seats: Vec::new(),
windows: Vec::new(),
layer_surfaces: Vec::new(),
popups: Vec::new(),
lock_surfaces: Vec::new(),
subsurfaces: Vec::new(),
_kbd_focus: None,
touch_points: HashMap::new(),
sctk_events: Vec::new(),
frame_status: HashMap::new(),
fractional_scaling_manager,
viewporter_state,
compositor_updates: Default::default(),
events_sender: winit_event_sender,
proxy,
id_map: Default::default(),
to_commit: HashMap::new(),
destroyed: HashSet::new(),
pending_popup: Default::default(),
activation_token_ctr: 0,
token_senders: HashMap::new(),
overlap_notifications: HashMap::new(),
subsurface_state: None,
},
_features: Default::default(),
};
let wl_compositor = state
.state .state
.events_sender .registry_state
.unbounded_send(Control::AboutToWait) .bind_one(&state.state.queue_handle, 1..=6, GlobalData)
.unwrap();
let wl_subcompositor = state.state.registry_state.bind_one(
&state.state.queue_handle,
1..=1,
GlobalData,
);
let wp_viewporter = state.state.registry_state.bind_one(
&state.state.queue_handle,
1..=1,
GlobalData,
);
let wl_shm = state
.state
.registry_state
.bind_one(&state.state.queue_handle, 1..=1, GlobalData)
.unwrap();
let wp_dmabuf = state
.state
.registry_state
.bind_one(&state.state.queue_handle, 2..=4, GlobalData)
.ok();
let wp_alpha_modifier = state
.state
.registry_state
.bind_one(&state.state.queue_handle, 1..=1, ())
.ok();
if let (Ok(wl_subcompositor), Ok(wp_viewporter)) =
(wl_subcompositor, wp_viewporter)
{ {
Ok(_) => {} let subsurface_state = SubsurfaceState {
Err(err) => { wl_compositor,
log::error!( wl_subcompositor,
"SCTK failed to send Control::AboutToWait. {err:?}" wp_viewporter,
); wl_shm,
if state.state.events_sender.is_closed() { wp_dmabuf,
return Ok(()); wp_alpha_modifier,
qh: state.state.queue_handle.clone(),
buffers: HashMap::new(),
unmapped_subsurfaces: Vec::new(),
new_iced_subsurfaces: Vec::new(),
};
state.state.subsurface_state =
Some(subsurface_state.clone());
state::send_event(
&state.state.events_sender,
&state.state.proxy,
SctkEvent::Subcompositor(subsurface_state),
);
} else {
log::warn!("Subsurfaces not supported.")
}
log::info!("SCTK setup complete.");
loop {
match state
.state
.events_sender
.unbounded_send(Control::AboutToWait)
{
Ok(_) => {}
Err(err) => {
log::error!(
"SCTK failed to send Control::AboutToWait. {err:?}"
);
if state.state.events_sender.is_closed() {
return Ok(());
}
} }
} }
}
if let Err(err) = if let Err(err) =
state.event_loop.dispatch(None, &mut state.state) state.event_loop.dispatch(None, &mut state.state)
{ {
log::error!("SCTK dispatch error: {err}"); log::error!("SCTK dispatch error: {err}");
} }
let had_events = !state.state.sctk_events.is_empty(); let had_events = !state.state.sctk_events.is_empty();
let mut wake_up = had_events; let mut wake_up = had_events;
for s in state for s in
.state
.layer_surfaces
.iter()
.map(|s| s.surface.wl_surface())
.chain(
state.state.popups.iter().map(|s| s.popup.wl_surface()),
)
.chain(
state state
.state .state
.lock_surfaces .layer_surfaces
.iter() .iter()
.map(|s| s.session_lock_surface.wl_surface()), .map(|s| s.surface.wl_surface())
) .chain(
{ state
let id = s.id(); .state
if state .popups
.state .iter()
.frame_status .map(|s| s.popup.wl_surface()),
.get(&id) )
.map(|v| !matches!(v, state::FrameStatus::Ready)) .chain(
.unwrap_or(true) state.state.lock_surfaces.iter().map(|s| {
|| !state.state.id_map.contains_key(&id) s.session_lock_surface.wl_surface()
}),
)
{ {
continue; let id = s.id();
} if state
wake_up = true;
_ = s.frame(&state.state.queue_handle, s.clone());
_ = state.state.frame_status.remove(&id);
_ = state.state.events_sender.unbounded_send(
Control::Winit(
winit::window::WindowId::from_raw(
id.as_ptr() as usize
),
winit::event::WindowEvent::RedrawRequested,
),
);
}
for e in state.state.sctk_events.drain(..) {
if let SctkEvent::Winit(id, e) = e {
_ = state
.state .state
.events_sender .frame_status
.unbounded_send(Control::Winit(id, e)); .get(&id)
} else { .map(|v| !matches!(v, state::FrameStatus::Ready))
.unwrap_or(true)
|| !state.state.id_map.contains_key(&id)
{
continue;
}
wake_up = true;
_ = s.frame(&state.state.queue_handle, s.clone());
_ = state.state.frame_status.remove(&id);
_ = state.state.events_sender.unbounded_send( _ = state.state.events_sender.unbounded_send(
Control::PlatformSpecific( Control::Winit(
crate::platform_specific::Event::Wayland(e), winit::window::WindowId::from_raw(
id.as_ptr() as usize
),
winit::event::WindowEvent::RedrawRequested,
), ),
); );
} }
for e in state.state.sctk_events.drain(..) {
if let SctkEvent::Winit(id, e) = e {
_ = state
.state
.events_sender
.unbounded_send(Control::Winit(id, e));
} else {
_ =
state
.state
.events_sender
.unbounded_send(Control::PlatformSpecific(
crate::platform_specific::Event::Wayland(e),
));
}
}
if wake_up {
state.state.proxy.wake_up();
}
} }
if wake_up { });
state.state.proxy.wake_up();
}
}
});
if res.is_finished() { if res.is_finished() {
log::warn!("SCTK thread finished."); log::warn!("SCTK thread finished.");
res.join().map(|_: Result<(), ConnectError>| action_tx) match res.join() {
Ok(_) => Ok(action_tx),
Err(e) => {
log::error!("SCTK thread exited with error: {e:?}");
return Err(e);
}
}
} else { } else {
Ok(action_tx) Ok(action_tx)
} }