From bec307c314a2e81ab14e2156a6e2b7e9364f7780 Mon Sep 17 00:00:00 2001
From: Gabriel Fontes <hi@m7.rs>
Date: Mon, 9 Mar 2026 23:29:26 -0300
Subject: [PATCH 1/4] refactor: switch to async

Co-Authored-By: ilkecan <ilkecan@protonmail.com>
---
 Cargo.lock  | 217 ++++++++++++++--------------------------------------
 Cargo.toml  |  13 +++-
 src/main.rs |  38 +++++----
 3 files changed, 90 insertions(+), 178 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index e9835a6..8b56695 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -79,79 +79,6 @@ dependencies = [
  "pin-project-lite",
 ]

-[[package]]
-name = "async-channel"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
-dependencies = [
- "concurrent-queue",
- "event-listener-strategy",
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-executor"
-version = "1.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
-dependencies = [
- "async-task",
- "concurrent-queue",
- "fastrand",
- "futures-lite",
- "pin-project-lite",
- "slab",
-]
-
-[[package]]
-name = "async-io"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
-dependencies = [
- "autocfg",
- "cfg-if",
- "concurrent-queue",
- "futures-io",
- "futures-lite",
- "parking",
- "polling",
- "rustix",
- "slab",
- "windows-sys",
-]
-
-[[package]]
-name = "async-lock"
-version = "3.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
-dependencies = [
- "event-listener",
- "event-listener-strategy",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-process"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
-dependencies = [
- "async-channel",
- "async-io",
- "async-lock",
- "async-signal",
- "async-task",
- "blocking",
- "cfg-if",
- "event-listener",
- "futures-lite",
- "rustix",
-]
-
 [[package]]
 name = "async-recursion"
 version = "1.1.1"
@@ -163,30 +90,6 @@ dependencies = [
  "syn",
 ]

-[[package]]
-name = "async-signal"
-version = "0.2.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
-dependencies = [
- "async-io",
- "async-lock",
- "atomic-waker",
- "cfg-if",
- "futures-core",
- "futures-io",
- "rustix",
- "signal-hook-registry",
- "slab",
- "windows-sys",
-]
-
-[[package]]
-name = "async-task"
-version = "4.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
-
 [[package]]
 name = "async-trait"
 version = "0.1.89"
@@ -198,12 +101,6 @@ dependencies = [
  "syn",
 ]

-[[package]]
-name = "atomic-waker"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
-
 [[package]]
 name = "autocfg"
 version = "1.5.0"
@@ -216,7 +113,9 @@ version = "2.0.127"
 dependencies = [
  "clap",
  "env_logger",
+ "futures-lite",
  "log",
+ "tokio",
  "trycmd",
  "tzf-rs",
  "zbus",
@@ -240,19 +139,6 @@ version = "2.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"

-[[package]]
-name = "blocking"
-version = "1.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
-dependencies = [
- "async-channel",
- "async-task",
- "futures-io",
- "futures-lite",
- "piper",
-]
-
 [[package]]
 name = "bumpalo"
 version = "3.20.2"
@@ -556,12 +442,6 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"

-[[package]]
-name = "hermit-abi"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
-
 [[package]]
 name = "hex"
 version = "0.4.3"
@@ -684,6 +564,17 @@ dependencies = [
  "autocfg",
 ]

+[[package]]
+name = "mio"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys",
+]
+
 [[package]]
 name = "multimap"
 version = "0.10.1"
@@ -751,37 +642,12 @@ version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"

-[[package]]
-name = "piper"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
-dependencies = [
- "atomic-waker",
- "fastrand",
- "futures-io",
-]
-
 [[package]]
 name = "plain"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"

-[[package]]
-name = "polling"
-version = "3.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
-dependencies = [
- "cfg-if",
- "concurrent-queue",
- "hermit-abi",
- "pin-project-lite",
- "rustix",
- "windows-sys",
-]
-
 [[package]]
 name = "pqueue"
 version = "0.1.0"
@@ -1068,12 +934,6 @@ version = "2.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"

-[[package]]
-name = "slab"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
-
 [[package]]
 name = "snapbox"
 version = "1.2.0"
@@ -1105,6 +965,16 @@ dependencies = [
  "anstream",
 ]

+[[package]]
+name = "socket2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
 [[package]]
 name = "syn"
 version = "2.0.117"
@@ -1129,6 +999,34 @@ dependencies = [
  "windows-sys",
 ]

+[[package]]
+name = "tokio"
+version = "1.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "tracing",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "toml_datetime"
 version = "1.1.1+spec-1.1.0"
@@ -1296,6 +1194,12 @@ dependencies = [
  "winapi-util",
 ]

+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
 [[package]]
 name = "wasip2"
 version = "1.0.2+wasi-0.2.9"
@@ -1530,14 +1434,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc"
 dependencies = [
  "async-broadcast",
- "async-executor",
- "async-io",
- "async-lock",
- "async-process",
  "async-recursion",
- "async-task",
  "async-trait",
- "blocking",
  "enumflags2",
  "event-listener",
  "futures-core",
@@ -1548,6 +1446,7 @@ dependencies = [
  "rustix",
  "serde",
  "serde_repr",
+ "tokio",
  "tracing",
  "uds_windows",
  "uuid",
diff --git a/Cargo.toml b/Cargo.toml
index 715fb54..c198f43 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,16 @@ env_logger = { version = "=0.11.10", default-features = false }
 log = { version = "=0.4.29", default-features = false }
 tzf-rs = { version = "=1.2.0", default-features = false }
 zvariant = { version = "=5.10.0", default-features = false }
+futures-lite = { version = "=2.6.1", default-features = false }
+
+[dependencies.tokio]
+version = "=1.50.0"
+default-features = false
+features = [
+  "macros",
+  "rt",
+  "time"
+]

 [dependencies.clap]
 version = "=4.6.1"
@@ -29,8 +39,7 @@ features = [
 version = "=5.14.0"
 default-features = false
 features = [
-  "async-io",
-  "blocking-api",
+  "tokio"
 ]

 [dev-dependencies]
diff --git a/src/main.rs b/src/main.rs
index f6342c1..6ee35a5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,9 +3,10 @@
 use std::error::Error;

 use clap::Parser;
+use futures_lite::stream::StreamExt;
 use log::{debug, error, info};
 use tzf_rs::DefaultFinder;
-use zbus::{blocking::Connection, proxy};
+use zbus::{proxy, Connection};

 mod geoclue;

@@ -28,7 +29,8 @@ trait Timedate {
     fn set_timezone(&self, timezone: &str, interactive: bool) -> zbus::Result<()>;
 }

-fn main() -> Result<(), Box<dyn Error>> {
+#[tokio::main(flavor = "current_thread")]
+async fn main() -> Result<(), Box<dyn Error>> {
     let args = Args::parse();

     env_logger::Builder::new()
@@ -43,29 +45,31 @@ fn main() -> Result<(), Box<dyn Error>> {

     let zone_finder = DefaultFinder::new();

-    let conn = Connection::system()?;
+    let conn = Connection::system().await?;

-    let gclue_manager = geoclue::ManagerProxyBlocking::new(&conn)?;
-    let gclue_client = gclue_manager.get_client()?;
-    gclue_client.set_desktop_id("automatic-timezoned")?;
-    gclue_client.set_distance_threshold(10000)?; // meters
-    gclue_client.set_requested_accuracy_level(geoclue::AccuracyLevel::City as u32)?;
+    let gclue_manager = geoclue::ManagerProxy::new(&conn).await?;
+    let gclue_client = gclue_manager.get_client().await?;
+    gclue_client.set_desktop_id("automatic-timezoned").await?;
+    gclue_client.set_distance_threshold(10000).await?; // meters
+    gclue_client
+        .set_requested_accuracy_level(geoclue::AccuracyLevel::City as u32)
+        .await?;

-    let timedate = TimedateProxyBlocking::new(&conn)?;
+    let timedate = TimedateProxy::new(&conn).await?;

-    let location_updated = gclue_client.receive_location_updated()?;
+    let mut location_updated = gclue_client.receive_location_updated().await?;

-    gclue_client.start()?;
+    gclue_client.start().await?;

-    for signal in location_updated {
+    while let Some(signal) = location_updated.next().await {
         let args = signal.args()?;

-        let location = geoclue::LocationProxyBlocking::builder(&conn)
+        let location = geoclue::LocationProxy::builder(&conn)
             .path(args.new())?
-            .build()?;
+            .build().await?;

-        let latitude = location.latitude()?;
-        let longitude = location.longitude()?;
+        let latitude = location.latitude().await?;
+        let longitude = location.longitude().await?;

         debug!("Received location update. Latitude: {latitude} / Longitude: {longitude}");

@@ -76,7 +80,7 @@ fn main() -> Result<(), Box<dyn Error>> {
             continue;
         }

-        timedate.set_timezone(timezone, false)?;
+        timedate.set_timezone(timezone, false).await?;
         info!("Set timezone to \"{timezone}\"",);
     }

--
2.52.0


From af1dfe237b0540d4bbee37f9d85be7f010553598 Mon Sep 17 00:00:00 2001
From: Gabriel Fontes <hi@m7.rs>
Date: Mon, 9 Mar 2026 23:37:27 -0300
Subject: [PATCH 2/4] refactor: extract location handling to function

---
 src/main.rs | 51 +++++++++++++++++++++++++++++++--------------------
 1 file changed, 31 insertions(+), 20 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 6ee35a5..2594d1f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,6 +29,36 @@ trait Timedate {
     fn set_timezone(&self, timezone: &str, interactive: bool) -> zbus::Result<()>;
 }

+async fn handle_location(
+    signal: geoclue::LocationUpdated,
+    timedate: &TimedateProxy<'_>,
+    zone_finder: &DefaultFinder,
+    conn: &Connection,
+) -> Result<(), Box<dyn Error>> {
+    let args = signal.args()?;
+
+    let location = geoclue::LocationProxy::builder(&conn)
+        .path(args.new())?
+        .build()
+        .await?;
+
+    let latitude = location.latitude().await?;
+    let longitude = location.longitude().await?;
+
+    debug!("Received location update. Latitude: {latitude} / Longitude: {longitude}");
+
+    let timezone = zone_finder.get_tz_name(longitude, latitude);
+
+    if timezone.is_empty() {
+        error!("Failed to find a timezone. Latitude: {latitude} / Longitude: {longitude}");
+    } else {
+        timedate.set_timezone(timezone, false).await?;
+        info!("Set timezone to \"{timezone}\"",);
+    }
+
+    Ok(())
+}
+
 #[tokio::main(flavor = "current_thread")]
 async fn main() -> Result<(), Box<dyn Error>> {
     let args = Args::parse();
@@ -62,26 +92,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
     gclue_client.start().await?;

     while let Some(signal) = location_updated.next().await {
-        let args = signal.args()?;
-
-        let location = geoclue::LocationProxy::builder(&conn)
-            .path(args.new())?
-            .build().await?;
-
-        let latitude = location.latitude().await?;
-        let longitude = location.longitude().await?;
-
-        debug!("Received location update. Latitude: {latitude} / Longitude: {longitude}");
-
-        let timezone = zone_finder.get_tz_name(longitude, latitude);
-
-        if timezone.is_empty() {
-            error!("Failed to find a timezone. Latitude: {latitude} / Longitude: {longitude}");
-            continue;
-        }
-
-        timedate.set_timezone(timezone, false).await?;
-        info!("Set timezone to \"{timezone}\"",);
+        handle_location(signal, &timedate, &zone_finder, &conn).await?
     }

     Ok(())
--
2.52.0


From 0c9377bfcc96488d95468d8a8656dfbd1c41526b Mon Sep 17 00:00:00 2001
From: Gabriel Fontes <hi@m7.rs>
Date: Mon, 9 Mar 2026 23:45:56 -0300
Subject: [PATCH 3/4] refactor: use anyhow for error handling

---
 Cargo.lock  | 135 ++++++++++++++++++++++++++++++++--------------------
 Cargo.toml  |   1 +
 src/main.rs |   6 +--
 3 files changed, 88 insertions(+), 54 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 8b56695..b037a41 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -111,6 +111,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 name = "automatic-timezoned"
 version = "2.0.127"
 dependencies = [
+ "anyhow",
  "clap",
  "env_logger",
  "futures-lite",
@@ -135,9 +136,9 @@ dependencies = [

 [[package]]
 name = "bitflags"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"

 [[package]]
 name = "bumpalo"
@@ -341,9 +342,9 @@ dependencies = [

 [[package]]
 name = "fastrand"
-version = "2.3.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"

 [[package]]
 name = "filetime"
@@ -393,6 +394,24 @@ dependencies = [
  "pin-project-lite",
 ]

+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
 [[package]]
 name = "geometry-rs"
 version = "0.4.1"
@@ -432,9 +451,9 @@ dependencies = [

 [[package]]
 name = "hashbrown"
-version = "0.16.1"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"

 [[package]]
 name = "heck"
@@ -472,12 +491,12 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"

 [[package]]
 name = "indexmap"
-version = "2.13.0"
+version = "2.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
 dependencies = [
  "equivalent",
- "hashbrown 0.16.1",
+ "hashbrown 0.17.0",
  "serde",
  "serde_core",
 ]
@@ -505,10 +524,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"

 [[package]]
 name = "js-sys"
-version = "0.3.94"
+version = "0.3.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
+checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
 dependencies = [
+ "cfg-if",
+ "futures-util",
  "once_cell",
  "wasm-bindgen",
 ]
@@ -521,15 +542,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"

 [[package]]
 name = "libc"
-version = "0.2.183"
+version = "0.2.186"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"

 [[package]]
 name = "libredox"
-version = "0.1.15"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08"
+checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
 dependencies = [
  "bitflags",
  "libc",
@@ -750,9 +771,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"

 [[package]]
 name = "rayon"
-version = "1.11.0"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
 dependencies = [
  "either",
  "rayon-core",
@@ -770,9 +791,9 @@ dependencies = [

 [[package]]
 name = "redox_syscall"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
+checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
 dependencies = [
  "bitflags",
 ]
@@ -845,9 +866,9 @@ dependencies = [

 [[package]]
 name = "semver"
-version = "1.0.27"
+version = "1.0.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"

 [[package]]
 name = "serde"
@@ -935,10 +956,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"

 [[package]]
-name = "snapbox"
-version = "1.2.0"
+name = "slab"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9a4b69d5b4116a26b662c0bb38895261da3f3bfc171657d66a2a3c7a41e929"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "snapbox"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f92ac911648d788a6435401d9b4803959039d4de9919fdabdb415a8bebd027be"
 dependencies = [
  "anstream",
  "anstyle",
@@ -1038,9 +1065,9 @@ dependencies = [

 [[package]]
 name = "toml_edit"
-version = "0.25.9+spec-1.1.0"
+version = "0.25.11+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5"
+checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
 dependencies = [
  "indexmap",
  "serde_core",
@@ -1048,16 +1075,16 @@ dependencies = [
  "toml_datetime",
  "toml_parser",
  "toml_writer",
- "winnow 1.0.1",
+ "winnow 1.0.2",
 ]

 [[package]]
 name = "toml_parser"
-version = "1.1.1+spec-1.1.0"
+version = "1.1.2+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9"
+checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
 dependencies = [
- "winnow 1.0.1",
+ "winnow 1.0.2",
 ]

 [[package]]
@@ -1166,9 +1193,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"

 [[package]]
 name = "uuid"
-version = "1.23.0"
+version = "1.23.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
+checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
 dependencies = [
  "js-sys",
  "serde_core",
@@ -1202,11 +1229,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"

 [[package]]
 name = "wasip2"
-version = "1.0.2+wasi-0.2.9"
+version = "1.0.3+wasi-0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
 dependencies = [
- "wit-bindgen",
+ "wit-bindgen 0.57.1",
 ]

 [[package]]
@@ -1215,14 +1242,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
 dependencies = [
- "wit-bindgen",
+ "wit-bindgen 0.51.0",
 ]

 [[package]]
 name = "wasm-bindgen"
-version = "0.2.117"
+version = "0.2.120"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
+checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -1233,9 +1260,9 @@ dependencies = [

 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.117"
+version = "0.2.120"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
+checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -1243,9 +1270,9 @@ dependencies = [

 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.117"
+version = "0.2.120"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
+checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
 dependencies = [
  "bumpalo",
  "proc-macro2",
@@ -1256,9 +1283,9 @@ dependencies = [

 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.117"
+version = "0.2.120"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
+checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
 dependencies = [
  "unicode-ident",
 ]
@@ -1332,9 +1359,9 @@ dependencies = [

 [[package]]
 name = "winnow"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
+checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
 dependencies = [
  "memchr",
 ]
@@ -1348,6 +1375,12 @@ dependencies = [
  "wit-bindgen-rust-macro",
 ]

+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
 [[package]]
 name = "wit-bindgen-core"
 version = "0.51.0"
@@ -1505,9 +1538,9 @@ dependencies = [

 [[package]]
 name = "zvariant_derive"
-version = "5.10.0"
+version = "5.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c"
+checksum = "5b949b639ab1b4bed763aa7481ba0e368af68d8b55532f8ed4bec86a59f2ca98"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -1518,13 +1551,13 @@ dependencies = [

 [[package]]
 name = "zvariant_utils"
-version = "3.3.0"
+version = "3.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9"
+checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691"
 dependencies = [
  "proc-macro2",
  "quote",
  "serde",
  "syn",
- "winnow 0.7.15",
+ "winnow 1.0.2",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index c198f43..5af7411 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ license = "GPL-3.0"
 keywords = ["dbus", "geoclue", "systemd", "timezone", "zoneinfo"]

 [dependencies]
+anyhow = { version = "=1.0.102", default-features = false }
 env_logger = { version = "=0.11.10", default-features = false }
 log = { version = "=0.4.29", default-features = false }
 tzf-rs = { version = "=1.2.0", default-features = false }
diff --git a/src/main.rs b/src/main.rs
index 2594d1f..428f7f8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
 #![warn(clippy::all)]

-use std::error::Error;

+use anyhow::Result;
 use clap::Parser;
 use futures_lite::stream::StreamExt;
 use log::{debug, error, info};
@@ -34,7 +34,7 @@ async fn handle_location(
     timedate: &TimedateProxy<'_>,
     zone_finder: &DefaultFinder,
     conn: &Connection,
-) -> Result<(), Box<dyn Error>> {
+) -> Result<()> {
     let args = signal.args()?;

     let location = geoclue::LocationProxy::builder(&conn)
@@ -60,7 +60,7 @@ async fn handle_location(
 }

 #[tokio::main(flavor = "current_thread")]
-async fn main() -> Result<(), Box<dyn Error>> {
+async fn main() -> Result<()> {
     let args = Args::parse();

     env_logger::Builder::new()
--
2.52.0


From 88de21b14c6995f1fd1992876382151aa6e73aa4 Mon Sep 17 00:00:00 2001
From: Gabriel Fontes <hi@m7.rs>
Date: Mon, 9 Mar 2026 23:45:56 -0300
Subject: [PATCH 4/4] feature: add timeout for geoclue connection and first
 location update

---
 README.md         |  7 ++++---
 src/main.rs       | 22 ++++++++++++++++++++--
 tests/cmd/help.md | 14 ++++++++------
 3 files changed, 32 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index 11ce166..d8dc0fc 100644
--- a/README.md
+++ b/README.md
@@ -34,9 +34,10 @@ Automatically update system timezone based on location
 Usage: automatic-timezoned [OPTIONS]

 Options:
-  -l, --log-level <LOG_LEVEL>  Log level filter. See <https://docs.rs/env_logger> for syntax [env: AUTOTZD_LOG_LEVEL=] [default: info]
-  -h, --help                   Print help
-  -V, --version                Print version
+  -l, --log-level <LOG_LEVEL>        Log level filter. See <https://docs.rs/env_logger> for syntax [env: AUTOTZD_LOG_LEVEL=] [default: info]
+  -t, --timeout-secs <TIMEOUT_SECS>  Timeout in seconds for connections [env: AUTOTZD_TIMEOUT_SECS=] [default: 30]
+  -h, --help                         Print help
+  -V, --version                      Print version

 ```

diff --git a/src/main.rs b/src/main.rs
index 428f7f8..cd38063 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,10 +1,12 @@
 #![warn(clippy::all)]

+use std::time::Duration;

-use anyhow::Result;
+use anyhow::{Context, Result};
 use clap::Parser;
 use futures_lite::stream::StreamExt;
 use log::{debug, error, info};
+use tokio::time::timeout;
 use tzf_rs::DefaultFinder;
 use zbus::{proxy, Connection};

@@ -17,6 +19,9 @@ struct Args {
     /// Log level filter. See <https://docs.rs/env_logger> for syntax
     #[arg(short, long, default_value = "info", env = "AUTOTZD_LOG_LEVEL")]
     log_level: String,
+    /// Timeout in seconds for connections.
+    #[arg(short, long, default_value = "30", env = "AUTOTZD_TIMEOUT_SECS")]
+    timeout_secs: u16,
 }

 #[proxy(
@@ -67,6 +72,8 @@ async fn main() -> Result<()> {
         .parse_filters(&args.log_level)
         .init();

+    let timeout_s = Duration::from_secs(args.timeout_secs.into());
+
     info!(
         "Starting {} {}...",
         env!("CARGO_PKG_NAME"),
@@ -78,7 +85,9 @@ async fn main() -> Result<()> {
     let conn = Connection::system().await?;

     let gclue_manager = geoclue::ManagerProxy::new(&conn).await?;
-    let gclue_client = gclue_manager.get_client().await?;
+    let gclue_client = timeout(timeout_s, gclue_manager.get_client())
+        .await
+        .context("Failed to connect to geoclue")??;
     gclue_client.set_desktop_id("automatic-timezoned").await?;
     gclue_client.set_distance_threshold(10000).await?; // meters
     gclue_client
@@ -91,6 +100,15 @@ async fn main() -> Result<()> {

     gclue_client.start().await?;

+    // Wait for first update with timeout set, to ensure it works
+    if let Some(signal) = timeout(timeout_s, location_updated.next())
+        .await
+        .context("Failed to get initial location")?
+    {
+        handle_location(signal, &timedate, &zone_finder, &conn).await?
+    }
+
+    // Wait for future updates
     while let Some(signal) = location_updated.next().await {
         handle_location(signal, &timedate, &zone_finder, &conn).await?
     }
diff --git a/tests/cmd/help.md b/tests/cmd/help.md
index f8cfb02..f39628c 100644
--- a/tests/cmd/help.md
+++ b/tests/cmd/help.md
@@ -9,9 +9,10 @@ Automatically update system timezone based on location
 Usage: automatic-timezoned [OPTIONS]

 Options:
-  -l, --log-level <LOG_LEVEL>  Log level filter. See <https://docs.rs/env_logger> for syntax [env: AUTOTZD_LOG_LEVEL=] [default: info]
-  -h, --help                   Print help
-  -V, --version                Print version
+  -l, --log-level <LOG_LEVEL>        Log level filter. See <https://docs.rs/env_logger> for syntax [env: AUTOTZD_LOG_LEVEL=] [default: info]
+  -t, --timeout-secs <TIMEOUT_SECS>  Timeout in seconds for connections [env: AUTOTZD_TIMEOUT_SECS=] [default: 30]
+  -h, --help                         Print help
+  -V, --version                      Print version

 ```

@@ -24,8 +25,9 @@ Automatically update system timezone based on location
 Usage: automatic-timezoned [OPTIONS]

 Options:
-  -l, --log-level <LOG_LEVEL>  Log level filter. See <https://docs.rs/env_logger> for syntax [env: AUTOTZD_LOG_LEVEL=] [default: info]
-  -h, --help                   Print help
-  -V, --version                Print version
+  -l, --log-level <LOG_LEVEL>        Log level filter. See <https://docs.rs/env_logger> for syntax [env: AUTOTZD_LOG_LEVEL=] [default: info]
+  -t, --timeout-secs <TIMEOUT_SECS>  Timeout in seconds for connections [env: AUTOTZD_TIMEOUT_SECS=] [default: 30]
+  -h, --help                         Print help
+  -V, --version                      Print version

 ```
--
2.52.0

