diff --git a/gui/images/arrow-down-up.svg b/gui/images/arrow-down-up.svg
new file mode 100644
index 0000000..2996226
--- /dev/null
+++ b/gui/images/arrow-down-up.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/gui/src/io.rs b/gui/src/io.rs
new file mode 100644
index 0000000..363235c
--- /dev/null
+++ b/gui/src/io.rs
@@ -0,0 +1,67 @@
+use std::sync::Arc;
+
+use slint::ComponentHandle;
+use tokio::sync::mpsc;
+
+use crate::{runtime::spawn, ui};
+
+/// A thing for spawning I/O-tasks and updating the GUI `ConnectionStatus` with the result.
+#[derive(Clone)]
+pub struct Io {
+ state: Arc,
+}
+
+struct State {
+ status: mpsc::Sender,
+}
+
+impl Io {
+ pub fn new(app: &ui::App) -> Self {
+ let (tx, mut rx) = mpsc::channel(10);
+ let app_weak = app.as_weak();
+
+ spawn(async move {
+ loop {
+ let Some(status) = rx.recv().await else {
+ return;
+ };
+
+ let _ = app_weak.upgrade_in_event_loop(move |app| {
+ app.global::().set_connection_status(status);
+ });
+ }
+ });
+
+ Io {
+ state: Arc::new(State { status: tx }),
+ }
+ }
+
+ /// Spawn a fallible async function.
+ ///
+ /// Update the `ConnectionStatus` in the GUI depending on whether the result is an error.
+ pub fn spawn(&self, f: impl FnOnce() -> Fut + Send + 'static)
+ where
+ Fut: Future