docs(test_portal): 中文注释 Portal 冒烟测试示例
This commit is contained in:
@@ -1,11 +1,41 @@
|
||||
//! XDG Portal 权限冒烟测试示例。
|
||||
//!
|
||||
//! 本示例演示完整的 Portal ScreenCast 授权流程,分四步:
|
||||
//! 1) 创建 Screencast proxy → 2) 创建 session → 3) 选择源(显示器/窗口)
|
||||
//! → 4) 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");
|
||||
@@ -13,11 +43,14 @@ fn main() {
|
||||
}
|
||||
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");
|
||||
@@ -30,7 +63,15 @@ fn main() {
|
||||
};
|
||||
|
||||
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,
|
||||
@@ -50,6 +91,8 @@ fn main() {
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -60,6 +103,8 @@ fn main() {
|
||||
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}"),
|
||||
|
||||
Reference in New Issue
Block a user