rosenthal: Add niri.

* rosenthal/packages/patches/niri.patch: New file.
* rosenthal/packages/patches/rust-libspa-sys.patch: New file.
* rosenthal/packages/patches/rust-libspa.patch: New file.
* rosenthal/packages/patches/rust-pipewire.patch: New file.
* rosenthal/packages/patches/rust-smithay.patch: New file.
* rosenthal/packages/rust-crates.scm (niri-cargo-inputs): New variable.
* rosenthal/packages/rust-apps.scm (niri): New variable.
* README.org (Packages): Add it.
* etc/manifest: Add it.
This commit is contained in:
Hilton Chain 2025-02-26 21:28:01 +08:00
parent 891eb84b38
commit f8c9cd9dcc
No known key found for this signature in database
GPG Key ID: ACC66D09CA528292
9 changed files with 5954 additions and 0 deletions

View File

@ -91,6 +91,7 @@ Rosenthal 頻道定義如下,將其加入 =~/.config/guix/channels.scm= 以由
+ emacs-wakatime-mode + emacs-wakatime-mode
+ forgejo + forgejo
+ grub-efi-luks2 + grub-efi-luks2
+ niri
+ pam-dumb-runtime-dir + pam-dumb-runtime-dir
+ socks2http + socks2http
+ tree-sitter-yaml + tree-sitter-yaml

View File

@ -12,6 +12,7 @@
"hugo-bin" "hugo-bin"
"komga-bin" "komga-bin"
"mihomo-bin" "mihomo-bin"
"niri"
"shadow-tls-bin" "shadow-tls-bin"
"sing-box-bin" "sing-box-bin"
"tailscale-bin" "tailscale-bin"

View File

@ -0,0 +1,31 @@
diff --git a/Cargo.toml b/Cargo.toml
index 6469c4e5..08d97316 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,15 +26,11 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracy-client = { version = "0.18.0", default-features = false }
[workspace.dependencies.smithay]
-# version = "0.4.1"
-git = "https://github.com/Smithay/smithay.git"
-# path = "../smithay"
+version = "0.4.1"
default-features = false
[workspace.dependencies.smithay-drm-extras]
-# version = "0.1.0"
-git = "https://github.com/Smithay/smithay.git"
-# path = "../smithay/smithay-drm-extras"
+version = "0.1.0"
[package]
name = "niri"
@@ -75,7 +71,7 @@ niri-ipc = { version = "25.2.0", path = "niri-ipc", features = ["clap"] }
ordered-float = "5.0.0"
pango = { version = "0.20.9", features = ["v1_44"] }
pangocairo = "0.20.7"
-pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", optional = true, features = ["v0_3_33"] }
+pipewire = { version = "0.8.0", optional = true, features = ["v0_3_33"] }
png = "0.17.16"
portable-atomic = { version = "1.10.0", default-features = false, features = ["float"] }
profiling = "1.0.16"

View File

@ -0,0 +1,54 @@
diff --git a/build.rs b/build.rs
index ea34c22..4bffe86 100644
--- a/build.rs
+++ b/build.rs
@@ -32,7 +32,7 @@ fn main() {
// can be called via FFI
.wrap_static_fns(true)
.wrap_static_fns_suffix("_libspa_rs")
- .wrap_static_fns_path(&out_path.join("static_fns"));
+ .wrap_static_fns_path(out_path.join("static_fns"));
let builder = libs
.iter()
diff --git a/src/type_info.rs b/src/type_info.rs
index 016c971..e1e60e9 100644
--- a/src/type_info.rs
+++ b/src/type_info.rs
@@ -101,9 +101,7 @@ mod test {
let type_info = super::spa_debug_type_find(spa_type_media_type, SPA_MEDIA_TYPE_audio);
assert_eq!(
ffi::CStr::from_ptr((*type_info).name),
- ffi::CString::new("Spa:Enum:MediaType:audio")
- .unwrap()
- .as_ref()
+ c"Spa:Enum:MediaType:audio"
);
}
}
@@ -112,12 +110,7 @@ mod test {
fn test_libspa_rs_debug_type_find_name() {
unsafe {
let name = super::spa_debug_type_find_name(spa_type_media_type, SPA_MEDIA_TYPE_audio);
- assert_eq!(
- ffi::CStr::from_ptr(name),
- ffi::CString::new("Spa:Enum:MediaType:audio")
- .unwrap()
- .as_ref()
- );
+ assert_eq!(ffi::CStr::from_ptr(name), c"Spa:Enum:MediaType:audio");
}
}
@@ -126,10 +119,7 @@ mod test {
unsafe {
let name =
super::spa_debug_type_find_short_name(spa_type_media_type, SPA_MEDIA_TYPE_audio);
- assert_eq!(
- ffi::CStr::from_ptr(name),
- ffi::CString::new("audio").unwrap().as_ref()
- );
+ assert_eq!(ffi::CStr::from_ptr(name), c"audio");
}
}
}

View File

