aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml8
-rw-r--r--Cargo.lock218
-rw-r--r--Cargo.toml4
-rw-r--r--src/backend/mod.rs19
-rw-r--r--src/backend/tty.rs24
-rw-r--r--src/backend/winit.rs13
-rw-r--r--src/config.rs4
-rw-r--r--src/dbus/mod.rs2
-rw-r--r--src/dbus/mutter_display_config.rs88
-rw-r--r--src/dbus/mutter_screen_cast.rs256
-rw-r--r--src/main.rs1
-rw-r--r--src/niri.rs329
-rw-r--r--src/pw_utils.rs382
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: |
diff --git a/Cargo.lock b/Cargo.lock
index e1a4800c..636cd0d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 6df39434..d959d6ab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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::