Files
wl-webrtc/examples/test_portal.rs

114 lines
5.5 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! XDG Portal 权限冒烟测试示例。
//!
//! 本示例演示完整的 Portal ScreenCast 授权流程,分四步:
//! 创建 Screencast proxy,再创建 session,然后选择源(显示器/窗口),
//! 最后 start() 触发系统授权对话框(用户点击"共享"后返回流信息)。
//!
//! 运行:`cargo run --example test_portal`(参见 AGENTS.md "Useful manual commands")。
//!
//! Rust 异步模型(与 Go 对比):
//! - Go 用 goroutine + channelasync 函数本身**惰性**,需 runtime 驱动。
//! - 本示例刻意**不用** `#[tokio::main]` 宏,而是手动 `Runtime::new()` + `block_on`
//! (与 src/backend_detect.rs 同款"手动 runtime"模式);这是因为 ashpd 内部缓存
//! zbus::Connection 到全局 OnceLock,若宏自动建的 runtime 被 drop
//! 缓存的 connection 会"僵尸化"导致后续 hang(详见 AGENTS.md)。
//!
//! 对照 Go`go func() { ... }()` ≈ `tokio::spawn(async { ... })`
//! 而 `block_on` 类似 Go 的 `select {}` 阻塞 main goroutine 等待退出。
// ashpd = XDG Portal 的 Rust 高层绑定,封装了 D-Bus ScreenCast 接口
use ashpd::desktop::screencast::{CursorMode, Screencast, SelectSourcesOptions, SourceType};
// PersistMode 控制"恢复令牌"持久化级别(DoNot / Persistent / ExplicitlyRevoked
use ashpd::desktop::PersistMode;
// BitFlags = 位域集合类型(一个值可同时包含多个 SourceType,类比 Go 的 iota | 操作)
use ashpd::enumflags2::BitFlags;
// 同步 main → 手动创建 tokio Runtime → block_on 阻塞驱动 async 块。
// 这种"同步外壳 + 异步内核"的写法等价于 `#[tokio::main] async fn main()`
// 但保留了显式控制 runtime 生命周期的灵活性(参见文件头说明)。
fn main() {
// 手动创建 tokio runtime(含 reactor + executor + 时间驱动);
// unwrap() 仅示例用;生产代码应返回 Result 并 `?` 传播(但 fn main 不返回 Result
let rt = tokio::runtime::Runtime::new().unwrap();
// block_on 阻塞当前线程直到传入的 future 完成;这是同步↔异步边界
rt.block_on(async {
// async {} 块构造一个匿名 future,仅在 block_on poll 时才执行(惰性,与 goroutine 不同)
eprintln!("1. Creating Screencast proxy...");
// Screencast::new() 内部通过 D-Bus 连接 org.freedesktop.portal.ScreenCast
// .await 让出执行权直到 future 就绪(Go 没有这个语法,需 channel/锁模拟)
let proxy = match Screencast::new().await {
Ok(p) => {
eprintln!(" OK");
p
}
Err(e) => {
eprintln!(" FAIL: {e}");
// early-return 仅退出 async 块(不是退出 main),block_on 返回 ()
return;
}
};
eprintln!("2. Creating session...");
// create_session 建立一个 ScreenCast 会话句柄;
// Default::default() 用类型默认参数(ashpd 推断为 SessionOptions,所有字段取 Default
let session = match proxy.create_session(Default::default()).await {
Ok(s) => {
eprintln!(" OK");
s
}
Err(e) => {
eprintln!(" FAIL: {e}");
return;
}
};
eprintln!("3. Selecting sources...");
// BitFlags<SourceType> 表达"可选多显示器/窗口/工作区"集合;
// 这里 `into()` 将单个 Monitor 转为位域(Go 类似 flag = 1 << iota
let sources: BitFlags<SourceType> = SourceType::Monitor.into();
// Builder 链式:每次 set_X 返回 &mut Self(类似 Go functional-options 模式但更显式)
// - cursor_mode Embedded:光标嵌入帧内
// - sources: 仅 Monitor(去掉窗口,简化授权 UX)
// - multiple=false:单选(一次只授权一个显示器)
// - persist_mode DoNot:不申请恢复令牌(避免持久权限残留)
// 整个 builder 链构造一个 future,末尾的 .await 等待 D-Bus 返回
let result = proxy
.select_sources(
&session,
SelectSourcesOptions::default()
.set_cursor_mode(CursorMode::Embedded)
.set_sources(sources)
.set_multiple(false)
.set_persist_mode(PersistMode::DoNot),
)
.await;
match result {
Ok(_) => eprintln!(" OK"),
Err(e) => {
eprintln!(" FAIL: {e}");
return;
}
}
eprintln!("4. Starting (should show dialog)...");
// start() 触发系统授权对话框(D-Bus 调用阻塞直到用户响应);
// 第二参数 parent_window = None(无父窗口,常见于 CLI 程序)
let response = match proxy.start(&session, None, Default::default()).await {
Ok(r) => {
eprintln!(" OK");
r
}
Err(e) => {
eprintln!(" FAIL: {e}");
return;
}
};
// Portal D-Bus 响应是双层结构:外层是 Request::responseOk/Err),
// 内层才是 ScreenCast 流信息(streams() 返回 PipeWire 节点 + dmabuf 信息列表)
match response.response() {
Ok(r) => eprintln!(" Got {} stream(s)", r.streams().len()),
Err(e) => eprintln!(" Response error: {e}"),
}
});
}