ipc: Add screencast request and events for PipeWire casts

Allows desktop bars to show when screen recording is active.
This commit is contained in:
Ivan Molodetskikh
2026-01-12 18:25:31 +03:00
parent 9c79108afa
commit 238caaf8da
6 changed files with 259 additions and 3 deletions
+68
View File
@@ -117,6 +117,8 @@ pub enum Request {
ReturnError,
/// Request information about the overview.
OverviewState,
/// Request information about screencasts.
Casts,
}
/// Reply from niri to client.
@@ -161,6 +163,8 @@ pub enum Response {
OutputConfigChanged(OutputConfigChanged),
/// Information about the overview.
OverviewState(Overview),
/// Information about screencasts.
Casts(Vec<Cast>),
}
/// Overview information.
@@ -1473,6 +1477,52 @@ pub struct LayerSurface {
pub keyboard_interactivity: LayerSurfaceKeyboardInteractivity,
}
/// A screencast.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct Cast {
/// Stream ID of the screencast that uniquely identifies it.
pub stream_id: u64,
/// Session ID of the screencast.
///
/// A session can have multiple screencast streams. Then multiple `Cast`s will have the same
/// `session_id`. Though, usually there's only one stream per session.
///
/// Do not confuse `session_id` with [`stream_id`](Self::stream_id).
pub session_id: u64,
/// Target being captured.
pub target: CastTarget,
/// Whether this is a Dynamic Cast Target screencast.
///
/// Meaning that actions like `SetDynamicCastWindow` will act on this screencast.
///
/// Keep in mind that the target can change even if this is `false`.
pub is_dynamic_target: bool,
/// Whether the cast is currently streaming frames.
///
/// This can be `false` for example when switching away to a different scene in OBS, which
/// pauses the stream.
pub is_active: bool,
}
/// Target of a screencast.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum CastTarget {
/// The target is not yet set, or was cleared.
Nothing {},
/// Casting an output.
Output {
/// Name of the screencasted output.
name: String,
},
/// Casting a window.
Window {
/// ID of the screencasted window.
id: u64,
},
}
/// A compositor event.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
@@ -1595,6 +1645,24 @@ pub enum Event {
/// be converted to a `String` (e.g. contained invalid UTF-8 bytes).
path: Option<String>,
},
/// The screencasts have changed.
CastsChanged {
/// The new screencast information.
///
/// This configuration completely replaces the previous configuration. I.e. if any casts
/// are missing from here, then they were stopped.
casts: Vec<Cast>,
},
/// A screencast started, or an existing cast changed.
CastStartedOrChanged {
/// The cast that started or changed.
cast: Cast,
},
/// A screencast stopped.
CastStopped {
/// Stream ID of the stopped screencast.
stream_id: u64,
},
}
impl From<Duration> for Timestamp {
+37 -1
View File
@@ -9,7 +9,7 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use crate::{Event, KeyboardLayouts, Window, Workspace};
use crate::{Cast, Event, KeyboardLayouts, Window, Workspace};
/// Part of the state communicated via the event stream.
pub trait EventStreamStatePart {
@@ -46,6 +46,9 @@ pub struct EventStreamState {
/// State of the config.
pub config: ConfigState,
/// State of screencasts.
pub casts: CastsState,
}
/// The workspaces state communicated over the event stream.
@@ -83,6 +86,13 @@ pub struct ConfigState {
pub failed: bool,
}
/// The casts state communicated over the event stream.
#[derive(Debug, Default)]
pub struct CastsState {
/// Map from a stream id to the screencast.
pub casts: HashMap<u64, Cast>,
}
impl EventStreamStatePart for EventStreamState {
fn replicate(&self) -> Vec<Event> {
let mut events = Vec::new();
@@ -91,6 +101,7 @@ impl EventStreamStatePart for EventStreamState {
events.extend(self.keyboard_layouts.replicate());
events.extend(self.overview.replicate());
events.extend(self.config.replicate());
events.extend(self.casts.replicate());
events
}
@@ -100,6 +111,7 @@ impl EventStreamStatePart for EventStreamState {
let event = self.keyboard_layouts.apply(event)?;
let event = self.overview.apply(event)?;
let event = self.config.apply(event)?;
let event = self.casts.apply(event)?;
Some(event)
}
}
@@ -285,3 +297,27 @@ impl EventStreamStatePart for ConfigState {
None
}
}
impl EventStreamStatePart for CastsState {
fn replicate(&self) -> Vec<Event> {
let casts = self.casts.values().cloned().collect();
vec![Event::CastsChanged { casts }]
}
fn apply(&mut self, event: Event) -> Option<Event> {
match event {
Event::CastsChanged { casts } => {
self.casts = casts.into_iter().map(|c| (c.stream_id, c)).collect();
}
Event::CastStartedOrChanged { cast } => {
self.casts.insert(cast.stream_id, cast);
}
Event::CastStopped { stream_id } => {
let cast = self.casts.remove(&stream_id);
cast.expect("stopped cast was missing from the map");
}
event => return Some(event),
}
None
}
}