mirror of
https://github.com/YaLTeR/niri.git
synced 2024-09-11 12:35:58 +03:00
Add initial monitor screencast portal impl
DmaBuf monitor screencasting through xdg-dekstop-portal-gnome! Somewhat limited currently, e.g. the cursor is always embedded. But gets most of the job done.
This commit is contained in:
parent
bd0ecf9174
commit
d52ca23caa
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -29,8 +29,10 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -y software-properties-common
|
||||
sudo add-apt-repository -y ppa:pipewire-debian/pipewire-upstream
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev
|
||||
sudo apt-get install -y libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev
|
||||
|
||||
- name: Install Rust
|
||||
run: |
|
||||
@ -67,8 +69,10 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -y software-properties-common
|
||||
sudo add-apt-repository -y ppa:pipewire-debian/pipewire-upstream
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev
|
||||
sudo apt-get install -y libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev
|
||||
|
||||
- name: Install Rust
|
||||
run: |
|
||||
|
218
Cargo.lock
generated
218
Cargo.lock
generated
@ -294,6 +294,26 @@ version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.66.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.31",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -405,6 +425,25 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -436,6 +475,17 @@ dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.2"
|
||||
@ -497,6 +547,21 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie-factory"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
@ -634,7 +699,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
"libloading 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -920,6 +985,12 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@ -1130,12 +1201,28 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.0"
|
||||
@ -1172,6 +1259,34 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0434617020ddca18b86067912970c55410ca654cdafd775480322f50b857a8c4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"cc",
|
||||
"convert_case",
|
||||
"cookie-factory",
|
||||
"libc",
|
||||
"libspa-sys",
|
||||
"nix 0.26.4",
|
||||
"nom",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libspa-sys"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e70ca3f3e70f858ef363046d06178c427b4e0b63d210c95fd87d752679d345"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev-sys"
|
||||
version = "0.1.4"
|
||||
@ -1368,6 +1483,7 @@ name = "niri"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-io",
|
||||
"bitflags 2.4.0",
|
||||
"clap",
|
||||
"directories",
|
||||
@ -1376,6 +1492,7 @@ dependencies = [
|
||||
"knuffel",
|
||||
"logind-zbus",
|
||||
"miette",
|
||||
"pipewire",
|
||||
"portable-atomic",
|
||||
"profiling",
|
||||
"sd-notify",
|
||||
@ -1614,6 +1731,12 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
@ -1632,6 +1755,34 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pipewire"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d009c8dd65e890b515a71950f7e4c801523b8894ff33863a40830bf762e9e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"libc",
|
||||
"libspa",
|
||||
"libspa-sys",
|
||||
"nix 0.26.4",
|
||||
"once_cell",
|
||||
"pipewire-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pipewire-sys"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "890c084e7b737246cb4799c86b71a0e4da536031ff7473dd639eba9f95039f64"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libspa-sys",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
@ -1875,6 +2026,12 @@ version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.23"
|
||||
@ -1957,6 +2114,15 @@ dependencies = [
|
||||
"syn 2.0.31",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.5"
|
||||
@ -1977,6 +2143,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
@ -2052,7 +2224,7 @@ dependencies = [
|
||||
"input",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libloading",
|
||||
"libloading 0.8.0",
|
||||
"libseat",
|
||||
"nix 0.26.4",
|
||||
"once_cell",
|
||||
@ -2175,6 +2347,25 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.0"
|
||||
@ -2269,11 +2460,26 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0a3ab2091e52d7299a39d098e200114a972df0a7724add02a273aa9aada592"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@ -2282,6 +2488,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
@ -2437,6 +2645,12 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -16,6 +16,7 @@ keyframe = { version = "1.1.1", default-features = false }
|
||||
knuffel = "3.2.0"
|
||||
logind-zbus = "3.1.2"
|
||||
miette = { version = "5.10.0", features = ["fancy"] }
|
||||
pipewire = "0.7.2"
|
||||
portable-atomic = { version = "1.4.3", default-features = false, features = ["float"] }
|
||||
profiling = "1.0.10"
|
||||
sd-notify = "0.4.1"
|
||||
@ -26,9 +27,11 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
tracy-client = { version = "0.15", default-features = false }
|
||||
xcursor = "0.3.4"
|
||||
zbus = { version = "3.14.1" }
|
||||
async-io = "1.13.0"
|
||||
|
||||
[dependencies.smithay]
|
||||
git = "https://github.com/Smithay/smithay.git"
|
||||
# path = "../smithay"
|
||||
default-features = false
|
||||
features = [
|
||||
"backend_drm",
|
||||
@ -48,6 +51,7 @@ features = [
|
||||
|
||||
[dependencies.smithay-drm-extras]
|
||||
git = "https://github.com/Smithay/smithay.git"
|
||||
# path = "../smithay/smithay-drm-extras"
|
||||
|
||||
[features]
|
||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
|
||||
|
@ -1,3 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use smithay::backend::allocator::gbm::GbmDevice;
|
||||
use smithay::backend::drm::DrmDeviceFd;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::output::Output;
|
||||
use smithay::wayland::dmabuf::DmabufFeedback;
|
||||
@ -79,6 +84,20 @@ impl Backend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
|
||||
match self {
|
||||
Backend::Tty(tty) => tty.connectors(),
|
||||
Backend::Winit(winit) => winit.connectors(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gbm_device(&self) -> Option<GbmDevice<DrmDeviceFd>> {
|
||||
match self {
|
||||
Backend::Tty(tty) => tty.gbm_device(),
|
||||
Backend::Winit(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tty(&mut self) -> &mut Tty {
|
||||
if let Self::Tty(v) = self {
|
||||
v
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::os::fd::FromRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Mutex, Arc};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
@ -44,6 +45,7 @@ pub struct Tty {
|
||||
udev_dispatcher: Dispatcher<'static, UdevBackend, LoopData>,
|
||||
primary_gpu_path: PathBuf,
|
||||
output_device: Option<OutputDevice>,
|
||||
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
||||
}
|
||||
|
||||
type GbmDrmCompositor = DrmCompositor<
|
||||
@ -236,6 +238,7 @@ impl Tty {
|
||||
udev_dispatcher,
|
||||
primary_gpu_path,
|
||||
output_device: None,
|
||||
connectors: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,6 +552,11 @@ impl Tty {
|
||||
tracy_client::internal::create_frame_name(format!("vblank on {output_name}\0").leak())
|
||||
};
|
||||
|
||||
self.connectors
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(output_name.clone(), output.clone());
|
||||
|
||||
let surface = Surface {
|
||||
name: output_name,
|
||||
compositor,
|
||||
@ -574,10 +582,10 @@ impl Tty {
|
||||
debug!("disconnecting connector: {connector:?}");
|
||||
let device = self.output_device.as_mut().unwrap();
|
||||
|
||||
if device.surfaces.remove(&crtc).is_none() {
|
||||
debug!("crts wasn't enabled");
|
||||
let Some(surface) = device.surfaces.remove(&crtc) else {
|
||||
debug!("crtc wasn't enabled");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let output = niri
|
||||
.global_space
|
||||
@ -590,6 +598,8 @@ impl Tty {
|
||||
.clone();
|
||||
|
||||
niri.remove_output(&output);
|
||||
|
||||
self.connectors.lock().unwrap().remove(&surface.name);
|
||||
}
|
||||
|
||||
pub fn seat_name(&self) -> String {
|
||||
@ -680,6 +690,14 @@ impl Tty {
|
||||
pub fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.output_device.as_mut().unwrap().dmabuf_state
|
||||
}
|
||||
|
||||
pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
|
||||
self.connectors.clone()
|
||||
}
|
||||
|
||||
pub fn gbm_device(&self) -> Option<GbmDevice<DrmDeviceFd>> {
|
||||
self.output_device.as_ref().map(|d| d.gbm.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_interval(mode: DrmMode) -> Duration {
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::backend::renderer::damage::OutputDamageTracker;
|
||||
@ -21,6 +23,7 @@ pub struct Winit {
|
||||
output: Output,
|
||||
backend: WinitGraphicsBackend<GlesRenderer>,
|
||||
damage_tracker: OutputDamageTracker,
|
||||
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
||||
}
|
||||
|
||||
impl Winit {
|
||||
@ -53,6 +56,11 @@ impl Winit {
|
||||
);
|
||||
output.set_preferred(mode);
|
||||
|
||||
let connectors = Arc::new(Mutex::new(HashMap::from([(
|
||||
"winit".to_owned(),
|
||||
output.clone(),
|
||||
)])));
|
||||
|
||||
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
|
||||
let timer = Timer::immediate();
|
||||
@ -99,6 +107,7 @@ impl Winit {
|
||||
output,
|
||||
backend,
|
||||
damage_tracker,
|
||||
connectors,
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,4 +171,8 @@ impl Winit {
|
||||
let renderer = self.backend.renderer();
|
||||
renderer.set_debug_flags(renderer.debug_flags() ^ DebugFlags::TINT);
|
||||
}
|
||||
|
||||
pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
|
||||
self.connectors.clone()
|
||||
}
|
||||
}
|
||||
|
@ -128,12 +128,15 @@ pub enum Action {
|
||||
pub struct DebugConfig {
|
||||
#[knuffel(child, unwrap(argument), default = 1.)]
|
||||
pub animation_slowdown: f64,
|
||||
#[knuffel(child)]
|
||||
pub screen_cast_in_non_session_instances: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
animation_slowdown: 1.,
|
||||
screen_cast_in_non_session_instances: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -308,6 +311,7 @@ mod tests {
|
||||
]),
|
||||
debug: DebugConfig {
|
||||
animation_slowdown: 2.,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
@ -1 +1,3 @@
|
||||
pub mod mutter_display_config;
|
||||
pub mod mutter_screen_cast;
|
||||
pub mod mutter_service_channel;
|
||||
|
88
src/dbus/mutter_display_config.rs
Normal file
88
src/dbus/mutter_display_config.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use serde::Serialize;
|
||||
use smithay::output::Output;
|
||||
use zbus::zvariant::{OwnedValue, Type};
|
||||
use zbus::{dbus_interface, fdo};
|
||||
|
||||
pub struct DisplayConfig {
|
||||
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Type)]
|
||||
pub struct Monitor {
|
||||
names: (String, String, String, String),
|
||||
modes: Vec<Mode>,
|
||||
properties: HashMap<String, OwnedValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Type)]
|
||||
pub struct Mode {
|
||||
id: String,
|
||||
width: i32,
|
||||
height: i32,
|
||||
refresh_rate: f64,
|
||||
preferred_scale: f64,
|
||||
supported_scales: Vec<f64>,
|
||||
properties: HashMap<String, OwnedValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Type)]
|
||||
pub struct LogicalMonitor {
|
||||
x: i32,
|
||||
y: i32,
|
||||
scale: f64,
|
||||
transform: u32,
|
||||
is_primary: bool,
|
||||
monitors: Vec<(String, String, String, String)>,
|
||||
properties: HashMap<String, OwnedValue>,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.gnome.Mutter.DisplayConfig")]
|
||||
impl DisplayConfig {
|
||||
async fn get_current_state(
|
||||
&self,
|
||||
) -> fdo::Result<(
|
||||
u32,
|
||||
Vec<Monitor>,
|
||||
Vec<LogicalMonitor>,
|
||||
HashMap<String, OwnedValue>,
|
||||
)> {
|
||||
// Construct the DBus response.
|
||||
let monitors: Vec<Monitor> = self
|
||||
.connectors
|
||||
.lock()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.map(|c| Monitor {
|
||||
names: (c.clone(), String::new(), String::new(), String::new()),
|
||||
modes: vec![],
|
||||
properties: HashMap::new(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let logical_monitors = monitors
|
||||
.iter()
|
||||
.map(|m| LogicalMonitor {
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1.,
|
||||
transform: 0,
|
||||
is_primary: false,
|
||||
monitors: vec![m.names.clone()],
|
||||
properties: HashMap::new(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((0, monitors, logical_monitors, HashMap::new()))
|
||||
}
|
||||
|
||||
// FIXME: monitors-changed signal.
|
||||
}
|
||||
|
||||
impl DisplayConfig {
|
||||
pub fn new(connectors: Arc<Mutex<HashMap<String, Output>>>) -> Self {
|
||||
Self { connectors }
|
||||
}
|
||||
}
|
256
src/dbus/mutter_screen_cast.rs
Normal file
256
src/dbus/mutter_screen_cast.rs
Normal file
@ -0,0 +1,256 @@
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use serde::Deserialize;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::calloop;
|
||||
use zbus::zvariant::{DeserializeDict, OwnedObjectPath, Type, Value};
|
||||
use zbus::{dbus_interface, fdo, InterfaceRef, ObjectServer, SignalContext};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ScreenCast {
|
||||
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
||||
to_niri: calloop::channel::Sender<ToNiriMsg>,
|
||||
sessions: Arc<Mutex<Vec<(Session, InterfaceRef<Session>)>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Session {
|
||||
id: usize,
|
||||
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
||||
to_niri: calloop::channel::Sender<ToNiriMsg>,
|
||||
streams: Arc<Mutex<Vec<(Stream, InterfaceRef<Stream>)>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Type, Clone, Copy)]
|
||||
pub enum CursorMode {
|
||||
#[default]
|
||||
Hidden = 0,
|
||||
Embedded = 1,
|
||||
Metadata = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, DeserializeDict, Type)]
|
||||
#[zvariant(signature = "dict")]
|
||||
struct RecordMonitorProperties {
|
||||
#[zvariant(rename = "cursor-mode")]
|
||||
cursor_mode: Option<CursorMode>,
|
||||
#[zvariant(rename = "is-recording")]
|
||||
_is_recording: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Stream {
|
||||
output: Output,
|
||||
cursor_mode: CursorMode,
|
||||
was_started: Arc<AtomicBool>,
|
||||
to_niri: calloop::channel::Sender<ToNiriMsg>,
|
||||
}
|
||||
|
||||
pub enum ToNiriMsg {
|
||||
StartCast {
|
||||
session_id: usize,
|
||||
output: Output,
|
||||
cursor_mode: CursorMode,
|
||||
signal_ctx: SignalContext<'static>,
|
||||
},
|
||||
StopCast {
|
||||
session_id: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.gnome.Mutter.ScreenCast")]
|
||||
impl ScreenCast {
|
||||
async fn create_session(
|
||||
&self,
|
||||
#[zbus(object_server)] server: &ObjectServer,
|
||||
properties: HashMap<&str, Value<'_>>,
|
||||
) -> fdo::Result<OwnedObjectPath> {
|
||||
if properties.contains_key("remote-desktop-session-id") {
|
||||
return Err(fdo::Error::Failed(
|
||||
"there are no remote desktop sessions".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
static NUMBER: AtomicUsize = AtomicUsize::new(0);
|
||||
let session_id = NUMBER.fetch_add(1, Ordering::SeqCst);
|
||||
let path = format!("/org/gnome/Mutter/ScreenCast/Session/u{}", session_id);
|
||||
let path = OwnedObjectPath::try_from(path).unwrap();
|
||||
|
||||
let session = Session::new(session_id, self.connectors.clone(), self.to_niri.clone());
|
||||
match server.at(&path, session.clone()).await {
|
||||
Ok(true) => {
|
||||
let iface = server.interface(&path).await.unwrap();
|
||||
self.sessions.lock().unwrap().push((session, iface));
|
||||
}
|
||||
Ok(false) => return Err(fdo::Error::Failed("session path already exists".to_owned())),
|
||||
Err(err) => {
|
||||
return Err(fdo::Error::Failed(format!(
|
||||
"error creating session object: {err:?}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn version(&self) -> i32 {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.gnome.Mutter.ScreenCast.Session")]
|
||||
impl Session {
|
||||
async fn start(&self) {
|
||||
debug!("start");
|
||||
|
||||
for (stream, iface) in &*self.streams.lock().unwrap() {
|
||||
stream.start(self.id, iface.signal_context().clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stop(
|
||||
&self,
|
||||
#[zbus(object_server)] server: &ObjectServer,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
) {
|
||||
debug!("stop");
|
||||
|
||||
Session::closed(&ctxt).await.unwrap();
|
||||
|
||||
if let Err(err) = self.to_niri.send(ToNiriMsg::StopCast {
|
||||
session_id: self.id,
|
||||
}) {
|
||||
warn!("error sending StopCast to niri: {err:?}");
|
||||
}
|
||||
|
||||
let streams = mem::take(&mut *self.streams.lock().unwrap());
|
||||
for (_, iface) in streams.iter() {
|
||||
server
|
||||
.remove::<Stream, _>(iface.signal_context().path())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
server.remove::<Session, _>(ctxt.path()).await.unwrap();
|
||||
}
|
||||
|
||||
async fn record_monitor(
|
||||
&mut self,
|
||||
#[zbus(object_server)] server: &ObjectServer,
|
||||
connector: &str,
|
||||
properties: RecordMonitorProperties,
|
||||
) -> fdo::Result<OwnedObjectPath> {
|
||||
debug!(connector, ?properties, "record_monitor");
|
||||
|
||||
let Some(output) = self.connectors.lock().unwrap().get(connector).cloned() else {
|
||||
return Err(fdo::Error::Failed("no such monitor".to_owned()));
|
||||
};
|
||||
|
||||
static NUMBER: AtomicUsize = AtomicUsize::new(0);
|
||||
let path = format!(
|
||||
"/org/gnome/Mutter/ScreenCast/Stream/u{}",
|
||||
NUMBER.fetch_add(1, Ordering::SeqCst)
|
||||
);
|
||||
let path = OwnedObjectPath::try_from(path).unwrap();
|
||||
|
||||
let cursor_mode = properties.cursor_mode.unwrap_or_default();
|
||||
|
||||
let stream = Stream::new(output, cursor_mode, self.to_niri.clone());
|
||||
match server.at(&path, stream.clone()).await {
|
||||
Ok(true) => {
|
||||
let iface = server.interface(&path).await.unwrap();
|
||||
self.streams.lock().unwrap().push((stream, iface));
|
||||
}
|
||||
Ok(false) => return Err(fdo::Error::Failed("stream path already exists".to_owned())),
|
||||
Err(err) => {
|
||||
return Err(fdo::Error::Failed(format!(
|
||||
"error creating stream object: {err:?}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn closed(ctxt: &SignalContext<'_>) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.gnome.Mutter.ScreenCast.Stream")]
|
||||
impl Stream {
|
||||
#[dbus_interface(signal)]
|
||||
pub async fn pipe_wire_stream_added(ctxt: &SignalContext<'_>, node_id: u32)
|
||||
-> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl ScreenCast {
|
||||
pub fn new(
|
||||
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
||||
to_niri: calloop::channel::Sender<ToNiriMsg>,
|
||||
) -> Self {
|
||||
Self {
|
||||
connectors,
|
||||
to_niri,
|
||||
sessions: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(
|
||||
id: usize,
|
||||
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
||||
to_niri: calloop::channel::Sender<ToNiriMsg>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
connectors,
|
||||
streams: Arc::new(Mutex::new(vec![])),
|
||||
to_niri,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Session {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.to_niri.send(ToNiriMsg::StopCast {
|
||||
session_id: self.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
pub fn new(
|
||||
output: Output,
|
||||
cursor_mode: CursorMode,
|
||||
to_niri: calloop::channel::Sender<ToNiriMsg>,
|
||||
) -> Self {
|
||||
Self {
|
||||
output,
|
||||
cursor_mode,
|
||||
was_started: Arc::new(AtomicBool::new(false)),
|
||||
to_niri,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&self, session_id: usize, ctxt: SignalContext<'static>) {
|
||||
if self.was_started.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = ToNiriMsg::StartCast {
|
||||
session_id,
|
||||
output: self.output.clone(),
|
||||
cursor_mode: self.cursor_mode,
|
||||
signal_ctx: ctxt,
|
||||
};
|
||||
|
||||
if let Err(err) = self.to_niri.send(msg) {
|
||||
warn!("error sending StartCast to niri: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ mod handlers;
|
||||
mod input;
|
||||
mod layout;
|
||||
mod niri;
|
||||
mod pw_utils;
|
||||
mod utils;
|
||||
|
||||
use std::env;
|
||||
|
329
src/niri.rs
329
src/niri.rs
@ -8,6 +8,7 @@ use std::{env, thread};
|
||||
use anyhow::Context;
|
||||
use directories::UserDirs;
|
||||
use sd_notify::NotifyState;
|
||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::surface::{
|
||||
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
||||
@ -16,7 +17,7 @@ use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderE
|
||||
use smithay::backend::renderer::element::{
|
||||
render_elements, AsRenderElements, Element, RenderElement, RenderElementStates,
|
||||
};
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::{Bind, ExportMem, Frame, ImportAll, Offscreen, Renderer};
|
||||
use smithay::desktop::utils::{
|
||||
send_dmabuf_feedback_surface_tree, send_frames_surface_tree,
|
||||
@ -31,7 +32,7 @@ use smithay::input::pointer::{CursorImageAttributes, CursorImageStatus, MotionEv
|
||||
use smithay::input::{Seat, SeatState};
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::calloop::generic::Generic;
|
||||
use smithay::reexports::calloop::{Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction};
|
||||
use smithay::reexports::calloop::{self, Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction};
|
||||
use smithay::reexports::nix::libc::CLOCK_MONOTONIC;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
|
||||
use smithay::reexports::wayland_server::backend::{
|
||||
@ -40,7 +41,7 @@ use smithay::reexports::wayland_server::backend::{
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::reexports::wayland_server::{Display, DisplayHandle};
|
||||
use smithay::utils::{
|
||||
IsAlive, Logical, Physical, Point, Rectangle, Scale, Transform, SERIAL_COUNTER,
|
||||
IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform, SERIAL_COUNTER,
|
||||
};
|
||||
use smithay::wayland::compositor::{with_states, CompositorClientState, CompositorState};
|
||||
use smithay::wayland::data_device::DataDeviceState;
|
||||
@ -57,9 +58,12 @@ use time::OffsetDateTime;
|
||||
|
||||
use crate::backend::{Backend, Tty, Winit};
|
||||
use crate::config::Config;
|
||||
use crate::dbus::mutter_display_config::DisplayConfig;
|
||||
use crate::dbus::mutter_screen_cast::{self, ScreenCast, ToNiriMsg};
|
||||
use crate::dbus::mutter_service_channel::ServiceChannel;
|
||||
use crate::frame_clock::FrameClock;
|
||||
use crate::layout::{MonitorRenderElement, MonitorSet};
|
||||
use crate::pw_utils::{Cast, PipeWire};
|
||||
use crate::utils::{center, get_monotonic_time, load_default_cursor};
|
||||
use crate::LoopData;
|
||||
|
||||
@ -102,6 +106,11 @@ pub struct Niri {
|
||||
|
||||
pub zbus_conn: Option<zbus::blocking::Connection>,
|
||||
pub inhibit_power_key_fd: Option<zbus::zvariant::OwnedFd>,
|
||||
pub screen_cast: ScreenCast,
|
||||
|
||||
// Casts are dropped before PipeWire to prevent a double-free (yay).
|
||||
pub casts: Vec<Cast>,
|
||||
pub pipewire: Option<PipeWire>,
|
||||
}
|
||||
|
||||
pub struct OutputState {
|
||||
@ -137,13 +146,7 @@ impl State {
|
||||
Backend::Tty(Tty::new(event_loop.clone()))
|
||||
};
|
||||
|
||||
let mut niri = Niri::new(
|
||||
&config,
|
||||
event_loop,
|
||||
stop_signal,
|
||||
display,
|
||||
backend.seat_name(),
|
||||
);
|
||||
let mut niri = Niri::new(&config, event_loop, stop_signal, display, &backend);
|
||||
backend.init(&mut niri);
|
||||
|
||||
Self {
|
||||
@ -195,7 +198,7 @@ impl Niri {
|
||||
event_loop: LoopHandle<'static, LoopData>,
|
||||
stop_signal: LoopSignal,
|
||||
display: &mut Display<State>,
|
||||
seat_name: String,
|
||||
backend: &Backend,
|
||||
) -> Self {
|
||||
let display_handle = display.handle();
|
||||
|
||||
@ -215,7 +218,7 @@ impl Niri {
|
||||
let presentation_state =
|
||||
PresentationState::new::<State>(&display_handle, CLOCK_MONOTONIC as u32);
|
||||
|
||||
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, seat_name);
|
||||
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
|
||||
let xkb = XkbConfig {
|
||||
rules: &config.input.keyboard.xkb.rules,
|
||||
model: &config.input.keyboard.xkb.model,
|
||||
@ -246,6 +249,102 @@ impl Niri {
|
||||
socket_name.to_string_lossy()
|
||||
);
|
||||
|
||||
let pipewire = match PipeWire::new(&event_loop) {
|
||||
Ok(pipewire) => Some(pipewire),
|
||||
Err(err) => {
|
||||
warn!("error starting PipeWire: {err:?}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let (to_niri, from_screen_cast) = calloop::channel::channel();
|
||||
event_loop
|
||||
.insert_source(from_screen_cast, {
|
||||
let to_niri = to_niri.clone();
|
||||
move |event, _, data| match event {
|
||||
calloop::channel::Event::Msg(msg) => match msg {
|
||||
ToNiriMsg::StartCast {
|
||||
session_id,
|
||||
output,
|
||||
cursor_mode,
|
||||
signal_ctx,
|
||||
} => {
|
||||
let _span = tracy_client::span!("StartCast");
|
||||
|
||||
debug!(session_id, "StartCast");
|
||||
|
||||
let gbm = match data.state.backend.gbm_device() {
|
||||
Some(gbm) => gbm,
|
||||
None => {
|
||||
debug!("no GBM device available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let pw = data.state.niri.pipewire.as_ref().unwrap();
|
||||
match pw.start_cast(
|
||||
to_niri.clone(),
|
||||
gbm,
|
||||
session_id,
|
||||
output,
|
||||
cursor_mode,
|
||||
signal_ctx,
|
||||
) {
|
||||
Ok(cast) => {
|
||||
data.state.niri.casts.push(cast);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("error starting screencast: {err:?}");
|
||||
|
||||
if let Err(err) =
|
||||
to_niri.send(ToNiriMsg::StopCast { session_id })
|
||||
{
|
||||
warn!("error sending StopCast to niri: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ToNiriMsg::StopCast { session_id } => {
|
||||
let _span = tracy_client::span!("StopCast");
|
||||
|
||||
debug!(session_id, "StopCast");
|
||||
|
||||
for i in (0..data.state.niri.casts.len()).rev() {
|
||||
let cast = &data.state.niri.casts[i];
|
||||
if cast.session_id != session_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cast = data.state.niri.casts.swap_remove(i);
|
||||
if let Err(err) = cast.stream.disconnect() {
|
||||
warn!("error disconnecting stream: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
let server =
|
||||
data.state.niri.zbus_conn.as_ref().unwrap().object_server();
|
||||
let path =
|
||||
format!("/org/gnome/Mutter/ScreenCast/Session/u{}", session_id);
|
||||
if let Ok(iface) =
|
||||
server.interface::<_, mutter_screen_cast::Session>(path)
|
||||
{
|
||||
let _span = tracy_client::span!("invoking Session::stop");
|
||||
|
||||
async_io::block_on(async move {
|
||||
iface
|
||||
.get()
|
||||
.stop(&server, iface.signal_context().clone())
|
||||
.await
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
calloop::channel::Event::Closed => (),
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let screen_cast = ScreenCast::new(backend.connectors(), to_niri);
|
||||
|
||||
let mut zbus_conn = None;
|
||||
let mut inhibit_power_key_fd = None;
|
||||
if std::env::var_os("NOTIFY_SOCKET").is_some() {
|
||||
@ -278,7 +377,7 @@ impl Niri {
|
||||
}
|
||||
|
||||
// Set up zbus, make sure it happens before anything might want it.
|
||||
let conn = zbus::blocking::ConnectionBuilder::session()
|
||||
let mut conn = zbus::blocking::ConnectionBuilder::session()
|
||||
.unwrap()
|
||||
.name("org.gnome.Mutter.ServiceChannel")
|
||||
.unwrap()
|
||||
@ -286,9 +385,24 @@ impl Niri {
|
||||
"/org/gnome/Mutter/ServiceChannel",
|
||||
ServiceChannel::new(display_handle.clone()),
|
||||
)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
if pipewire.is_some() && !config.debug.screen_cast_in_non_session_instances {
|
||||
conn = conn
|
||||
.name("org.gnome.Mutter.ScreenCast")
|
||||
.unwrap()
|
||||
.serve_at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
|
||||
.unwrap()
|
||||
.name("org.gnome.Mutter.DisplayConfig")
|
||||
.unwrap()
|
||||
.serve_at(
|
||||
"/org/gnome/Mutter/DisplayConfig",
|
||||
DisplayConfig::new(backend.connectors()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let conn = conn.build().unwrap();
|
||||
zbus_conn = Some(conn);
|
||||
|
||||
// Notify systemd we're ready.
|
||||
@ -321,6 +435,23 @@ impl Niri {
|
||||
warn!("error inhibiting power key: {err:?}");
|
||||
}
|
||||
}
|
||||
} else if pipewire.is_some() && config.debug.screen_cast_in_non_session_instances {
|
||||
let conn = zbus::blocking::ConnectionBuilder::session()
|
||||
.unwrap()
|
||||
.name("org.gnome.Mutter.ScreenCast")
|
||||
.unwrap()
|
||||
.serve_at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
|
||||
.unwrap()
|
||||
.name("org.gnome.Mutter.DisplayConfig")
|
||||
.unwrap()
|
||||
.serve_at(
|
||||
"/org/gnome/Mutter/DisplayConfig",
|
||||
DisplayConfig::new(backend.connectors()),
|
||||
)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
zbus_conn = Some(conn);
|
||||
}
|
||||
|
||||
let display_source = Generic::new(
|
||||
@ -364,6 +495,9 @@ impl Niri {
|
||||
|
||||
zbus_conn,
|
||||
inhibit_power_key_fd,
|
||||
screen_cast,
|
||||
pipewire,
|
||||
casts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@ -720,6 +854,9 @@ impl Niri {
|
||||
|
||||
// Send the frame callbacks.
|
||||
self.send_frame_callbacks(output);
|
||||
|
||||
// Render and send to PipeWire screencast streams.
|
||||
self.send_for_screen_cast(backend, output, &elements, presentation_time);
|
||||
}
|
||||
|
||||
fn send_dmabuf_feedbacks(&self, output: &Output, feedback: &DmabufFeedback) {
|
||||
@ -827,6 +964,63 @@ impl Niri {
|
||||
feedback
|
||||
}
|
||||
|
||||
fn send_for_screen_cast(
|
||||
&mut self,
|
||||
backend: &mut Backend,
|
||||
output: &Output,
|
||||
elements: &[OutputRenderElements<GlesRenderer>],
|
||||
presentation_time: Duration,
|
||||
) {
|
||||
let _span = tracy_client::span!("Niri::send_for_screen_cast");
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
|
||||
for cast in &mut self.casts {
|
||||
if !cast.is_active.get() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if &cast.output != output {
|
||||
continue;
|
||||
}
|
||||
|
||||
let last = cast.last_frame_time;
|
||||
let min = cast.min_time_between_frames.get();
|
||||
if !last.is_zero() && presentation_time - last < min {
|
||||
trace!(
|
||||
"skipping frame because it is too soon \
|
||||
last={last:?} now={presentation_time:?} diff={:?} < min={min:?}",
|
||||
presentation_time - last,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
let mut buffer = match cast.stream.dequeue_buffer() {
|
||||
Some(buffer) => buffer,
|
||||
None => {
|
||||
warn!("no available buffer in pw stream, skipping frame");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let data = &mut buffer.datas_mut()[0];
|
||||
let fd = data.as_raw().fd as i32;
|
||||
let dmabuf = cast.dmabufs.borrow()[&fd].clone();
|
||||
|
||||
// FIXME: Hidden / embedded / metadata cursor
|
||||
render_to_dmabuf(backend.renderer(), dmabuf, size, elements).unwrap();
|
||||
|
||||
let maxsize = data.as_raw().maxsize;
|
||||
let chunk = data.chunk_mut();
|
||||
*chunk.size_mut() = maxsize;
|
||||
*chunk.stride_mut() = maxsize as i32 / size.h;
|
||||
}
|
||||
|
||||
cast.last_frame_time = presentation_time;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screenshot(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
@ -835,39 +1029,9 @@ impl Niri {
|
||||
let _span = tracy_client::span!("Niri::screenshot");
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let output_rect = Rectangle::from_loc_and_size((0, 0), size);
|
||||
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
|
||||
let fourcc = Fourcc::Abgr8888;
|
||||
|
||||
let texture: GlesTexture = renderer
|
||||
.create_buffer(fourcc, buffer_size)
|
||||
.context("error creating texture")?;
|
||||
|
||||
let elements = self.render(renderer, output);
|
||||
|
||||
renderer.bind(texture).context("error binding texture")?;
|
||||
let mut frame = renderer
|
||||
.render(size, Transform::Normal)
|
||||
.context("error starting frame")?;
|
||||
|
||||
frame
|
||||
.clear([0.1, 0.1, 0.1, 1.], &[output_rect])
|
||||
.context("error clearing")?;
|
||||
|
||||
for element in elements.into_iter().rev() {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(Scale::from(1.));
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[output_rect])
|
||||
.context("error drawing element")?;
|
||||
}
|
||||
|
||||
let sync_point = frame.finish().context("error finishing frame")?;
|
||||
sync_point.wait();
|
||||
|
||||
let mapping = renderer
|
||||
.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size), fourcc)
|
||||
.context("error copying framebuffer")?;
|
||||
let mapping = render_and_download(renderer, size, &elements).context("error rendering")?;
|
||||
let copy = renderer
|
||||
.map_texture(&mapping)
|
||||
.context("error mapping texture")?;
|
||||
@ -928,3 +1092,76 @@ impl ClientData for ClientState {
|
||||
fn initialized(&self, _client_id: ClientId) {}
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
||||
}
|
||||
|
||||
fn render_and_download(
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
elements: &[OutputRenderElements<GlesRenderer>],
|
||||
) -> anyhow::Result<GlesMapping> {
|
||||
let _span = tracy_client::span!("render_and_download");
|
||||
|
||||
let output_rect = Rectangle::from_loc_and_size((0, 0), size);
|
||||
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
|
||||
let fourcc = Fourcc::Abgr8888;
|
||||
|
||||
let texture: GlesTexture = renderer
|
||||
.create_buffer(fourcc, buffer_size)
|
||||
.context("error creating texture")?;
|
||||
|
||||
renderer.bind(texture).context("error binding texture")?;
|
||||
let mut frame = renderer
|
||||
.render(size, Transform::Normal)
|
||||
.context("error starting frame")?;
|
||||
|
||||
frame
|
||||
.clear([0.1, 0.1, 0.1, 1.], &[output_rect])
|
||||
.context("error clearing")?;
|
||||
|
||||
for element in elements.iter().rev() {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(Scale::from(1.));
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[output_rect])
|
||||
.context("error drawing element")?;
|
||||
}
|
||||
|
||||
let sync_point = frame.finish().context("error finishing frame")?;
|
||||
sync_point.wait();
|
||||
|
||||
let mapping = renderer
|
||||
.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size), fourcc)
|
||||
.context("error copying framebuffer")?;
|
||||
Ok(mapping)
|
||||
}
|
||||
|
||||
fn render_to_dmabuf(
|
||||
renderer: &mut GlesRenderer,
|
||||
dmabuf: Dmabuf,
|
||||
size: Size<i32, Physical>,
|
||||
elements: &[OutputRenderElements<GlesRenderer>],
|
||||
) -> anyhow::Result<()> {
|
||||
let _span = tracy_client::span!("render_to_dmabuf");
|
||||
|
||||
let output_rect = Rectangle::from_loc_and_size((0, 0), size);
|
||||
|
||||
renderer.bind(dmabuf).context("error binding texture")?;
|
||||
let mut frame = renderer
|
||||
.render(size, Transform::Normal)
|
||||
.context("error starting frame")?;
|
||||
|
||||
frame
|
||||
.clear([0.1, 0.1, 0.1, 1.], &[output_rect])
|
||||
.context("error clearing")?;
|
||||
|
||||
for element in elements.iter().rev() {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(Scale::from(1.));
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[output_rect])
|
||||
.context("error drawing element")?;
|
||||
}
|
||||
|
||||
let _sync_point = frame.finish().context("error finishing frame")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
382
src/pw_utils.rs
Normal file
382
src/pw_utils.rs
Normal file
@ -0,0 +1,382 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
use std::mem;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use pipewire::spa::data::DataType;
|
||||
use pipewire::spa::format::{FormatProperties, MediaSubtype, MediaType};
|
||||
use pipewire::spa::param::format_utils::parse_format;
|
||||
use pipewire::spa::param::video::{VideoFormat, VideoInfoRaw};
|
||||
use pipewire::spa::param::ParamType;
|
||||
use pipewire::spa::pod::serialize::PodSerializer;
|
||||
use pipewire::spa::pod::{self, ChoiceValue, Pod, Property, PropertyFlags};
|
||||
use pipewire::spa::sys::*;
|
||||
use pipewire::spa::utils::{Choice, ChoiceEnum, ChoiceFlags, Fraction, Rectangle, SpaTypes};
|
||||
use pipewire::spa::Direction;
|
||||
use pipewire::stream::{Stream, StreamFlags, StreamListener, StreamState};
|
||||
use pipewire::{Context, Core, MainLoop, Properties};
|
||||
use smithay::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
|
||||
use smithay::backend::allocator::gbm::{GbmBufferFlags, GbmDevice};
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::drm::DrmDeviceFd;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::calloop::generic::Generic;
|
||||
use smithay::reexports::calloop::{self, Interest, LoopHandle, Mode, PostAction};
|
||||
use smithay::reexports::gbm::Modifier;
|
||||
use zbus::SignalContext;
|
||||
|
||||
use crate::dbus::mutter_screen_cast::{self, CursorMode, ToNiriMsg};
|
||||
use crate::LoopData;
|
||||
|
||||
pub struct PipeWire {
|
||||
_context: Context<MainLoop>,
|
||||
pub core: Core,
|
||||
}
|
||||
|
||||
pub struct Cast {
|
||||
pub session_id: usize,
|
||||
pub stream: Rc<Stream>,
|
||||
_listener: StreamListener<()>,
|
||||
pub is_active: Rc<Cell<bool>>,
|
||||
pub output: Output,
|
||||
pub cursor_mode: CursorMode,
|
||||
pub last_frame_time: Duration,
|
||||
pub min_time_between_frames: Rc<Cell<Duration>>,
|
||||
pub dmabufs: Rc<RefCell<HashMap<i32, Dmabuf>>>,
|
||||
}
|
||||
|
||||
impl PipeWire {
|
||||
pub fn new(event_loop: &LoopHandle<'static, LoopData>) -> anyhow::Result<Self> {
|
||||
let main_loop = MainLoop::new().context("error creating MainLoop")?;
|
||||
let context = Context::new(&main_loop).context("error creating Context")?;
|
||||
let core = context.connect(None).context("error creating Core")?;
|
||||
|
||||
let listener = core
|
||||
.add_listener_local()
|
||||
.error(|id, seq, res, message| {
|
||||
warn!(id, seq, res, message, "pw error");
|
||||
})
|
||||
.register();
|
||||
mem::forget(listener);
|
||||
|
||||
let generic = Generic::new(main_loop.fd().as_raw_fd(), Interest::READ, Mode::Level);
|
||||
event_loop
|
||||
.insert_source(generic, move |_, _, _| {
|
||||
let _span = tracy_client::span!("pipewire iteration");
|
||||
main_loop.iterate(Duration::ZERO);
|
||||
Ok(PostAction::Continue)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(Self {
|
||||
_context: context,
|
||||
core,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_cast(
|
||||
&self,
|
||||
to_niri: calloop::channel::Sender<ToNiriMsg>,
|
||||
gbm: GbmDevice<DrmDeviceFd>,
|
||||
session_id: usize,
|
||||
output: Output,
|
||||
cursor_mode: CursorMode,
|
||||
signal_ctx: SignalContext<'static>,
|
||||
) -> anyhow::Result<Cast> {
|
||||
let _span = tracy_client::span!("PipeWire::start_cast");
|
||||
|
||||
let stop_cast = move || {
|
||||
if let Err(err) = to_niri.send(ToNiriMsg::StopCast { session_id }) {
|
||||
warn!("error sending StopCast to niri: {err:?}");
|
||||
}
|
||||
};
|
||||
|
||||
let mode = output.current_mode().unwrap();
|
||||
let size = mode.size;
|
||||
let refresh = mode.refresh;
|
||||
|
||||
let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new())
|
||||
.context("error creating Stream")?;
|
||||
|
||||
// Like in good old wayland-rs times...
|
||||
let stream = Rc::new(stream);
|
||||
let node_id = Rc::new(Cell::new(None));
|
||||
let is_active = Rc::new(Cell::new(false));
|
||||
let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO));
|
||||
let dmabufs = Rc::new(RefCell::new(HashMap::new()));
|
||||
|
||||
let listener = stream
|
||||
.add_local_listener_with_user_data(())
|
||||
.state_changed({
|
||||
let stream = stream.clone();
|
||||
let is_active = is_active.clone();
|
||||
let stop_cast = stop_cast.clone();
|
||||
move |old, new| {
|
||||
debug!("pw stream: state changed: {old:?} -> {new:?}");
|
||||
|
||||
match new {
|
||||
StreamState::Paused => {
|
||||
if node_id.get().is_none() {
|
||||
let id = stream.node_id();
|
||||
node_id.set(Some(id));
|
||||
debug!("pw stream: sending signal with {id}");
|
||||
|
||||
let _span = tracy_client::span!("sending PipeWireStreamAdded");
|
||||
async_io::block_on(async {
|
||||
let res = mutter_screen_cast::Stream::pipe_wire_stream_added(
|
||||
&signal_ctx,
|
||||
id,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = res {
|
||||
warn!("error sending PipeWireStreamAdded: {err:?}");
|
||||
stop_cast();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
is_active.set(false);
|
||||
}
|
||||
StreamState::Error(_) => {
|
||||
if is_active.get() {
|
||||
is_active.set(false);
|
||||
stop_cast();
|
||||
}
|
||||
}
|
||||
StreamState::Unconnected => (),
|
||||
StreamState::Connecting => (),
|
||||
StreamState::Streaming => {
|
||||
is_active.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.param_changed({
|
||||
let min_time_between_frames = min_time_between_frames.clone();
|
||||
move |stream, id, _data, pod| {
|
||||
let id = ParamType::from_raw(id);
|
||||
trace!(?id, "pw stream: param_changed");
|
||||
|
||||
if id != ParamType::Format {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(pod) = pod else { return };
|
||||
|
||||
let (m_type, m_subtype) = match parse_format(pod) {
|
||||
Ok(x) => x,
|
||||
Err(err) => {
|
||||
warn!("pw stream: error parsing format: {err:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if m_type != MediaType::Video || m_subtype != MediaSubtype::Raw {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut format = VideoInfoRaw::new();
|
||||
format.parse(pod).unwrap();
|
||||
trace!("pw stream: got format = {format:?}");
|
||||
|
||||
let max_frame_rate = format.max_framerate();
|
||||
// Subtract 0.5 ms to improve edge cases when equal to refresh rate.
|
||||
let min_frame_time = Duration::from_secs_f64(
|
||||
max_frame_rate.denom as f64 / max_frame_rate.num as f64,
|
||||
) - Duration::from_micros(500);
|
||||
min_time_between_frames.set(min_frame_time);
|
||||
|
||||
const BPP: u32 = 4;
|
||||
let stride = format.size().width * BPP;
|
||||
let size = stride * format.size().height;
|
||||
|
||||
let o1 = pod::object!(
|
||||
SpaTypes::ObjectParamBuffers,
|
||||
ParamType::Buffers,
|
||||
Property::new(
|
||||
SPA_PARAM_BUFFERS_buffers,
|
||||
pod::Value::Choice(ChoiceValue::Int(Choice(
|
||||
ChoiceFlags::empty(),
|
||||
ChoiceEnum::Range {
|
||||
default: 16,
|
||||
min: 2,
|
||||
max: 16
|
||||
}
|
||||
))),
|
||||
),
|
||||
Property::new(SPA_PARAM_BUFFERS_blocks, pod::Value::Int(1)),
|
||||
Property::new(SPA_PARAM_BUFFERS_size, pod::Value::Int(size as i32)),
|
||||
Property::new(SPA_PARAM_BUFFERS_stride, pod::Value::Int(stride as i32)),
|
||||
Property::new(SPA_PARAM_BUFFERS_align, pod::Value::Int(16)),
|
||||
Property::new(
|
||||
SPA_PARAM_BUFFERS_dataType,
|
||||
pod::Value::Choice(ChoiceValue::Int(Choice(
|
||||
ChoiceFlags::empty(),
|
||||
ChoiceEnum::Flags {
|
||||
default: 1 << DataType::DmaBuf.as_raw(),
|
||||
flags: vec![1 << DataType::DmaBuf.as_raw()],
|
||||
},
|
||||
))),
|
||||
),
|
||||
);
|
||||
|
||||
// FIXME: Hidden / embedded / metadata cursor
|
||||
|
||||
// let o2 = pod::object!(
|
||||
// SpaTypes::ObjectParamMeta,
|
||||
// ParamType::Meta,
|
||||
// Property::new(SPA_PARAM_META_type,
|
||||
// pod::Value::Id(Id(SPA_META_Header))),
|
||||
// Property::new(
|
||||
// SPA_PARAM_META_size,
|
||||
// pod::Value::Int(size_of::<spa_meta_header>() as i32)
|
||||
// ),
|
||||
// );
|
||||
let mut b1 = vec![];
|
||||
// let mut b2 = vec![];
|
||||
let mut params = [
|
||||
make_pod(&mut b1, o1).as_raw_ptr().cast_const(),
|
||||
// make_pod(&mut b2, o2).as_raw_ptr().cast_const(),
|
||||
];
|
||||
stream.update_params(&mut params).unwrap();
|
||||
}
|
||||
})
|
||||
.add_buffer({
|
||||
let dmabufs = dmabufs.clone();
|
||||
let stop_cast = stop_cast.clone();
|
||||
move |buffer| {
|
||||
trace!("pw stream: add_buffer");
|
||||
|
||||
unsafe {
|
||||
let spa_buffer = (*buffer).buffer;
|
||||
let spa_data = (*spa_buffer).datas;
|
||||
assert!((*spa_buffer).n_datas > 0);
|
||||
assert!((*spa_data).type_ & (1 << DataType::DmaBuf.as_raw()) > 0);
|
||||
|
||||
let bo = match gbm.create_buffer_object::<()>(
|
||||
size.w as u32,
|
||||
size.h as u32,
|
||||
Fourcc::Xrgb8888,
|
||||
GbmBufferFlags::RENDERING | GbmBufferFlags::LINEAR,
|
||||
) {
|
||||
Ok(bo) => bo,
|
||||
Err(err) => {
|
||||
warn!("error creating GBM buffer object: {err:?}");
|
||||
stop_cast();
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dmabuf = match bo.export() {
|
||||
Ok(dmabuf) => dmabuf,
|
||||
Err(err) => {
|
||||
warn!("error exporting GBM buffer object as dmabuf: {err:?}");
|
||||
stop_cast();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let fd = dmabuf.handles().next().unwrap().as_raw_fd();
|
||||
|
||||
(*spa_data).type_ = DataType::DmaBuf.as_raw();
|
||||
(*spa_data).maxsize = dmabuf.strides().next().unwrap() * size.h as u32;
|
||||
(*spa_data).fd = fd as i64;
|
||||
(*spa_data).flags = SPA_DATA_FLAG_READWRITE;
|
||||
|
||||
assert!(dmabufs.borrow_mut().insert(fd, dmabuf).is_none());
|
||||
}
|
||||
}
|
||||
})
|
||||
.remove_buffer({
|
||||
let dmabufs = dmabufs.clone();
|
||||
move |buffer| {
|
||||
trace!("pw stream: remove_buffer");
|
||||
|
||||
unsafe {
|
||||
let spa_buffer = (*buffer).buffer;
|
||||
let spa_data = (*spa_buffer).datas;
|
||||
assert!((*spa_buffer).n_datas > 0);
|
||||
|
||||
let fd = (*spa_data).fd as i32;
|
||||
dmabufs.borrow_mut().remove(&fd);
|
||||
}
|
||||
}
|
||||
})
|
||||
.register()
|
||||
.unwrap();
|
||||
|
||||
let object = pod::object!(
|
||||
SpaTypes::ObjectParamFormat,
|
||||
ParamType::EnumFormat,
|
||||
pod::property!(FormatProperties::MediaType, Id, MediaType::Video),
|
||||
pod::property!(FormatProperties::MediaSubtype, Id, MediaSubtype::Raw),
|
||||
pod::property!(FormatProperties::VideoFormat, Id, VideoFormat::BGRx),
|
||||
Property {
|
||||
key: FormatProperties::VideoModifier.as_raw(),
|
||||
value: pod::Value::Long(u64::from(Modifier::Invalid) as i64),
|
||||
flags: PropertyFlags::MANDATORY,
|
||||
},
|
||||
pod::property!(
|
||||
FormatProperties::VideoSize,
|
||||
Rectangle,
|
||||
Rectangle {
|
||||
width: size.w as u32,
|
||||
height: size.h as u32,
|
||||
}
|
||||
),
|
||||
pod::property!(
|
||||
FormatProperties::VideoFramerate,
|
||||
Fraction,
|
||||
Fraction { num: 0, denom: 1 }
|
||||
),
|
||||
pod::property!(
|
||||
FormatProperties::VideoMaxFramerate,
|
||||
Choice,
|
||||
Range,
|
||||
Fraction,
|
||||
Fraction {
|
||||
num: refresh as u32,
|
||||
denom: 1000
|
||||
},
|
||||
Fraction { num: 1, denom: 1 },
|
||||
Fraction {
|
||||
num: refresh as u32,
|
||||
denom: 1000
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let mut params = [make_pod(&mut buffer, object)];
|
||||
stream
|
||||
.connect(
|
||||
Direction::Output,
|
||||
None,
|
||||
StreamFlags::DRIVER | StreamFlags::ALLOC_BUFFERS,
|
||||
&mut params,
|
||||
)
|
||||
.context("error connecting stream")?;
|
||||
|
||||
let cast = Cast {
|
||||
session_id,
|
||||
stream,
|
||||
_listener: listener,
|
||||
is_active,
|
||||
output,
|
||||
cursor_mode,
|
||||
last_frame_time: Duration::ZERO,
|
||||
min_time_between_frames,
|
||||
dmabufs,
|
||||
};
|
||||
Ok(cast)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_pod(buffer: &mut Vec<u8>, object: pod::Object) -> &Pod {
|
||||
PodSerializer::serialize(Cursor::new(&mut *buffer), &pod::Value::Object(object)).unwrap();
|
||||
Pod::from_bytes(buffer).unwrap()
|
||||
}
|
Loading…
Reference in New Issue
Block a user