Skip to content

Commit dd61e61

Browse files
committed
Decode logind session paths by reply signature
1 parent 5b9a50a commit dd61e61

File tree

4 files changed

+96
-16
lines changed

4 files changed

+96
-16
lines changed

llm-docs/LLM-TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ The project daemon is located at `src/daemon/` (Rust).
3939
# Notes
4040
- 2026-01-18: logind session monitoring failure is non-fatal; daemon continues without native terminal switching.
4141
- 2026-01-18: logind session resolution now falls back to the user’s `Display` session when `GetSessionByPID` reports no session (systemd user service with lingering).
42-
- 2026-01-19: logind object path parsing accepts object paths, single-field structures, or strings (robust reply decoding).
42+
- 2026-01-19: logind object path parsing accepts signatures `o`, `s`, `v`, or single-field structures (robust reply decoding).

llm-docs/implementation-notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ When all windows are closed (no window focused), the daemon switches to the defa
115115
The daemon watches `org.freedesktop.login1.Session.Active` on the system bus. When the session becomes inactive (Ctrl+Alt+F*), it applies the `on_native_terminal` rule if present, otherwise it behaves like an unfocused state. When the session becomes active again, it refreshes focus by querying the backend (GNOME GetFocus DBus, KDE script callback, Wayland/X11 active-window query).
116116

117117
Session resolution prefers `XDG_SESSION_ID`, otherwise `GetSessionByPID`. If the PID is not in a logind session (common for systemd user services with lingering), it falls back to the user’s `Display` session via `GetUserByPID` + `org.freedesktop.login1.User.Display`.
118-
Logind replies are decoded via `OwnedValue` to tolerate object paths returned as a direct value, a single-field structure, or a string.
118+
Logind replies are decoded by inspecting the reply signature (accepting `o`, `s`, `v`, or single-field structures) to tolerate object paths returned as a direct value, a single-field structure, or a string.
119119

120120
If logind monitoring fails to start (no system bus, permissions, etc.), the daemon logs the error and continues without native terminal switching.
121121

