authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-01 12:37:40-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-01 16:46:23-07:00
logdd5848fb96853a2c144af246475587ad09f1aefa
tree1dff7a08ba3bde39bd1a66692ba519b12ff96f8c
parent0108e94dff934720a1cd09e3931f16b9a710936c
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: quitting on Wayland no longer segfaults

The window state outlived winit's event loop, which disconnects the Wayland display when run_app returns. Dropping the state afterwards made the Wayland clipboard's worker thread destroy its proxies on the freed display (wl_proxy_destroy in smithay-clipboard), so every quit on GNOME and KDE Wayland ended in SIGSEGV. The state now drops in exiting(), while the connection is still open. Reproduced and verified on Fedora Silverblue 44 (GNOME 50) and Kinoite 44 (Plasma 6.6), x86_64. Fixes #54. Assisted-by: claude-opus-5.5

1 files changed, 14 insertions(+), 6 deletions(-)

crates/snowbound/src/main.rs+14-6
...@@ -342,6 +342,8 @@ struct App {...@@ -342,6 +342,8 @@ struct App {
342 /// Files and folders to open, as File, Open does, once the window opens.342 /// Files and folders to open, as File, Open does, once the window opens.
343 opening: Vec<PathBuf>,343 opening: Vec<PathBuf>,
344 state: Option<State>,344 state: Option<State>,
345 /// The staged update to swap in once the app has quit.
346 restart: Option<PathBuf>,
345 startup_error: Option<Box<dyn Error>>,347 startup_error: Option<Box<dyn Error>>,
346}348}
347349
...@@ -5792,6 +5794,15 @@ impl ApplicationHandler<UserEvent> for App {...@@ -5792,6 +5794,15 @@ impl ApplicationHandler<UserEvent> for App {
5792 .min();5794 .min();
5793 event_loop.set_control_flow(next.map_or(ControlFlow::Wait, ControlFlow::WaitUntil));5795 event_loop.set_control_flow(next.map_or(ControlFlow::Wait, ControlFlow::WaitUntil));
5794 }5796 }
5797
5798 /// Drops the window's state while the event loop still holds the Wayland connection the
5799 /// clipboard's thread and the surface use; `run_app` closes it on returning.
5800 fn exiting(&mut self, _: &ActiveEventLoop) {
5801 self.restart = self
5802 .state
5803 .take()
5804 .and_then(|state| state.updates.restarting());
5805 }
5795}5806}
57965807
5797fn write_png(path: &Path, size: [u32; 2], pixels: &[u8]) -> Result<(), Box<dyn Error>> {5808fn write_png(path: &Path, size: [u32; 2], pixels: &[u8]) -> Result<(), Box<dyn Error>> {
...@@ -6094,15 +6105,12 @@ fn launch() -> Result<(), Box<dyn Error>> {...@@ -6094,15 +6105,12 @@ fn launch() -> Result<(), Box<dyn Error>> {
6094 substitutes,6105 substitutes,
6095 screenshot,6106 screenshot,
6096 state: None,6107 state: None,
6108 restart: None,
6097 startup_error: None,6109 startup_error: None,
6098 };6110 };
6099 event_loop.run_app(&mut app)?;6111 event_loop.run_app(&mut app)?;
6100 let restart = app6112 if let Some(staged) = &app.restart {
6101 .state6113 update::relaunch(staged)?;
6102 .as_ref()
6103 .and_then(|state| state.updates.restarting());
6104 if let Some(staged) = restart {
6105 update::relaunch(&staged)?;
6106 }6114 }
6107 app.startup_error.map_or(Ok(()), Err)6115 app.startup_error.map_or(Ok(()), Err)
6108}6116}