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:
1 parent
e64ab52190
commit
ab11269246
32 files changed
+2125
-165
No files matched your search
@@ -38,7 +38,8 @@ use overview::{overview_chrome_element, overview_strip_elements};
|
||||
pub(crate) use resources::RenderResources;
|
||||
use scene::cursor_elements;
|
||||
pub(crate) use scene::{
|
||||
capture_output_elements, frame_effect_metrics, layer_surfaces, sampled_output_elements,
|
||||
capture_output_elements, capture_workspace_elements, frame_effect_metrics, layer_surfaces,
|
||||
sampled_output_elements,
|
||||
};
|
||||
pub(crate) use shader::SharedPixelShaderElement;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
|
||||
@@ -21,7 +21,12 @@ pub(super) fn overview_chrome_element(
|
||||
width: f64,
|
||||
radius: f64,
|
||||
) -> Option<PhysicalShaderElement> {
|
||||
let output_id = state.output_id(output)?;
|
||||
let output_id = state.output_id(output).or_else(|| {
|
||||
output
|
||||
.user_data()
|
||||
.get::<crate::handlers::workspace_capture::CaptureOutputId>()
|
||||
.map(|id| id.0)
|
||||
})?;
|
||||
let geometry = rounded_visual_rect(rect, output_geometry.loc);
|
||||
let corners = RoundedRect::new(rect, output_geometry.loc, scale, radius);
|
||||
let mode = output.current_mode()?;
|
||||
|
||||
@@ -56,6 +56,18 @@ pub(super) struct ContextPrograms {
|
||||
}
|
||||
|
||||
impl RenderResources {
|
||||
pub(crate) fn retain_workspace_windows(
|
||||
&mut self,
|
||||
windows: &std::collections::HashSet<WindowId>,
|
||||
) {
|
||||
self.windows.retain(|id, _| windows.contains(id));
|
||||
for output in self.outputs.values_mut() {
|
||||
output
|
||||
.chrome
|
||||
.retain(|(id, _), _| windows.contains(&WindowId(*id)));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn prepare_window_corners(&mut self, id: WindowId, shape: CornerShape) -> bool {
|
||||
let buffers = self.windows.entry(id).or_default();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -113,17 +113,17 @@ pub(super) fn elements(
|
||||
state: &mut Ferese,
|
||||
renderer: &mut GlesRenderer,
|
||||
output: &Output,
|
||||
output_geometry: Rectangle<i32, Logical>,
|
||||
frame: &crate::state::FrameScene,
|
||||
bar: &TabBar,
|
||||
content: Rect,
|
||||
alpha: f32,
|
||||
capture: bool,
|
||||
) -> Vec<AnimatedWindowRenderElement> {
|
||||
let Some(g) = state.tab_indicator_geometry(bar, content) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(output_geometry) = state.space.output_geometry(output) else {
|
||||
let Some(g) = state.tab_indicator_geometry_for_frame(bar, content, frame) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
let settings = state.tab_indicator_settings;
|
||||
let anchor = bar.group.windows[0];
|
||||
|
||||
@@ -9,6 +9,7 @@ pub(super) fn tab_elements(
|
||||
state: &mut Ferese,
|
||||
renderer: &mut GlesRenderer,
|
||||
output: &Output,
|
||||
output_geometry: Rectangle<i32, super::Logical>,
|
||||
frame: &crate::state::FrameScene,
|
||||
active: ferese_layout::WindowId,
|
||||
capture: bool,
|
||||
@@ -19,13 +20,27 @@ pub(super) fn tab_elements(
|
||||
let Some(workspace) = state.workspaces.workspace_for_window(active) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(bar) = state
|
||||
.visible_tab_bars(workspace)
|
||||
.find(|bar| bar.group.active == active)
|
||||
.cloned()
|
||||
else {
|
||||
return Vec::new();
|
||||
let bar = if let Some(tabs) = &frame.capture_tabs {
|
||||
tabs.iter()
|
||||
.find(|bar| bar.group.active == active)
|
||||
.filter(|_| {
|
||||
state
|
||||
.workspaces
|
||||
.workspace(workspace)
|
||||
.is_some_and(|ws| ws.fullscreen.is_none())
|
||||
&& state
|
||||
.windows
|
||||
.record(active)
|
||||
.is_some_and(|window| !window.maximized)
|
||||
})
|
||||
.cloned()
|
||||
} else {
|
||||
state
|
||||
.visible_tab_bars(workspace)
|
||||
.find(|bar| bar.group.active == active)
|
||||
.cloned()
|
||||
};
|
||||
let Some(bar) = bar else { return Vec::new() };
|
||||
let Some(sample) = frame.windows.get(&active) else {
|
||||
return Vec::new();
|
||||
};
|
||||
@@ -37,6 +52,8 @@ pub(super) fn tab_elements(
|
||||
state,
|
||||
renderer,
|
||||
output,
|
||||
output_geometry,
|
||||
frame,
|
||||
&bar,
|
||||
sample.presentation.bounds.current,
|
||||
sample.presentation.alpha(),
|
||||
@@ -48,9 +65,7 @@ pub(super) fn tab_elements(
|
||||
}
|
||||
let content = sample.presentation.bounds.current;
|
||||
let alpha = sample.presentation.alpha();
|
||||
let Some(output_geometry) = state.space.output_geometry(output) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
let width = content.width / bar.group.windows.len() as f64;
|
||||
let shell = ferese_layout::Rect::new(
|
||||
|
||||
Reference in new issue
Block a user