use std::collections::HashMap;
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::time::Duration;
use futures_util::StreamExt;
use tokio_util::sync::CancellationToken;
use waydriver::sink::ExternalSinks;
use zbus::zvariant::{OwnedObjectPath, OwnedValue, Value};
fn spawn_dbus_daemon(dir: &Path) -> (String, Child) {
let socket = dir.join("bus");
let address = format!("unix:path={}", socket.display());
let child = Command::new("dbus-daemon")
.args(["--session", "--nofork", "--nopidfile"])
.arg(format!("--address={address}"))
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn dbus-daemon — is it installed?");
for _ in 0..50 {
if socket.exists() {
break;
}
std::thread::sleep(Duration::from_millis(100));
}
assert!(socket.exists(), "dbus-daemon socket never appeared");
(address, child)
}
#[tokio::test]
#[ignore = "spawns a private dbus-daemon; run with --ignored"]
async fn captures_notification_and_open_uri() {
let dir = tempfile::tempdir().expect("tempdir");
let (address, mut daemon) = spawn_dbus_daemon(dir.path());
let sinks = ExternalSinks::start(&address)
.await
.expect("start external sinks");
let client = zbus::connection::Builder::address(address.as_str())
.expect("address")
.build()
.await
.expect("client connect");
let empty_hints: HashMap<String, OwnedValue> = HashMap::new();
let reply = client
.call_method(
Some("org.freedesktop.Notifications"),
"/org/freedesktop/Notifications",
Some("org.freedesktop.Notifications"),
"Notify",
&(
"waydriver-test",
0u32,
"dialog-information",
"summary-x",
"body-y",
Vec::<String>::new(),
empty_hints,
-1i32,
),
)
.await
.expect("Notify call");
let id: u32 = reply.body().deserialize().expect("deserialize id");
assert_eq!(id, 1, "first notification id should be 1");
let captured = sinks.notifications();
assert_eq!(captured.len(), 1, "exactly one notification captured");
let n = &captured[0];
assert_eq!(n.app_name, "waydriver-test");
assert_eq!(n.summary, "summary-x");
assert_eq!(n.body, "body-y");
assert_eq!(n.expire_timeout, -1);
assert_eq!(n.id, 1);
let handle_token = "wdtest1";
let unique = client
.unique_name()
.expect("client has a unique name")
.as_str()
.to_string();
let expected_path = format!(
"/org/freedesktop/portal/desktop/request/{}/{}",
unique.trim_start_matches(':').replace('.', "_"),
handle_token
);
let rule = zbus::MatchRule::builder()
.msg_type(zbus::message::Type::Signal)
.interface("org.freedesktop.portal.Request")
.unwrap()
.member("Response")
.unwrap()
.path(expected_path.as_str())
.unwrap()
.build();
let mut responses = zbus::MessageStream::for_match_rule(rule, &client, None)
.await
.expect("subscribe Response");
let mut options: HashMap<String, OwnedValue> = HashMap::new();
options.insert(
"handle_token".to_string(),
OwnedValue::try_from(Value::from(handle_token)).unwrap(),
);
let reply = client
.call_method(
Some("org.freedesktop.portal.Desktop"),
"/org/freedesktop/portal/desktop",
Some("org.freedesktop.portal.OpenURI"),
"OpenURI",
&("", "https://example.com/waydriver", options),
)
.await
.expect("OpenURI call");
let handle: OwnedObjectPath = reply.body().deserialize().expect("deserialize handle");
assert_eq!(handle.as_str(), expected_path, "handle path matches token");
let open = sinks.open_uri_requests();
assert_eq!(open.len(), 1, "exactly one open-uri captured");
assert_eq!(open[0].uri, "https://example.com/waydriver");
let sig = tokio::time::timeout(Duration::from_secs(5), responses.next())
.await
.expect("Response within 5s")
.expect("stream item")
.expect("valid message");
let (response, _results): (u32, HashMap<String, OwnedValue>) =
sig.body().deserialize().expect("deserialize Response");
assert_eq!(response, 0, "portal Response is success (0)");
let token = CancellationToken::new();
let found = sinks
.wait_for_notification(
0,
|n| n.summary == "summary-x",
Duration::from_secs(1),
&token,
)
.await
.expect("wait_for_notification finds the existing entry");
assert_eq!(found.id, 1);
drop(client);
drop(sinks);
let _ = daemon.kill();
let _ = daemon.wait();
}