diff options
| -rw-r--r-- | .github/workflows/ci.yml | 8 | ||||
| -rw-r--r-- | Cargo.lock | 218 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | src/backend/mod.rs | 19 | ||||
| -rw-r--r-- | src/backend/tty.rs | 24 | ||||
| -rw-r--r-- | src/backend/winit.rs | 13 | ||||
| -rw-r--r-- | src/config.rs | 4 | ||||
| -rw-r--r-- | src/dbus/mod.rs | 2 | ||||
| -rw-r--r-- | src/dbus/mutter_display_config.rs | 88 | ||||
| -rw-r--r-- | src/dbus/mutter_screen_cast.rs | 256 | ||||
| -rw-r--r-- | src/main.rs | 1 | ||||
| -rw-r--r-- | src/niri.rs | 329 | ||||
| -rw-r--r-- | src/pw_utils.rs | 382 |
13 files changed, 1295 insertions, 53 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 679d3535..73af9f4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: | @@ -295,6 +295,26 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -406,6 +426,25 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -437,6 +476,17 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -498,6 +548,21 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -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]] @@ -921,6 +986,12 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1131,6 +1202,12 @@ 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" @@ -1138,6 +1215,16 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" @@ -1173,6 +1260,34 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -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", @@ -1615,6 +1732,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1633,6 +1756,34 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1876,6 +2027,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1958,6 +2115,15 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1978,6 +2144,12 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2052,7 +2224,7 @@ dependencies = [ "input", "lazy_static", "libc", - "libloading", + "libloading 0.8.0", "libseat", "nix 0.26.4", "once_cell", @@ -2176,6 +2348,25 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2270,10 +2461,25 @@ dependencies = [ ] [[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", ] @@ -2438,6 +2646,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -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"] diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 8a59e6f3..8473ccc2 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -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 diff --git a/src/backend/tty.rs b/src/backend/tty.rs index a3298655..343da54e 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -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 { diff --git a/src/backend/winit.rs b/src/backend/winit.rs index be8ec29a..e7a50ac7 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -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() + } } diff --git a/src/config.rs b/src/config.rs index 9d03275e..b624f88e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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() }, }, ); diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index 01b4c391..84a8bc30 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -1 +1,3 @@ +pub mod mutter_display_config; +pub mod mutter_screen_cast; pub mod mutter_service_channel; diff --git a/src/dbus/mutter_display_config.rs b/src/dbus/mutter_display_config.rs new file mode 100644 index 00000000..1807f01d --- /dev/null +++ b/src/dbus/mutter_display_config.rs @@ -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 } + } +} diff --git a/src/dbus/mutter_screen_cast.rs b/src/dbus/mutter_screen_cast.rs new file mode 100644 index 00000000..320319cc --- /dev/null +++ b/src/dbus/mutter_screen_cast.rs @@ -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:?}"); + } + } +} diff --git a/src/main.rs b/src/main.rs index 81422065..6c5e9ab0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod handlers; mod input; mod layout; mod niri; +mod pw_utils; mod utils; use std::env; diff --git a/src/niri.rs b/src/niri.rs index b4302a4c..a48879e9 100644 --- a/src/niri.rs +++ b/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:: |