@ -0,0 +1,960 @@
diff --git a/Cargo.toml b/Cargo.toml
index 1f2cc3e..df2d682 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,7 +49,7 @@ version = "0.3.2"
version = "0.2"
[dependencies.nix]
-version = "0.27"
+version = "0.29"
[dependencies.nom]
version = "7"
diff --git a/src/lib.rs b/src/lib.rs
index 398658b..6b7c995 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: MIT
//! The `libspa` crate provides a high-level API to interact with
-//! [libspa](https://gitlab.freedesktop.org/pipewire/pipewire/-/tree/master/doc/spa).
+//! [libspa].
+//!
+//! [libspa]: https://docs.pipewire.org/page_spa.html
pub mod buffer;
pub mod param;
@@ -11,6 +13,3 @@ pub mod support;
pub mod utils;
pub use spa_sys as sys;
-
-/// prelude module re-exporing all the traits providing public API.
-pub mod prelude {}
diff --git a/src/param/audio/mod.rs b/src/param/audio/mod.rs
index 017fc09..f6d731d 100644
--- a/src/param/audio/mod.rs
+++ b/src/param/audio/mod.rs
@@ -51,6 +51,17 @@ impl AudioFormat {
pub const ULAW: Self = Self(spa_sys::SPA_AUDIO_FORMAT_ULAW);
pub const ALAW: Self = Self(spa_sys::SPA_AUDIO_FORMAT_ALAW);
+ pub const S16: Self = Self(spa_sys::SPA_AUDIO_FORMAT_S16);
+ pub const U16: Self = Self(spa_sys::SPA_AUDIO_FORMAT_U16);
+ pub const S18: Self = Self(spa_sys::SPA_AUDIO_FORMAT_S18);
+ pub const U18: Self = Self(spa_sys::SPA_AUDIO_FORMAT_U18);
+ pub const S20: Self = Self(spa_sys::SPA_AUDIO_FORMAT_S20);
+ pub const U20: Self = Self(spa_sys::SPA_AUDIO_FORMAT_U20);
+ pub const S24: Self = Self(spa_sys::SPA_AUDIO_FORMAT_S24);
+ pub const U24: Self = Self(spa_sys::SPA_AUDIO_FORMAT_U24);
+ pub const S32: Self = Self(spa_sys::SPA_AUDIO_FORMAT_S32);
+ pub const U32: Self = Self(spa_sys::SPA_AUDIO_FORMAT_U32);
+
pub const U8P: Self = Self(spa_sys::SPA_AUDIO_FORMAT_U8P);
pub const S16P: Self = Self(spa_sys::SPA_AUDIO_FORMAT_S16P);
pub const S24_32P: Self = Self(spa_sys::SPA_AUDIO_FORMAT_S24_32P);
diff --git a/src/pod/builder.rs b/src/pod/builder.rs
index 138caec..d0209af 100644
--- a/src/pod/builder.rs
+++ b/src/pod/builder.rs
@@ -141,7 +141,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -163,7 +163,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -177,7 +177,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -189,7 +189,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -201,7 +201,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -213,7 +213,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -225,7 +225,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -237,7 +237,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -256,7 +256,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -274,7 +274,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -290,7 +290,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -302,7 +302,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -314,7 +314,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -326,7 +326,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -344,7 +344,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -370,7 +370,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -394,7 +394,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -412,7 +412,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -438,7 +438,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -449,7 +449,7 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -468,13 +468,14 @@ impl<'d> Builder<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
pub fn add_control(&mut self, offset: u32, type_: u32) -> c_int {
// Older versions of pipewire mistakenly had the return type as uint32_t,
// so we need to use try_into().unwrap() to ensure those versions also work
+ #[allow(clippy::useless_conversion)]
unsafe {
spa_sys::spa_pod_builder_control(self.as_raw_ptr(), offset, type_)
.try_into()
@@ -511,7 +512,7 @@ impl<'d> Builder<'d> {
/// Bytes(<&[u8]>),
/// }
/// );
-/// builder_add(<&mut libspa::pod::builder::Builder>,
+/// builder_add!(<&mut libspa::pod::builder::Builder>,
/// Object(
/// <type as u32>,
/// <id as u32>
diff --git a/src/pod/mod.rs b/src/pod/mod.rs
index 98c21ee..9f51ded 100644
--- a/src/pod/mod.rs
+++ b/src/pod/mod.rs
@@ -17,6 +17,7 @@ use std::{
io::{Seek, Write},
mem::MaybeUninit,
os::fd::RawFd,
+ ptr::addr_of,
};
use bitflags::bitflags;
@@ -89,7 +90,18 @@ impl Pod {
}
pub fn as_raw_ptr(&self) -> *mut spa_sys::spa_pod {
- std::ptr::addr_of!(self.0).cast_mut()
+ addr_of!(self.0).cast_mut()
+ }
+
+ /// Returns a pointer to the pods body.
+ ///
+ /// If the pod has an empty body, this can be outside the pods allocation.
+ pub fn body(&self) -> *mut c_void {
+ unsafe {
+ self.as_raw_ptr()
+ .byte_add(std::mem::size_of::<spa_sys::spa_pod>())
+ .cast()
+ }
}
/// Construct a pod from raw bytes.
@@ -158,7 +170,7 @@ impl Pod {
if res >= 0 {
Ok(b.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -176,7 +188,7 @@ impl Pod {
if res >= 0 {
Ok(Id(id.assume_init()))
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -194,7 +206,7 @@ impl Pod {
if res >= 0 {
Ok(int.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -212,7 +224,7 @@ impl Pod {
if res >= 0 {
Ok(long.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -230,7 +242,7 @@ impl Pod {
if res >= 0 {
Ok(float.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -248,7 +260,7 @@ impl Pod {
if res >= 0 {
Ok(double.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -278,7 +290,7 @@ impl Pod {
let bytes = std::slice::from_raw_parts(bytes.cast(), len.try_into().unwrap());
Ok(bytes)
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -303,7 +315,7 @@ impl Pod {
let pointer = pointer.assume_init();
Ok((pointer, _type))
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -323,7 +335,7 @@ impl Pod {
let fd: RawFd = fd.try_into().unwrap();
Ok(fd)
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -341,7 +353,7 @@ impl Pod {
if res >= 0 {
Ok(rectangle.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -359,7 +371,7 @@ impl Pod {
if res >= 0 {
Ok(fraction.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -384,17 +396,324 @@ impl Pod {
res != 0
}
+ pub fn as_struct(&self) -> Result<&PodStruct, Errno> {
+ if self.is_struct() {
+ // Safety: We already know that the pod is valid, and since it is a struct, we can
+ // safely create a PodStruct from it
+ Ok(unsafe { PodStruct::from_raw(self.as_raw_ptr() as *const spa_sys::spa_pod_struct) })
+ } else {
+ Err(Errno::EINVAL)
+ }
+ }
+
pub fn is_object(&self) -> bool {
let res = unsafe { spa_sys::spa_pod_is_object(self.as_raw_ptr()) };
res != 0
}
+ // TODO: spa_pod_is_object_type, spa_pod_is_object_id
+
+ pub fn as_object(&self) -> Result<&PodObject, Errno> {
+ if self.is_object() {
+ // Safety: We already know that the pod is valid, and since it is an object, we can
+ // safely create a PodObject from it
+ Ok(unsafe { PodObject::from_raw(self.as_raw_ptr() as *const spa_sys::spa_pod_object) })
+ } else {
+ Err(Errno::EINVAL)
+ }
+ }
+
pub fn is_sequence(&self) -> bool {
let res = unsafe { spa_sys::spa_pod_is_sequence(self.as_raw_ptr()) };
res != 0
}
}
+impl<'p> From<&'p PodStruct> for &'p Pod {
+ fn from(value: &'p PodStruct) -> Self {
+ value.as_pod()
+ }
+}
+
+impl<'p> From<&'p PodObject> for &'p Pod {
+ fn from(value: &'p PodObject) -> Self {
+ value.as_pod()
+ }
+}
+
+/// A transparent wrapper around a `spa_sys::spa_pod_struct`.
+#[repr(transparent)]
+pub struct PodStruct(spa_sys::spa_pod_struct);
+
+impl PodStruct {
+ /// # Safety
+ ///
+ /// The provided pointer must point to a valid, well-aligned pod of type struct.
+ ///
+ /// All restrictions from [`Pod::from_raw`] also apply here.
+ pub unsafe fn from_raw(pod: *const spa_sys::spa_pod_struct) -> &'static Self {
+ pod.cast::<Self>().as_ref().unwrap()
+ }
+
+ /// # Safety
+ ///
+ /// The provided pointer must point to a valid, well-aligned pod of type struct.
+ ///
+ /// All restrictions from [`Pod::from_raw_mut`] also apply here.
+ pub unsafe fn from_raw_mut(pod: *mut spa_sys::spa_pod_struct) -> &'static mut Self {
+ pod.cast::<Self>().as_mut().unwrap()
+ }
+
+ pub fn as_raw_ptr(&self) -> *mut spa_sys::spa_pod_struct {
+ std::ptr::addr_of!(self.0).cast_mut()
+ }
+
+ pub fn as_pod(&self) -> &Pod {
+ // Safety: Since this is a valid spa_pod_object, it must also be a valid spa_pod
+ unsafe { Pod::from_raw(addr_of!(self.0.pod)) }
+ }
+
+ pub fn fields(&self) -> PodStructIter<'_> {
+ PodStructIter::new(self)
+ }
+}
+
+impl<'p> TryFrom<&'p Pod> for &'p PodStruct {
+ type Error = Errno;
+
+ fn try_from(value: &'p Pod) -> Result<Self, Self::Error> {
+ value.as_struct()
+ }
+}
+
+impl AsRef<Pod> for PodStruct {
+ fn as_ref(&self) -> &Pod {
+ self.as_pod()
+ }
+}
+
+pub struct PodStructIter<'s> {
+ struct_pod: &'s PodStruct,
+ next: *mut c_void,
+}
+
+impl<'s> PodStructIter<'s> {
+ fn new(struct_pod: &'s PodStruct) -> Self {
+ let first_field = struct_pod.as_pod().body();
+
+ Self {
+ struct_pod,
+ next: first_field,
+ }
+ }
+}
+
+impl<'s> Iterator for PodStructIter<'s> {
+ type Item = &'s Pod;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // Check if the iterator has at least one element left that we can return
+ let has_next = unsafe {
+ spa_sys::spa_pod_is_inside(
+ self.struct_pod.as_pod().body(),
+ self.struct_pod.0.pod.size,
+ self.next,
+ )
+ };
+
+ if has_next {
+ let res = unsafe { Pod::from_raw(self.next as *const spa_sys::spa_pod) };
+
+ // Advance iter to next property
+ self.next = unsafe { spa_sys::spa_pod_next(self.next) };
+
+ Some(res)
+ } else {
+ None
+ }
+ }
+}
+
+/// A transparent wrapper around a `spa_sys::spa_pod_object`.
+#[repr(transparent)]
+pub struct PodObject(spa_sys::spa_pod_object);
+
+impl PodObject {
+ /// # Safety
+ ///
+ /// The provided pointer must point to a valid, well-aligned pod of type object.
+ ///
+ /// All restrictions from [`Pod::from_raw`] also apply here.
+ pub unsafe fn from_raw(pod: *const spa_sys::spa_pod_object) -> &'static Self {
+ pod.cast::<Self>().as_ref().unwrap()
+ }
+
+ /// # Safety
+ ///
+ /// The provided pointer must point to a valid, well-aligned pod of type object.
+ ///
+ /// All restrictions from [`Pod::from_raw_mut`] also apply here.
+ pub unsafe fn from_raw_mut(pod: *mut spa_sys::spa_pod_object) -> &'static mut Self {
+ pod.cast::<Self>().as_mut().unwrap()
+ }
+
+ pub fn as_raw_ptr(&self) -> *mut spa_sys::spa_pod_object {
+ std::ptr::addr_of!(self.0).cast_mut()
+ }
+
+ pub fn as_pod(&self) -> &Pod {
+ // Safety: Since this is a valid spa_pod_object, it must also be a valid spa_pod
+ unsafe { Pod::from_raw(addr_of!(self.0.pod)) }
+ }
+
+ pub fn type_(&self) -> SpaTypes {
+ SpaTypes::from_raw(self.0.body.type_)
+ }
+
+ pub fn id(&self) -> Id {
+ Id(self.0.body.id)
+ }
+
+ pub fn props(&self) -> PodObjectIter<'_> {
+ PodObjectIter::new(self)
+ }
+
+ pub fn find_prop(&self, /* TODO: start, */ key: Id) -> Option<&PodProp> {
+ let prop = unsafe {
+ spa_sys::spa_pod_object_find_prop(self.as_raw_ptr(), std::ptr::null(), key.0)
+ };
+
+ if !prop.is_null() {
+ unsafe { Some(PodProp::from_raw(prop)) }
+ } else {
+ None
+ }
+ }
+
+ pub fn fixate(&mut self) {
+ let _res = unsafe { spa_sys::spa_pod_object_fixate(self.as_raw_ptr()) };
+ // C implementation always returns 0
+ }
+
+ #[cfg(feature = "v0_3_40")]
+ pub fn is_fixated(&self) -> bool {
+ let res = unsafe { spa_sys::spa_pod_object_is_fixated(self.as_raw_ptr()) };
+ res != 0
+ }
+}
+
+impl<'p> TryFrom<&'p Pod> for &'p PodObject {
+ type Error = Errno;
+
+ fn try_from(value: &'p Pod) -> Result<Self, Self::Error> {
+ value.as_object()
+ }
+}
+
+impl AsRef<Pod> for PodObject {
+ fn as_ref(&self) -> &Pod {
+ self.as_pod()
+ }
+}
+
+pub struct PodObjectIter<'o> {
+ object: &'o PodObject,
+ next: *mut spa_sys::spa_pod_prop,
+}
+
+impl<'o> PodObjectIter<'o> {
+ fn new(object: &'o PodObject) -> Self {
+ let first_prop = unsafe { spa_sys::spa_pod_prop_first(addr_of!(object.0.body)) };
+
+ Self {
+ object,
+ next: first_prop,
+ }
+ }
+}
+
+impl<'o> Iterator for PodObjectIter<'o> {
+ type Item = &'o PodProp;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // Check if the iterator has at least one element left that we can return
+ let has_next = unsafe {
+ spa_sys::spa_pod_prop_is_inside(
+ addr_of!(self.object.0.body),
+ self.object.0.pod.size,
+ self.next,
+ )
+ };
+
+ if has_next {
+ let res = unsafe { PodProp::from_raw(self.next.cast_const()) };
+
+ // Advance iter to next property
+ self.next = unsafe { spa_sys::spa_pod_prop_next(self.next) };
+
+ Some(res)
+ } else {
+ None
+ }
+ }
+}
+
+bitflags! {
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ pub struct PodPropFlags: u32 {
+ const READONLY = spa_sys::SPA_POD_PROP_FLAG_READONLY;
+ const HARDWARE = spa_sys::SPA_POD_PROP_FLAG_HARDWARE;
+ const HINT_DICT = spa_sys::SPA_POD_PROP_FLAG_HINT_DICT;
+ const MANDATORY = spa_sys::SPA_POD_PROP_FLAG_MANDATORY;
+ const DONT_FIXATE = spa_sys::SPA_POD_PROP_FLAG_DONT_FIXATE;
+ }
+}
+
+/// A transparent wrapper around a `spa_sys::spa_pod_prop`.
+#[repr(transparent)]
+pub struct PodProp(spa_sys::spa_pod_prop);
+
+impl PodProp {
+ /// # Safety
+ ///
+ /// The provided pointer must point to a valid, well-aligned [`spa_sys::spa_pod_prop`].
+ ///
+ /// While this struct doesn't represent a full pod, all restrictions from [`Pod::from_raw`] also apply
+ /// to this struct and the contained `value` pod.
+ pub unsafe fn from_raw(prop: *const spa_sys::spa_pod_prop) -> &'static Self {
+ prop.cast::<Self>().as_ref().unwrap()
+ }
+
+ /// # Safety
+ ///
+ /// The provided pointer must point to a valid, well-aligned pod of type object.
+ ///
+ /// While this struct doesn't represent a full pod, all restrictions from [`Pod::from_raw`] also apply
+ /// to this struct and the contained `value` pod.
+ pub unsafe fn from_raw_mut(prop: *mut spa_sys::spa_pod_prop) -> &'static mut Self {
+ prop.cast::<Self>().as_mut().unwrap()
+ }
+
+ pub fn as_raw_ptr(&self) -> *mut spa_sys::spa_pod_prop {
+ std::ptr::addr_of!(self.0).cast_mut()
+ }
+
+ pub fn key(&self) -> Id {
+ Id(self.0.key)
+ }
+
+ pub fn flags(&self) -> PodPropFlags {
+ PodPropFlags::from_bits_retain(self.0.flags)
+ }
+
+ pub fn value(&self) -> &Pod {
+ // Safety: Since PodProp may only be constructed around valid Pods, the contained value must also be valid.
+ // We don't mutate the pod and neither can the returned reference.
+ // The returned lifetime is properly shortened by this methods signature.
+ unsafe { Pod::from_raw(addr_of!(self.0.value)) }
+ }
+}
+
/// Implementors of this trait are the canonical representation of a specific type of fixed sized SPA pod.
///
/// They can be used as an output type for [`FixedSizedPod`] implementors
diff --git a/src/pod/parser.rs b/src/pod/parser.rs
index a909d57..7203ce5 100644
--- a/src/pod/parser.rs
+++ b/src/pod/parser.rs
@@ -134,7 +134,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -145,7 +145,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(b.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -157,7 +157,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(Id(id.assume_init()))
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -169,7 +169,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(int.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -181,7 +181,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(long.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -193,7 +193,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(float.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -205,7 +205,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(double.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -220,7 +220,7 @@ impl<'d> Parser<'d> {
let string = CStr::from_ptr(string);
Ok(string)
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -241,7 +241,7 @@ impl<'d> Parser<'d> {
let bytes = std::slice::from_raw_parts(bytes, len.try_into().unwrap());
Ok(bytes)
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -258,7 +258,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok((ptr.assume_init(), Id(type_.assume_init())))
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -270,7 +270,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(fd.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -282,7 +282,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(rect.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -294,7 +294,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(frac.assume_init())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -311,7 +311,7 @@ impl<'d> Parser<'d> {
Ok(pod)
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
@@ -329,7 +329,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(())
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
@@ -353,7 +353,7 @@ impl<'d> Parser<'d> {
if res >= 0 {
Ok(Id(id.assume_init()))
} else {
- Err(Errno::from_i32(-res))
+ Err(Errno::from_raw(-res))
}
}
}
diff --git a/src/utils/dict.rs b/src/utils/dict.rs
index 7c02cff..4e99133 100644
--- a/src/utils/dict.rs
+++ b/src/utils/dict.rs
@@ -102,7 +102,6 @@ impl DictRef {
///
/// # Examples
/// ```
- /// use libspa::prelude::*;
/// use libspa::{utils::dict::StaticDict, static_dict};
///
/// static DICT: StaticDict = static_dict! {
@@ -145,9 +144,7 @@ impl std::fmt::Debug for DictRef {
impl<'a> fmt::Debug for Entries<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_map()
- .entries(self.0.clone().map(|(k, v)| (k, v)))
- .finish()
+ f.debug_map().entries(self.0.clone()).finish()
}
}
@@ -392,7 +389,7 @@ macro_rules! static_dict {
};
unsafe {
- let ptr = &RAW as *const _ as *mut _;
+ let ptr = std::ptr::addr_of!(RAW).cast_mut();
StaticDict::from_ptr(ptr::NonNull::new_unchecked(ptr))
}
}};
@@ -421,7 +418,7 @@ unsafe impl Sync for StaticDict {}
mod tests {
use super::{DictRef, Flags, StaticDict};
use spa_sys::spa_dict;
- use std::{ffi::CString, ptr};
+ use std::ptr;
#[test]
fn test_empty_dict() {
@@ -447,20 +444,8 @@ mod tests {
};
let mut iter = dict.iter_cstr();
- assert_eq!(
- (
- CString::new("K0").unwrap().as_c_str(),
- CString::new("V0").unwrap().as_c_str()
- ),
- iter.next().unwrap()
- );
- assert_eq!(
- (
- CString::new("K1").unwrap().as_c_str(),
- CString::new("V1").unwrap().as_c_str()
- ),
- iter.next().unwrap()
- );
+ assert_eq!((c"K0", c"V0"), iter.next().unwrap());
+ assert_eq!((c"K1", c"V1"), iter.next().unwrap());
assert_eq!(None, iter.next());
}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index c0c4051..6aa83f2 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -144,7 +144,7 @@ impl SpaTypes {
Self(raw)
}
- /// Get the raw [`c_uint`](std::os::raw::c_uint) representing this `SpaTypes`.
+ /// Get the raw [`c_uint`] representing this `SpaTypes`.
pub fn as_raw(&self) -> c_uint {
self.0
}
diff --git a/src/utils/result.rs b/src/utils/result.rs
index 83d1e87..8d06636 100644
--- a/src/utils/result.rs
+++ b/src/utils/result.rs
@@ -133,7 +133,7 @@ impl Error {
fn new(e: i32) -> Self {
assert!(e > 0);
- Self(Errno::from_i32(e))
+ Self(Errno::from_raw(e))
}
}

View File

@ -0,0 +1,345 @@
diff --git a/Cargo.toml b/Cargo.toml
index bef8079..f795a3e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,7 +45,7 @@ version = "2"
version = "0.2"
[dependencies.nix]
-version = "0.27"
+version = "0.29"
features = [
"signal",
"fs",
diff --git a/src/channel.rs b/src/channel.rs
index 3c8c742..672cddc 100644
--- a/src/channel.rs
+++ b/src/channel.rs
@@ -89,7 +89,11 @@ impl<T: 'static> Receiver<T> {
F: Fn(T) + 'static,
{
let channel = self.channel.clone();
- let readfd = channel.lock().expect("Channel mutex lock poisoned").readfd;
+ let readfd = channel
+ .lock()
+ .expect("Channel mutex lock poisoned")
+ .readfd
+ .as_raw_fd();
// Attach the pipe as an IO source to the loop.
// Whenever the pipe is written to, call the users callback with each message in the queue.
@@ -97,7 +101,7 @@ impl<T: 'static> Receiver<T> {
let mut channel = channel.lock().expect("Channel mutex lock poisoned");
// Read from the pipe to make it block until written to again.
- let _ = nix::unistd::read(channel.readfd, &mut [0]);
+ let _ = nix::unistd::read(channel.readfd.as_raw_fd(), &mut [0]);
channel.queue.drain(..).for_each(&callback);
});
@@ -162,7 +166,7 @@ impl<T> Sender<T> {
// If no messages are waiting already, signal the receiver to read some.
// Because the channel mutex is locked, it is alright to do this before pushing the message.
if channel.queue.is_empty() {
- match nix::unistd::write(channel.writefd, &[1u8]) {
+ match nix::unistd::write(&channel.writefd, &[1u8]) {
Ok(_) => (),
Err(_) => return Err(t),
}
@@ -178,21 +182,12 @@ impl<T> Sender<T> {
/// Shared state between the [`Sender`]s and the [`Receiver`].
struct Channel<T> {
/// A pipe used to signal the loop the receiver is attached to that messages are waiting.
- readfd: RawFd,
- writefd: RawFd,
+ readfd: OwnedFd,
+ writefd: OwnedFd,
/// Queue of any messages waiting to be received.
queue: VecDeque<T>,
}
-impl<T> Drop for Channel<T> {
- fn drop(&mut self) {
- // We do not error check here, because the pipe does not contain any data that might be lost,
- // and because there is no way to handle an error in a `Drop` implementation anyways.
- let _ = nix::unistd::close(self.readfd);
- let _ = nix::unistd::close(self.writefd);
- }
-}
-
/// Create a Sender-Receiver pair, where the sender can be used to send messages to the receiver.
///
/// This functions similar to [`std::sync::mpsc`], but with a receiver that can be attached to any
diff --git a/src/client.rs b/src/client.rs
index cd28024..89f71e7 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -5,7 +5,10 @@ use bitflags::bitflags;
use libc::c_void;
use std::ops::Deref;
use std::pin::Pin;
-use std::{ffi::CString, ptr};
+use std::{
+ ffi::{CStr, CString},
+ ptr,
+};
use std::{fmt, mem};
use crate::{
@@ -53,7 +56,11 @@ impl Client {
pub fn error(&self, id: u32, res: i32, message: &str) {
let message = CString::new(message).expect("Null byte in message parameter");
+ let message_cstr = message.as_c_str();
+ Client::error_cstr(self, id, res, message_cstr)
+ }
+ pub fn error_cstr(&self, id: u32, res: i32, message: &CStr) {
unsafe {
spa_interface_call_method!(
self.proxy.as_ptr(),
diff --git a/src/core.rs b/src/core.rs
index 960c3c2..1feb79f 100644
--- a/src/core.rs
+++ b/src/core.rs
@@ -126,8 +126,17 @@ impl CoreRef {
factory_name: &str,
properties: &impl AsRef<spa::utils::dict::DictRef>,
) -> Result<P, Error> {
- let type_ = P::type_();
let factory_name = CString::new(factory_name).expect("Null byte in factory_name parameter");
+ let factory_name_cstr = factory_name.as_c_str();
+ CoreRef::create_object_cstr(self, factory_name_cstr, properties)
+ }
+
+ pub fn create_object_cstr<P: ProxyT>(
+ &self,
+ factory_name: &CStr,
+ properties: &impl AsRef<spa::utils::dict::DictRef>,
+ ) -> Result<P, Error> {
+ let type_ = P::type_();
let type_str = CString::new(type_.to_string())
.expect("Null byte in string representation of type_ parameter");
diff --git a/src/lib.rs b/src/lib.rs
index e63490c..7f100df 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -140,12 +140,6 @@ mod utils;
pub use pw_sys as sys;
pub use spa;
-// Re-export all the traits in a prelude module, so that applications
-// can always "use pipewire::prelude::*" without getting conflicts
-pub mod prelude {
- pub use spa::prelude::*;
-}
-
use std::ptr;
/// Initialize PipeWire
diff --git a/src/loop_.rs b/src/loop_.rs
index 01560c4..3dfa8bc 100644
--- a/src/loop_.rs
+++ b/src/loop_.rs
@@ -635,7 +635,14 @@ impl<'l> TimerSource<'l> {
fn duration_to_timespec(duration: Duration) -> spa_sys::timespec {
spa_sys::timespec {
tv_sec: duration.as_secs().try_into().expect("Duration too long"),
- tv_nsec: duration.subsec_nanos().try_into().unwrap(),
+ // `Into` is only implemented on some platforms for these types,
+ // so use a fallible conversion.
+ // As there are a limited amount of nanoseconds in a second, this shouldn't fail
+ #[allow(clippy::unnecessary_fallible_conversions)]
+ tv_nsec: duration
+ .subsec_nanos()
+ .try_into()
+ .expect("Nanoseconds should fit into timespec"),
}
}
diff --git a/src/metadata.rs b/src/metadata.rs
index 64a68e1..ebdc3e7 100644
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -55,6 +55,18 @@ impl Metadata {
let key = CString::new(key).expect("Invalid byte in metadata key");
let type_ = type_.map(|t| CString::new(t).expect("Invalid byte in metadata type"));
let value = value.map(|v| CString::new(v).expect("Invalid byte in metadata value"));
+ let key_cstr = key.as_c_str();
+
+ Metadata::set_property_cstr(self, subject, key_cstr, type_.as_deref(), value.as_deref())
+ }
+
+ pub fn set_property_cstr(
+ &self,
+ subject: u32,
+ key: &CStr,
+ type_: Option<&CStr>,
+ value: Option<&CStr>,
+ ) {
unsafe {
spa::spa_interface_call_method!(
self.proxy.as_ptr(),
@@ -62,8 +74,8 @@ impl Metadata {
set_property,
subject,
key.as_ptr() as *const _,
- type_.as_deref().map_or_else(ptr::null, CStr::as_ptr) as *const _,
- value.as_deref().map_or_else(ptr::null, CStr::as_ptr) as *const _
+ type_.map_or_else(ptr::null, CStr::as_ptr) as *const _,
+ value.map_or_else(ptr::null, CStr::as_ptr) as *const _
);
}
}
diff --git a/src/permissions.rs b/src/permissions.rs
index bdb011c..7d445ad 100644
--- a/src/permissions.rs
+++ b/src/permissions.rs
@@ -16,17 +16,33 @@ bitflags! {
}
}
+#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Permission(pw_sys::pw_permission);
impl Permission {
+ pub fn new(id: u32, flags: PermissionFlags) -> Self {
+ Self(pw_sys::pw_permission {
+ id,
+ permissions: flags.bits(),
+ })
+ }
+
pub fn id(&self) -> u32 {
self.0.id
}
+ pub fn set_id(&mut self, id: u32) {
+ self.0.id = id;
+ }
+
pub fn permission_flags(&self) -> PermissionFlags {
PermissionFlags::from_bits_retain(self.0.permissions)
}
+
+ pub fn set_permission_flags(&mut self, flags: PermissionFlags) {
+ self.0.permissions = flags.bits();
+ }
}
impl fmt::Debug for Permission {
diff --git a/src/properties.rs b/src/properties.rs
index c71cf72..008bed6 100644
--- a/src/properties.rs
+++ b/src/properties.rs
@@ -1,4 +1,10 @@
-use std::{ffi::CString, fmt, mem::ManuallyDrop, ops::Deref, ptr};
+use std::{
+ ffi::{CStr, CString},
+ fmt,
+ mem::ManuallyDrop,
+ ops::Deref,
+ ptr,
+};
/// A collection of key/value pairs.
///
@@ -36,7 +42,6 @@ pub struct Properties {
///
/// Any expression that evaluates to a `impl Into<Vec<u8>>` can be used for both keys and values.
/// ```rust
-/// use pipewire::prelude::*;
/// use pipewire::properties::properties;
///
/// let key = String::from("Key");
@@ -149,6 +154,19 @@ impl Clone for Properties {
}
}
+impl<K, V> FromIterator<(K, V)> for Properties
+where
+ K: Into<Vec<u8>>,
+ V: Into<Vec<u8>>,
+{
+ fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
+ let mut props = Self::new();
+ props.extend(iter);
+
+ props
+ }
+}
+
impl Drop for Properties {
fn drop(&mut self) {
unsafe { pw_sys::pw_properties_free(self.ptr.as_ptr()) }
@@ -196,6 +214,11 @@ impl PropertiesRef {
pub fn get(&self, key: &str) -> Option<&str> {
let key = CString::new(key).expect("key contains null byte");
+ let key_cstr = key.as_c_str();
+ PropertiesRef::get_cstr(self, key_cstr)
+ }
+
+ pub fn get_cstr(&self, key: &CStr) -> Option<&str> {
let res =
unsafe { pw_sys::pw_properties_get(self.as_raw_ptr().cast_const(), key.as_ptr()) };
@@ -245,6 +268,18 @@ impl fmt::Debug for PropertiesRef {
}
}
+impl<K, V> Extend<(K, V)> for PropertiesRef
+where
+ K: Into<Vec<u8>>,
+ V: Into<Vec<u8>>,
+{
+ fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
+ for (k, v) in iter {
+ self.insert(k, v);
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src/stream.rs b/src/stream.rs
index ff28af8..e04ff64 100644
--- a/src/stream.rs
+++ b/src/stream.rs
@@ -64,6 +64,13 @@ impl Stream {
/// Initialises a new stream with the given `name` and `properties`.
pub fn new(core: &Core, name: &str, properties: Properties) -> Result<Self, Error> {
let name = CString::new(name).expect("Invalid byte in stream name");
+
+ let c_str = name.as_c_str();
+ Stream::new_cstr(core, c_str, properties)
+ }
+
+ /// Initialises a new stream with the given `name` as Cstr and `properties`.
+ pub fn new_cstr(core: &Core, name: &CStr, properties: Properties) -> Result<Self, Error> {
let stream = unsafe {
pw_sys::pw_stream_new(core.as_raw_ptr(), name.as_ptr(), properties.into_raw())
};
@@ -249,8 +256,18 @@ impl StreamRef {
///
pub fn set_error(&mut self, res: i32, error: &str) {
let error = CString::new(error).expect("failed to convert error to CString");
+ let error_cstr = error.as_c_str();
+ StreamRef::set_error_cstr(self, res, error_cstr)
+ }
+
+ /// Set the stream in error state with CStr
+ ///
+ /// # Panics
+ /// Will panic if `error` contains a 0 byte.
+ ///
+ pub fn set_error_cstr(&mut self, res: i32, error: &CStr) {
unsafe {
- pw_sys::pw_stream_set_error(self.as_raw_ptr(), res, error.as_c_str().as_ptr());
+ pw_sys::pw_stream_set_error(self.as_raw_ptr(), res, error.as_ptr());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,17 @@
#:use-module (guix download) #:use-module (guix download)
#:use-module (guix git-download) #:use-module (guix git-download)
#:use-module (guix build-system cargo) #:use-module (guix build-system cargo)
#:use-module (gnu packages admin)
#:use-module (gnu packages freedesktop)
#:use-module (gnu packages gl)
#:use-module (gnu packages glib)
#:use-module (gnu packages gtk)
#:use-module (gnu packages linux)
#:use-module (gnu packages llvm)
#:use-module (gnu packages pkg-config)
#:use-module (gnu packages wm)
#:use-module (gnu packages xdisorg)
#:use-module (rosenthal packages)
#:use-module (rosenthal packages rust-crates)) #:use-module (rosenthal packages rust-crates))
(define-public atuin (define-public atuin
@ -56,3 +67,60 @@
additional context for commands. Additionally, it provides optional and fully additional context for commands. Additionally, it provides optional and fully
encrypted synchronisation of history between machines, via an Atuin server.") encrypted synchronisation of history between machines, via an Atuin server.")
(license license:gpl3))) (license license:gpl3)))
(define-public niri
(package
(name "niri")
(version "25.02")
(source (origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/YaLTeR/niri")
(commit (string-append "v" version))))
(file-name (git-file-name name version))
(sha256
(base32
"0vzskaalcz6pcml687n54adjddzgf5r07gggc4fhfsa08h1wfd4r"))
(patches (list (local-file "./patches/niri.patch")))))
(build-system cargo-build-system)
(arguments
(list #:install-source? #f
#:phases
#~(modify-phases %standard-phases
(add-after 'configure 'set-rust-flags
(lambda _
(setenv "RUSTFLAGS" (string-join
'("-C" "link-arg=-lEGL"
"-C" "link-arg=-lwayland-client")
" "))))
(add-before 'check 'prepare-test-environment
(lambda _
(setenv "XDG_RUNTIME_DIR" "/tmp")))
(add-after 'install 'install-extras
(lambda _
(install-file
"resources/niri.desktop"
(in-vicinity #$output "share/wayland-sessions"))
(install-file
"resources/niri-portals.conf"
(in-vicinity #$output "share/xdg-desktop-portal")))))))
(native-inputs
(list pkg-config))
(inputs
(cons* clang
libdisplay-info
libinput-minimal
libseat
libxkbcommon
mesa
pango
pipewire
wayland
(force niri-cargo-inputs)))
(home-page "https://github.com/YaLTeR/niri")
(synopsis "Scrollable-tiling Wayland compositor")
(description
"Niri is a scrollable-tiling Wayland compositor which arranges windows in a
scrollable format. It is considered stable for daily use and performs most
functions expected of a Wayland compositor.")
(license license:gpl3)))

File diff suppressed because it is too large Load Diff