114 lines
5.5 KiB
Rust
114 lines
5.5 KiB
Rust
//! XDG Portal 权限冒烟测试示例。
|
||
//!
|
||
//! 本示例演示完整的 Portal ScreenCast 授权流程,分四步:
|
||
//! 创建 Screencast proxy,再创建 session,然后选择源(显示器/窗口),
|
||
//! 最后 start() 触发系统授权对话框(用户点击"共享"后返回流信息)。
|
||
//!
|
||
//! 运行:`cargo run --example test_portal`(参见 AGENTS.md "Useful manual commands")。
|
||
//!
|
||
//! Rust 异步模型(与 Go 对比):
|
||
//! - Go 用 goroutine + channel;async 函数本身**惰性**,需 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::response(Ok/Err),
|
||
// 内层才是 ScreenCast 流信息(streams() 返回 PipeWire 节点 + dmabuf 信息列表)
|
||
match response.response() {
|
||
Ok(r) => eprintln!(" Got {} stream(s)", r.streams().len()),
|
||
Err(e) => eprintln!(" Response error: {e}"),
|
||
}
|
||
});
|
||
}
|