src/daemon/main.rs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,22 +2058,17 @@ async fn resolve_logind_session_path(
20582058

20592059
if let Ok(session_id) = env::var("XDG_SESSION_ID") {
20602060
println!("[Logind] Using XDG_SESSION_ID={}", session_id);
2061-
let path = parse_logind_object_path(
2062-
manager.call::<_, _, OwnedValue>("GetSession", &(session_id)).await?,
2063-
"GetSession",
2064-
)?;
2061+
let reply = manager.call_method("GetSession", &(session_id)).await?;
2062+
let path = decode_logind_object_path_reply(&reply, "GetSession")?;
20652063
println!("[Logind] Using session path: {}", path.as_str());
20662064
return Ok(path);
20672065
}
20682066
println!("[Logind] XDG_SESSION_ID not set; resolving session via logind");
20692067

20702068
let pid = std::process::id();
2071-
match manager
2072-
.call::<_, _, OwnedValue>("GetSessionByPID", &(pid))
2073-
.await
2074-
{
2075-
Ok(value) => {
2076-
let path = parse_logind_object_path(value, "GetSessionByPID")?;
2069+
match manager.call_method("GetSessionByPID", &(pid)).await {
2070+
Ok(reply) => {
2071+
let path = decode_logind_object_path_reply(&reply, "GetSessionByPID")?;
20772072
println!("[Logind] Using session path: {}", path.as_str());
20782073
Ok(path)
20792074
}
@@ -2134,6 +2129,48 @@ fn parse_logind_object_path_from_structure(structure: &Structure<'_>) -> Option<
21342129
logind_object_path_from_value(&fields[0])
21352130
}
21362131

2132+
fn decode_logind_object_path_reply(
2133+
reply: &zbus::Message,
2134+
context: &str,
2135+
) -> Result<OwnedObjectPath, Box<dyn std::error::Error + Send + Sync>> {
2136+
let body = reply.body();
2137+
let signature = body.signature().to_string();
2138+
match signature.as_str() {
2139+
"o" => Ok(body.deserialize_unchecked::<OwnedObjectPath>()?),
2140+
"s" => {
2141+
let text = body.deserialize_unchecked::<String>()?;
2142+
OwnedObjectPath::try_from(text).map_err(|error| {
2143+
format!(
2144+
"logind {} returned invalid object path string: {}",
2145+
context, error
2146+
)
2147+
.into()
2148+
})
2149+
}
2150+
"v" => {
2151+
let value = body.deserialize::<OwnedValue>()?;
2152+
parse_logind_object_path(value, context)
2153+
}
2154+
_ => {
2155+
if signature.starts_with('(') {
2156+
let structure = body.deserialize::<Structure>()?;
2157+
return parse_logind_object_path_from_structure(&structure).ok_or_else(|| {
2158+
format!(
2159+
"logind {} returned unexpected structure: {}",
2160+
context, signature
2161+
)
2162+
.into()
2163+
});
2164+
}
2165+
Err(format!(
2166+
"logind {} returned unexpected signature: {}",
2167+
context, signature
2168+
)
2169+
.into())
2170+
}
2171+
}
2172+
}
2173+
21372174
fn logind_object_path_from_value(value: &Value<'_>) -> Option<OwnedObjectPath> {
21382175
match value {
21392176
Value::ObjectPath(path) => Some(OwnedObjectPath::from(path.clone())),
@@ -2148,10 +2185,8 @@ async fn resolve_logind_display_session_path(
21482185
connection: &Connection,
21492186
pid: u32,
21502187
) -> Result<OwnedObjectPath, Box<dyn std::error::Error + Send + Sync>> {
2151-
let user_path = parse_logind_object_path(
2152-
manager.call::<_, _, OwnedValue>("GetUserByPID", &(pid)).await?,
2153-
"GetUserByPID",
2154-
)?;
2188+
let user_reply = manager.call_method("GetUserByPID", &(pid)).await?;
2189+
let user_path = decode_logind_object_path_reply(&user_reply, "GetUserByPID")?;
21552190
let user_proxy = zbus::Proxy::new(
21562191
connection,
21572192
LOGIND_BUS_NAME,

src/daemon/tests.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::*;
22
use clap::Parser;
3+
use zbus::Message;
34
use proptest::prelude::*;
45
use std::future::Future;
56
use std::path::Path;
@@ -1816,3 +1817,47 @@ fn test_parse_logind_object_path_string() {
18161817

18171818
assert_eq!(parsed.as_str(), "/org/freedesktop/login1/session/_1");
18181819
}
1820+
1821+
#[test]
1822+
fn test_decode_logind_object_path_reply_object_path() {
1823+
use zbus::zvariant::ObjectPath;
1824+
1825+
let path = ObjectPath::try_from("/org/freedesktop/login1/session/_1").unwrap();
1826+
let reply = Message::method_call("/org/freedesktop/login1", "GetSession")
1827+
.unwrap()
1828+
.build(&path)
1829+
.unwrap();
1830+
let parsed = decode_logind_object_path_reply(&reply, "test").unwrap();
1831+
1832+
assert_eq!(parsed.as_str(), "/org/freedesktop/login1/session/_1");
1833+
}
1834+
1835+
#[test]
1836+
fn test_decode_logind_object_path_reply_structure() {
1837+
use zbus::zvariant::{ObjectPath, StructureBuilder};
1838+
1839+
let path = ObjectPath::try_from("/org/freedesktop/login1/session/_1").unwrap();
1840+
let structure = StructureBuilder::new().add_field(path).build().unwrap();
1841+
let reply = Message::method_call("/org/freedesktop/login1", "GetSession")
1842+
.unwrap()
1843+
.build(&structure)
1844+
.unwrap();
1845+
let parsed = decode_logind_object_path_reply(&reply, "test").unwrap();
1846+
1847+
assert_eq!(parsed.as_str(), "/org/freedesktop/login1/session/_1");
1848+
}
1849+
1850+
#[test]
1851+
fn test_decode_logind_object_path_reply_variant() {
1852+
use zbus::zvariant::{ObjectPath, Value};
1853+
1854+
let path = ObjectPath::try_from("/org/freedesktop/login1/session/_1").unwrap();
1855+
let value = OwnedValue::try_from(Value::from(path)).unwrap();
1856+
let reply = Message::method_call("/org/freedesktop/login1", "GetSession")
1857+
.unwrap()
1858+
.build(&value)
1859+
.unwrap();
1860+
let parsed = decode_logind_object_path_reply(&reply, "test").unwrap();
1861+
1862+
assert_eq!(parsed.as_str(), "/org/freedesktop/login1/session/_1");
1863+
}

0 commit comments

Comments
 (0)