Add workspace screen sharing (#64)

Add workspace selection to the screen-sharing picker with pinned capture, privacy filtering and bounded capture resources. Refresh captured tab labels when title or app-ID metadata changes.
This commit is contained in:
Samuel Onoja authored and GitHub committed 2026-10-25 13:59:34 +01:00
1 parent e64ab52190
commit ab11269246
32 files changed
+2125 -165

No files matched your search

+86 -24
View File
@@ -32,7 +32,7 @@ pub(crate) fn sampled_output_elements(
include_cursor: bool,
frame: &crate::state::FrameScene,
) -> Vec<AnimatedWindowRenderElement> {
scene_elements(state, renderer, output, include_cursor, frame, false)
scene_elements(state, renderer, output, include_cursor, frame, false, None)
}
pub(crate) fn capture_output_elements(
@@ -46,11 +46,35 @@ pub(crate) fn capture_output_elements(
// display rendering. Only the capture scene can populate these caches.
crate::backends::direct::capture::touch_cache(state);
std::mem::swap(&mut state.render, &mut state.capture_render);
let elements = scene_elements(state, renderer, output, include_cursor, frame, true);
let elements = scene_elements(state, renderer, output, include_cursor, frame, true, None);
std::mem::swap(&mut state.render, &mut state.capture_render);
elements
}
/// The caller supplies resources owned by this capture view. No display or
/// other workspace's blur/snapshot cache may populate this scene.
pub(crate) fn capture_workspace_elements(
state: &mut Ferese,
renderer: &mut GlesRenderer,
view: &crate::handlers::workspace_capture::WorkspaceSource,
include_cursor: bool,
frame: &crate::state::FrameScene,
resources: &mut super::RenderResources,
) -> Vec<AnimatedWindowRenderElement> {
std::mem::swap(&mut state.render, resources);
let elements = scene_elements(
state,
renderer,
&view.render_output,
include_cursor,
frame,
true,
Some(view),
);
std::mem::swap(&mut state.render, resources);
elements
}
fn scene_elements(
state: &mut Ferese,
renderer: &mut GlesRenderer,
@@ -58,6 +82,7 @@ fn scene_elements(
include_cursor: bool,
frame: &crate::state::FrameScene,
capture: bool,
workspace: Option<&crate::handlers::workspace_capture::WorkspaceSource>,
) -> Vec<AnimatedWindowRenderElement> {
if state.session_lock.active() {
let Some(geometry) = state.space.output_geometry(output) else {
@@ -138,7 +163,10 @@ fn scene_elements(
);
return elements;
}
let Some(output_geometry) = state.space.output_geometry(output) else {
let Some(output_geometry) = workspace
.map(|view| view.geometry)
.or_else(|| state.space.output_geometry(output))
else {
return Vec::new();
};
let scale = output.current_scale().fractional_scale();
@@ -155,7 +183,9 @@ fn scene_elements(
} else {
&[Layer::Overlay, Layer::Top]
};
elements.extend(layer_elements(state, renderer, output, upper_layers));
if workspace.is_none() {
elements.extend(layer_elements(state, renderer, output, upper_layers));
}
if frame.overview.is_presenting() {
elements.extend(overview_strip_elements(
state,
@@ -170,14 +200,22 @@ fn scene_elements(
let prepare_window = |window: &Window| {
// Configure immediately, but present the frame/shadow only after
// the first buffer commit has settled the actual client geometry.
if (capture && state.capture_protected(window)) || !state.window_content_ready(window) {
let ready = if workspace.is_some() {
crate::state::window_has_buffer(window)
} else {
state.window_content_ready(window)
};
if (capture && state.capture_protected(window)) || !ready {
return None;
}
let id = *state.windows.ids().get(window)?;
// Scrolling columns may sit outside their monitor's rectangle.
// They must not reappear on a neighboring output just because
// their global animated coordinates overlap it.
if !state.window_belongs_to_output(id, output) {
if workspace.map_or_else(
|| !state.window_belongs_to_output(id, output),
|workspace| state.workspaces.workspace_for_window(id) != Some(workspace.id),
) {
return None;
}
let sample = frame.windows.get(&id)?;
@@ -194,12 +232,23 @@ fn scene_elements(
presentation_alpha,
))
};
let windows = if state.overview.is_active() {
let windows = if workspace.is_none() && state.overview.is_active() {
state
.windows
.overview_windows()
.filter_map(prepare_window)
.collect::<Vec<_>>()
} else if workspace.is_some() {
let mut windows = frame
.windows
.keys()
.filter_map(|id| state.windows.window(*id))
.filter_map(prepare_window)
.collect::<Vec<_>>();
windows.sort_by_key(|(_, id, sample, ..)| {
std::cmp::Reverse(state.workspace_capture_stack(*id, sample.geometry))
});
windows
} else {
state
.space
@@ -209,27 +258,38 @@ fn scene_elements(
.collect::<Vec<_>>()
};
let mut closing = super::closing::grouped_elements(
state,
renderer,
output,
windows.iter().map(|(_, id, ..)| *id),
frame.delta,
);
let mut closing = if workspace.is_some() {
Default::default()
} else {
super::closing::grouped_elements(
state,
renderer,
output,
windows.iter().map(|(_, id, ..)| *id),
frame.delta,
)
};
for (window, id, sample, visual, decoration_progress, presentation_alpha) in windows {
if let Some(group) = closing.remove(&Some(id)) {
elements.extend(group);
}
elements.extend(super::tabs::tab_elements(
state, renderer, output, frame, id, capture,
state,
renderer,
output,
output_geometry,
frame,
id,
capture,
));
let constrain = rounded_visual_rect(visual, output_geometry.loc);
let material_surface = window
.toplevel()
.map(|toplevel| toplevel.wl_surface())
.filter(|surface| {
crate::effects::surface_role(surface)
.is_some_and(|(role, _)| role == crate::effects::SemanticRole::Modal)
workspace.is_none()
&& crate::effects::surface_role(surface)
.is_some_and(|(role, _)| role == crate::effects::SemanticRole::Modal)
});
let window_radius = if material_surface.is_some() {
state.theme_settings.material_radius
@@ -418,13 +478,15 @@ fn scene_elements(
if let Some(group) = closing.remove(&None) {
elements.extend(group);
}
elements.extend(layer_elements(
state,
renderer,
output,
&[Layer::Bottom, Layer::Background],
));
state.prepare_theme_wallpaper(renderer, output);
if workspace.is_none() {
elements.extend(layer_elements(
state,
renderer,
output,
&[Layer::Bottom, Layer::Background],
));
}
state.prepare_theme_wallpaper(renderer, workspace.map_or(output, |view| &view.output));
let progress = state.theme_progress();
let wallpaper = state.wallpaper.element(renderer, output);
let has_wallpaper = wallpaper.is_some();