kms: Output mirroring
This commit is contained in:
parent
19ba568f02
commit
3eb6c02008
4 changed files with 365 additions and 108 deletions
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
element::{CosmicElement, DamageElement},
|
element::{CosmicElement, DamageElement},
|
||||||
workspace_elements, CLEAR_COLOR,
|
workspace_elements, CLEAR_COLOR,
|
||||||
},
|
},
|
||||||
config::OutputConfig,
|
config::{OutputConfig, OutputState},
|
||||||
shell::Shell,
|
shell::Shell,
|
||||||
state::{BackendData, Common, Fps, SurfaceDmabufFeedback},
|
state::{BackendData, Common, Fps, SurfaceDmabufFeedback},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
|
@ -36,14 +36,18 @@ use smithay::{
|
||||||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||||
renderer::{
|
renderer::{
|
||||||
buffer_dimensions,
|
buffer_dimensions,
|
||||||
damage::Error as RenderError,
|
damage::{Error as RenderError, OutputDamageTracker},
|
||||||
element::{Element, RenderElementStates},
|
element::{
|
||||||
gles::GlesRenderbuffer,
|
texture::{TextureRenderBuffer, TextureRenderElement},
|
||||||
|
utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior},
|
||||||
|
Element, Kind, RenderElementStates,
|
||||||
|
},
|
||||||
|
gles::{GlesRenderbuffer, GlesTexture},
|
||||||
glow::GlowRenderer,
|
glow::GlowRenderer,
|
||||||
multigpu::{gbm::GbmGlesBackend, Error as MultiError, GpuManager},
|
multigpu::{gbm::GbmGlesBackend, Error as MultiError, GpuManager},
|
||||||
sync::SyncPoint,
|
sync::SyncPoint,
|
||||||
utils::with_renderer_surface_state,
|
utils::with_renderer_surface_state,
|
||||||
Bind, ImportDma, Offscreen,
|
Bind, ImportDma, Offscreen, Renderer, Texture,
|
||||||
},
|
},
|
||||||
session::{libseat::LibSeatSession, Event as SessionEvent, Session},
|
session::{libseat::LibSeatSession, Event as SessionEvent, Session},
|
||||||
udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent},
|
udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent},
|
||||||
|
|
@ -155,7 +159,11 @@ impl fmt::Debug for Device {
|
||||||
pub struct Surface {
|
pub struct Surface {
|
||||||
surface: Option<GbmDrmCompositor>,
|
surface: Option<GbmDrmCompositor>,
|
||||||
connector: connector::Handle,
|
connector: connector::Handle,
|
||||||
|
|
||||||
output: Output,
|
output: Output,
|
||||||
|
mirroring: Option<Output>,
|
||||||
|
mirroring_textures: HashMap<DrmNode, MirroringState>,
|
||||||
|
|
||||||
refresh_rate: u32,
|
refresh_rate: u32,
|
||||||
vrr: bool,
|
vrr: bool,
|
||||||
scheduled: bool,
|
scheduled: bool,
|
||||||
|
|
@ -165,6 +173,45 @@ pub struct Surface {
|
||||||
feedback: HashMap<DrmNode, SurfaceDmabufFeedback>,
|
feedback: HashMap<DrmNode, SurfaceDmabufFeedback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MirroringState {
|
||||||
|
texture: TextureRenderBuffer<GlesTexture>,
|
||||||
|
damage_tracker: OutputDamageTracker,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MirroringState {
|
||||||
|
fn new_with_renderer(
|
||||||
|
renderer: &mut GlMultiRenderer,
|
||||||
|
format: Fourcc,
|
||||||
|
output: &Output,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let size = output
|
||||||
|
.current_mode()
|
||||||
|
.map(|mode| mode.size)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_logical(1)
|
||||||
|
.to_buffer(1, Transform::Normal);
|
||||||
|
let opaque_regions = vec![Rectangle::from_loc_and_size((0, 0), size)];
|
||||||
|
|
||||||
|
let texture = Offscreen::<GlesTexture>::create_buffer(renderer, format, size)?;
|
||||||
|
let transform = output.current_transform();
|
||||||
|
let texture_buffer = TextureRenderBuffer::from_texture(
|
||||||
|
renderer,
|
||||||
|
texture,
|
||||||
|
1,
|
||||||
|
transform,
|
||||||
|
Some(opaque_regions),
|
||||||
|
);
|
||||||
|
|
||||||
|
let damage_tracker = OutputDamageTracker::from_output(output);
|
||||||
|
|
||||||
|
Ok(MirroringState {
|
||||||
|
texture: texture_buffer,
|
||||||
|
damage_tracker,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type GbmDrmCompositor = DrmCompositor<
|
pub type GbmDrmCompositor = DrmCompositor<
|
||||||
GbmAllocator<DrmDeviceFd>,
|
GbmAllocator<DrmDeviceFd>,
|
||||||
GbmDevice<DrmDeviceFd>,
|
GbmDevice<DrmDeviceFd>,
|
||||||
|
|
@ -1036,6 +1083,8 @@ impl Device {
|
||||||
|
|
||||||
let data = Surface {
|
let data = Surface {
|
||||||
output: output.clone(),
|
output: output.clone(),
|
||||||
|
mirroring: None,
|
||||||
|
mirroring_textures: HashMap::new(),
|
||||||
surface: None,
|
surface: None,
|
||||||
connector: conn,
|
connector: conn,
|
||||||
vrr,
|
vrr,
|
||||||
|
|
@ -1183,7 +1232,7 @@ impl Surface {
|
||||||
api.renderer(&render_node, &target_node, compositor.format())
|
api.renderer(&render_node, &target_node, compositor.format())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
),
|
),
|
||||||
None => (target_node, api.single_renderer(&target_node).unwrap()),
|
_ => (target_node, api.single_renderer(&target_node).unwrap()),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.fps.start();
|
self.fps.start();
|
||||||
|
|
@ -1197,8 +1246,10 @@ impl Surface {
|
||||||
|
|
||||||
let mut elements = {
|
let mut elements = {
|
||||||
let shell = state.shell.read().unwrap();
|
let shell = state.shell.read().unwrap();
|
||||||
let (previous_workspace, workspace) = shell.workspaces.active(&self.output);
|
let output = self.mirroring.as_ref().unwrap_or(&self.output);
|
||||||
let (previous_idx, idx) = shell.workspaces.active_num(&self.output);
|
|
||||||
|
let (previous_workspace, workspace) = shell.workspaces.active(output);
|
||||||
|
let (previous_idx, idx) = shell.workspaces.active_num(output);
|
||||||
let previous_workspace = previous_workspace
|
let previous_workspace = previous_workspace
|
||||||
.zip(previous_idx)
|
.zip(previous_idx)
|
||||||
.map(|((w, start), idx)| (w.handle, idx, start));
|
.map(|((w, start), idx)| (w.handle, idx, start));
|
||||||
|
|
@ -1211,7 +1262,7 @@ impl Surface {
|
||||||
&state.config,
|
&state.config,
|
||||||
&state.theme,
|
&state.theme,
|
||||||
state.clock.now(),
|
state.clock.now(),
|
||||||
&self.output,
|
output,
|
||||||
previous_workspace,
|
previous_workspace,
|
||||||
workspace,
|
workspace,
|
||||||
CursorMode::All,
|
CursorMode::All,
|
||||||
|
|
@ -1229,73 +1280,166 @@ impl Surface {
|
||||||
ScreencopyFrame,
|
ScreencopyFrame,
|
||||||
Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>,
|
Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>,
|
||||||
)> = self
|
)> = self
|
||||||
.output
|
.mirroring
|
||||||
.take_pending_frames()
|
.is_none()
|
||||||
.into_iter()
|
.then(|| {
|
||||||
.map(|(session, frame)| {
|
self.output
|
||||||
let additional_damage = frame.damage();
|
.take_pending_frames()
|
||||||
let session_data = session.user_data().get::<SessionData>().unwrap();
|
.into_iter()
|
||||||
let mut damage_tracking = session_data.borrow_mut();
|
.map(|(session, frame)| {
|
||||||
|
let additional_damage = frame.damage();
|
||||||
|
let session_data = session.user_data().get::<SessionData>().unwrap();
|
||||||
|
let mut damage_tracking = session_data.borrow_mut();
|
||||||
|
|
||||||
let old_len = if !additional_damage.is_empty() {
|
let old_len = if !additional_damage.is_empty() {
|
||||||
let area = self
|
let area = self
|
||||||
.output
|
.output
|
||||||
.current_mode()
|
.current_mode()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
/* TODO: Mode is Buffer..., why is this Physical in the first place */
|
/* TODO: Mode is Buffer..., why is this Physical in the first place */
|
||||||
.size
|
.size
|
||||||
.to_logical(1)
|
.to_logical(1)
|
||||||
.to_buffer(1, Transform::Normal)
|
.to_buffer(1, Transform::Normal)
|
||||||
.to_f64();
|
.to_f64();
|
||||||
|
|
||||||
let old_len = elements.len();
|
let old_len = elements.len();
|
||||||
elements.extend(
|
elements.extend(
|
||||||
additional_damage
|
additional_damage
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|rect| {
|
.map(|rect| {
|
||||||
rect.to_f64()
|
rect.to_f64()
|
||||||
.to_logical(
|
.to_logical(
|
||||||
self.output.current_scale().fractional_scale(),
|
self.output.current_scale().fractional_scale(),
|
||||||
self.output.current_transform(),
|
self.output.current_transform(),
|
||||||
&area,
|
&area,
|
||||||
)
|
)
|
||||||
.to_i32_round()
|
.to_i32_round()
|
||||||
})
|
})
|
||||||
.map(DamageElement::new)
|
.map(DamageElement::new)
|
||||||
.map(Into::into),
|
.map(Into::into),
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(old_len)
|
Some(old_len)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let buffer = frame.buffer();
|
let buffer = frame.buffer();
|
||||||
let age = damage_tracking.age_for_buffer(&buffer);
|
let age = damage_tracking.age_for_buffer(&buffer);
|
||||||
let res = damage_tracking.dt.damage_output(age, &elements);
|
let res = damage_tracking.dt.damage_output(age, &elements);
|
||||||
|
|
||||||
if let Some(old_len) = old_len {
|
if let Some(old_len) = old_len {
|
||||||
elements.truncate(old_len);
|
elements.truncate(old_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = res.map(|(a, b)| (a.cloned(), b));
|
let res = res.map(|(a, b)| (a.cloned(), b));
|
||||||
std::mem::drop(damage_tracking);
|
std::mem::drop(damage_tracking);
|
||||||
(session, frame, res)
|
(session, frame, res)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
})
|
})
|
||||||
.collect();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let res = compositor.render_frame(
|
let res = if let Some(mirrored_output) = self.mirroring.as_ref().filter(|mirrored_output| {
|
||||||
&mut renderer,
|
mirrored_output.current_mode().is_some_and(|mirror_mode| {
|
||||||
&elements,
|
self.output
|
||||||
CLEAR_COLOR, // TODO use a theme neutral color
|
.current_mode()
|
||||||
);
|
.is_some_and(|mode| mode != mirror_mode)
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
let mirroring_state = {
|
||||||
|
let entry = self.mirroring_textures.entry(*target_node);
|
||||||
|
let mut new_state = None;
|
||||||
|
if matches!(entry, std::collections::hash_map::Entry::Vacant(_)) {
|
||||||
|
new_state = Some(MirroringState::new_with_renderer(
|
||||||
|
&mut renderer,
|
||||||
|
compositor.format(),
|
||||||
|
mirrored_output,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
// I really want a failable initializer...
|
||||||
|
entry.or_insert_with(|| new_state.unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
mirroring_state
|
||||||
|
.texture
|
||||||
|
.render()
|
||||||
|
.draw::<_, <GlMultiRenderer as Renderer>::Error>(|tex| {
|
||||||
|
let res = match mirroring_state.damage_tracker.render_output_with(
|
||||||
|
&mut renderer,
|
||||||
|
tex.clone(),
|
||||||
|
1,
|
||||||
|
&elements,
|
||||||
|
CLEAR_COLOR,
|
||||||
|
) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(RenderError::Rendering(err)) => return Err(err),
|
||||||
|
Err(RenderError::OutputNoMode(_)) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.wait(&res.sync)?;
|
||||||
|
|
||||||
|
let transform = mirrored_output.current_transform();
|
||||||
|
let area = tex.size().to_logical(1, transform);
|
||||||
|
|
||||||
|
Ok(res
|
||||||
|
.damage
|
||||||
|
.cloned()
|
||||||
|
.map(|v| {
|
||||||
|
v.into_iter()
|
||||||
|
.map(|r| r.to_logical(1).to_buffer(1, transform, &area))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default())
|
||||||
|
})
|
||||||
|
.context("Failed to draw to offscreen render target")?;
|
||||||
|
|
||||||
|
let texture_elem = TextureRenderElement::from_texture_render_buffer(
|
||||||
|
(0., 0.),
|
||||||
|
&mirroring_state.texture,
|
||||||
|
Some(1.0),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Kind::Unspecified,
|
||||||
|
);
|
||||||
|
let texture_geometry = texture_elem.geometry(1.0.into());
|
||||||
|
elements = constrain_render_elements(
|
||||||
|
std::iter::once(texture_elem),
|
||||||
|
(0, 0),
|
||||||
|
Rectangle::from_loc_and_size(
|
||||||
|
(0, 0),
|
||||||
|
self.output
|
||||||
|
.geometry()
|
||||||
|
.size
|
||||||
|
.as_logical()
|
||||||
|
.to_f64()
|
||||||
|
.to_physical(self.output.current_scale().fractional_scale())
|
||||||
|
.to_i32_round(),
|
||||||
|
),
|
||||||
|
texture_geometry,
|
||||||
|
ConstrainScaleBehavior::Fit,
|
||||||
|
ConstrainAlign::CENTER,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
.map(CosmicElement::Mirror)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
renderer = api.single_renderer(&target_node).unwrap();
|
||||||
|
compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0])
|
||||||
|
} else {
|
||||||
|
compositor.render_frame(
|
||||||
|
&mut renderer,
|
||||||
|
&elements,
|
||||||
|
CLEAR_COLOR, // TODO use a theme neutral color
|
||||||
|
)
|
||||||
|
};
|
||||||
self.fps.render();
|
self.fps.render();
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(frame_result) => {
|
Ok(frame_result) => {
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
let feedback = if !frame_result.is_empty {
|
let feedback = if !frame_result.is_empty && self.mirroring.is_none() {
|
||||||
Some((
|
Some((
|
||||||
state.take_presentation_feedback(&self.output, &frame_result.states),
|
state.take_presentation_feedback(&self.output, &frame_result.states),
|
||||||
rx,
|
rx,
|
||||||
|
|
@ -1462,31 +1606,33 @@ impl Surface {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
state.send_frames(&self.output, &frame_result.states, |source_node| {
|
if self.mirroring.is_none() {
|
||||||
Some(
|
state.send_frames(&self.output, &frame_result.states, |source_node| {
|
||||||
self.feedback
|
Some(
|
||||||
.entry(source_node)
|
self.feedback
|
||||||
.or_insert_with(|| {
|
.entry(source_node)
|
||||||
let render_formats = api
|
.or_insert_with(|| {
|
||||||
.single_renderer(&source_node)
|
let render_formats = api
|
||||||
.unwrap()
|
.single_renderer(&source_node)
|
||||||
.dmabuf_formats()
|
.unwrap()
|
||||||
.collect::<HashSet<_>>();
|
.dmabuf_formats()
|
||||||
let target_formats = api
|
.collect::<HashSet<_>>();
|
||||||
.single_renderer(target_node)
|
let target_formats = api
|
||||||
.unwrap()
|
.single_renderer(target_node)
|
||||||
.dmabuf_formats()
|
.unwrap()
|
||||||
.collect::<HashSet<_>>();
|
.dmabuf_formats()
|
||||||
get_surface_dmabuf_feedback(
|
.collect::<HashSet<_>>();
|
||||||
source_node,
|
get_surface_dmabuf_feedback(
|
||||||
render_formats,
|
source_node,
|
||||||
target_formats,
|
render_formats,
|
||||||
compositor,
|
target_formats,
|
||||||
)
|
compositor,
|
||||||
})
|
)
|
||||||
.clone(),
|
})
|
||||||
)
|
.clone(),
|
||||||
});
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
compositor.reset_buffers();
|
compositor.reset_buffers();
|
||||||
|
|
@ -1513,6 +1659,17 @@ impl KmsState {
|
||||||
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
||||||
xdg_activation_state: &XdgActivationState,
|
xdg_activation_state: &XdgActivationState,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let outputs = self
|
||||||
|
.devices
|
||||||
|
.values()
|
||||||
|
.flat_map(|device| {
|
||||||
|
device
|
||||||
|
.surfaces
|
||||||
|
.values()
|
||||||
|
.map(|surface| surface.output.clone())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let recreated = if let Some(device) = self
|
let recreated = if let Some(device) = self
|
||||||
.devices
|
.devices
|
||||||
.values_mut()
|
.values_mut()
|
||||||
|
|
@ -1528,8 +1685,19 @@ impl KmsState {
|
||||||
.get::<RefCell<OutputConfig>>()
|
.get::<RefCell<OutputConfig>>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.borrow();
|
.borrow();
|
||||||
|
let mirrored_output = if let OutputState::Mirroring(conn) = &output_config.enabled {
|
||||||
|
Some(
|
||||||
|
outputs
|
||||||
|
.iter()
|
||||||
|
.find(|output| &output.name() == conn)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(anyhow::anyhow!("Unable to find mirroring output"))?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if !output_config.enabled {
|
if output_config.enabled == OutputState::Disabled {
|
||||||
if !test_only {
|
if !test_only {
|
||||||
shell.workspaces.remove_output(
|
shell.workspaces.remove_output(
|
||||||
output,
|
output,
|
||||||
|
|
@ -1541,6 +1709,7 @@ impl KmsState {
|
||||||
// just drop it
|
// just drop it
|
||||||
surface.pending = false;
|
surface.pending = false;
|
||||||
}
|
}
|
||||||
|
surface.mirroring_textures.clear();
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1635,9 +1804,22 @@ impl KmsState {
|
||||||
surface.surface = Some(target);
|
surface.surface = Some(target);
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
shell
|
|
||||||
.workspaces
|
if mirrored_output != surface.mirroring {
|
||||||
.add_output(output, workspace_state, xdg_activation_state);
|
shell.workspaces.remove_output(
|
||||||
|
output,
|
||||||
|
shell.seats.iter(),
|
||||||
|
workspace_state,
|
||||||
|
xdg_activation_state,
|
||||||
|
);
|
||||||
|
surface.mirroring = mirrored_output.clone();
|
||||||
|
surface.mirroring_textures.clear();
|
||||||
|
}
|
||||||
|
if mirrored_output.is_none() {
|
||||||
|
shell
|
||||||
|
.workspaces
|
||||||
|
.add_output(output, workspace_state, xdg_activation_state);
|
||||||
|
}
|
||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -1737,11 +1919,13 @@ impl KmsState {
|
||||||
output: &Output,
|
output: &Output,
|
||||||
estimated_rendertime: Option<Duration>,
|
estimated_rendertime: Option<Duration>,
|
||||||
) -> Result<(), InsertError<Timer>> {
|
) -> Result<(), InsertError<Timer>> {
|
||||||
if let Some((device, crtc, surface)) = self
|
for (device, crtc, surface) in self
|
||||||
.devices
|
.devices
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.flat_map(|(node, d)| d.surfaces.iter_mut().map(move |(c, s)| (node, c, s)))
|
.flat_map(|(node, d)| d.surfaces.iter_mut().map(move |(c, s)| (node, c, s)))
|
||||||
.find(|(_, _, s)| s.output == *output)
|
.filter(|(_, _, s)| {
|
||||||
|
s.output == *output || s.mirroring.as_ref().is_some_and(|o| o == output)
|
||||||
|
})
|
||||||
{
|
{
|
||||||
if surface.surface.is_none() {
|
if surface.surface.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -1774,7 +1958,7 @@ impl KmsState {
|
||||||
let common = &mut state.common;
|
let common = &mut state.common;
|
||||||
let target_node = target_device.render_node;
|
let target_node = target_device.render_node;
|
||||||
let render_node = render_node_for_output(
|
let render_node = render_node_for_output(
|
||||||
&surface.output,
|
surface.mirroring.as_ref().unwrap_or(&surface.output),
|
||||||
backend.primary_node,
|
backend.primary_node,
|
||||||
target_node,
|
target_node,
|
||||||
&*common.shell.read().unwrap(),
|
&*common.shell.read().unwrap(),
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ use smithay::{
|
||||||
backend::renderer::{
|
backend::renderer::{
|
||||||
element::{
|
element::{
|
||||||
surface::WaylandSurfaceRenderElement,
|
surface::WaylandSurfaceRenderElement,
|
||||||
utils::{Relocate, RelocateRenderElement},
|
texture::TextureRenderElement,
|
||||||
|
utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement},
|
||||||
Element, Id, Kind, RenderElement, UnderlyingStorage,
|
Element, Id, Kind, RenderElement, UnderlyingStorage,
|
||||||
},
|
},
|
||||||
|
gles::GlesTexture,
|
||||||
glow::{GlowFrame, GlowRenderer},
|
glow::{GlowFrame, GlowRenderer},
|
||||||
utils::{CommitCounter, DamageSet},
|
utils::{CommitCounter, DamageSet},
|
||||||
Frame, ImportAll, ImportMem, Renderer,
|
Frame, ImportAll, ImportMem, Renderer,
|
||||||
|
|
@ -30,6 +32,11 @@ where
|
||||||
Dnd(WaylandSurfaceRenderElement<R>),
|
Dnd(WaylandSurfaceRenderElement<R>),
|
||||||
MoveGrab(CosmicMappedRenderElement<R>),
|
MoveGrab(CosmicMappedRenderElement<R>),
|
||||||
AdditionalDamage(DamageElement),
|
AdditionalDamage(DamageElement),
|
||||||
|
Mirror(
|
||||||
|
CropRenderElement<
|
||||||
|
RelocateRenderElement<RescaleRenderElement<TextureRenderElement<GlesTexture>>>,
|
||||||
|
>,
|
||||||
|
),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
Egui(TextureRenderElement<GlesTexture>),
|
Egui(TextureRenderElement<GlesTexture>),
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +54,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.id(),
|
CosmicElement::Dnd(elem) => elem.id(),
|
||||||
CosmicElement::MoveGrab(elem) => elem.id(),
|
CosmicElement::MoveGrab(elem) => elem.id(),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.id(),
|
CosmicElement::AdditionalDamage(elem) => elem.id(),
|
||||||
|
CosmicElement::Mirror(elem) => elem.id(),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.id(),
|
CosmicElement::Egui(elem) => elem.id(),
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +67,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.current_commit(),
|
CosmicElement::Dnd(elem) => elem.current_commit(),
|
||||||
CosmicElement::MoveGrab(elem) => elem.current_commit(),
|
CosmicElement::MoveGrab(elem) => elem.current_commit(),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.current_commit(),
|
CosmicElement::AdditionalDamage(elem) => elem.current_commit(),
|
||||||
|
CosmicElement::Mirror(elem) => elem.current_commit(),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.current_commit(),
|
CosmicElement::Egui(elem) => elem.current_commit(),
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +80,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.src(),
|
CosmicElement::Dnd(elem) => elem.src(),
|
||||||
CosmicElement::MoveGrab(elem) => elem.src(),
|
CosmicElement::MoveGrab(elem) => elem.src(),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.src(),
|
CosmicElement::AdditionalDamage(elem) => elem.src(),
|
||||||
|
CosmicElement::Mirror(elem) => elem.src(),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.src(),
|
CosmicElement::Egui(elem) => elem.src(),
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +93,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.geometry(scale),
|
CosmicElement::Dnd(elem) => elem.geometry(scale),
|
||||||
CosmicElement::MoveGrab(elem) => elem.geometry(scale),
|
CosmicElement::MoveGrab(elem) => elem.geometry(scale),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.geometry(scale),
|
CosmicElement::AdditionalDamage(elem) => elem.geometry(scale),
|
||||||
|
CosmicElement::Mirror(elem) => elem.geometry(scale),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.geometry(scale),
|
CosmicElement::Egui(elem) => elem.geometry(scale),
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +106,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.location(scale),
|
CosmicElement::Dnd(elem) => elem.location(scale),
|
||||||
CosmicElement::MoveGrab(elem) => elem.location(scale),
|
CosmicElement::MoveGrab(elem) => elem.location(scale),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.location(scale),
|
CosmicElement::AdditionalDamage(elem) => elem.location(scale),
|
||||||
|
CosmicElement::Mirror(elem) => elem.location(scale),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.location(scale),
|
CosmicElement::Egui(elem) => elem.location(scale),
|
||||||
}
|
}
|
||||||
|
|
@ -107,6 +119,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.transform(),
|
CosmicElement::Dnd(elem) => elem.transform(),
|
||||||
CosmicElement::MoveGrab(elem) => elem.transform(),
|
CosmicElement::MoveGrab(elem) => elem.transform(),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.transform(),
|
CosmicElement::AdditionalDamage(elem) => elem.transform(),
|
||||||
|
CosmicElement::Mirror(elem) => elem.transform(),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.transform(),
|
CosmicElement::Egui(elem) => elem.transform(),
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +136,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.damage_since(scale, commit),
|
CosmicElement::Dnd(elem) => elem.damage_since(scale, commit),
|
||||||
CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit),
|
CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit),
|
CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit),
|
||||||
|
CosmicElement::Mirror(elem) => elem.damage_since(scale, commit),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.damage_since(scale, commit),
|
CosmicElement::Egui(elem) => elem.damage_since(scale, commit),
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +149,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.opaque_regions(scale),
|
CosmicElement::Dnd(elem) => elem.opaque_regions(scale),
|
||||||
CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale),
|
CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale),
|
CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale),
|
||||||
|
CosmicElement::Mirror(elem) => elem.opaque_regions(scale),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.opaque_regions(scale),
|
CosmicElement::Egui(elem) => elem.opaque_regions(scale),
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +162,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.alpha(),
|
CosmicElement::Dnd(elem) => elem.alpha(),
|
||||||
CosmicElement::MoveGrab(elem) => elem.alpha(),
|
CosmicElement::MoveGrab(elem) => elem.alpha(),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.alpha(),
|
CosmicElement::AdditionalDamage(elem) => elem.alpha(),
|
||||||
|
CosmicElement::Mirror(elem) => elem.alpha(),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.alpha(),
|
CosmicElement::Egui(elem) => elem.alpha(),
|
||||||
}
|
}
|
||||||
|
|
@ -159,6 +175,7 @@ where
|
||||||
CosmicElement::Dnd(elem) => elem.kind(),
|
CosmicElement::Dnd(elem) => elem.kind(),
|
||||||
CosmicElement::MoveGrab(elem) => elem.kind(),
|
CosmicElement::MoveGrab(elem) => elem.kind(),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.kind(),
|
CosmicElement::AdditionalDamage(elem) => elem.kind(),
|
||||||
|
CosmicElement::Mirror(elem) => elem.kind(),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.kind(),
|
CosmicElement::Egui(elem) => elem.kind(),
|
||||||
}
|
}
|
||||||
|
|
@ -181,6 +198,9 @@ impl RenderElement<GlowRenderer> for CosmicElement<GlowRenderer> {
|
||||||
CosmicElement::AdditionalDamage(elem) => {
|
CosmicElement::AdditionalDamage(elem) => {
|
||||||
RenderElement::<GlowRenderer>::draw(elem, frame, src, dst, damage)
|
RenderElement::<GlowRenderer>::draw(elem, frame, src, dst, damage)
|
||||||
}
|
}
|
||||||
|
CosmicElement::Mirror(elem) => {
|
||||||
|
RenderElement::<GlowRenderer>::draw(elem, frame, src, dst, damage)
|
||||||
|
}
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => {
|
CosmicElement::Egui(elem) => {
|
||||||
RenderElement::<GlowRenderer>::draw(elem, frame, src, dst, damage)
|
RenderElement::<GlowRenderer>::draw(elem, frame, src, dst, damage)
|
||||||
|
|
@ -195,6 +215,7 @@ impl RenderElement<GlowRenderer> for CosmicElement<GlowRenderer> {
|
||||||
CosmicElement::Dnd(elem) => elem.underlying_storage(renderer),
|
CosmicElement::Dnd(elem) => elem.underlying_storage(renderer),
|
||||||
CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer),
|
CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer),
|
CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer),
|
||||||
|
CosmicElement::Mirror(elem) => elem.underlying_storage(renderer),
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => elem.underlying_storage(renderer),
|
CosmicElement::Egui(elem) => elem.underlying_storage(renderer),
|
||||||
}
|
}
|
||||||
|
|
@ -217,6 +238,14 @@ impl<'a> RenderElement<GlMultiRenderer<'a>> for CosmicElement<GlMultiRenderer<'a
|
||||||
CosmicElement::AdditionalDamage(elem) => {
|
CosmicElement::AdditionalDamage(elem) => {
|
||||||
RenderElement::<GlMultiRenderer<'a>>::draw(elem, frame, src, dst, damage)
|
RenderElement::<GlMultiRenderer<'a>>::draw(elem, frame, src, dst, damage)
|
||||||
}
|
}
|
||||||
|
CosmicElement::Mirror(elem) => {
|
||||||
|
let elem = {
|
||||||
|
let glow_frame = frame.glow_frame_mut();
|
||||||
|
RenderElement::<GlowRenderer>::draw(elem, glow_frame, src, dst, damage)
|
||||||
|
.map_err(|err| GlMultiError::Render(err))
|
||||||
|
};
|
||||||
|
elem
|
||||||
|
}
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => {
|
CosmicElement::Egui(elem) => {
|
||||||
let elem = {
|
let elem = {
|
||||||
|
|
@ -236,6 +265,15 @@ impl<'a> RenderElement<GlMultiRenderer<'a>> for CosmicElement<GlMultiRenderer<'a
|
||||||
CosmicElement::Dnd(elem) => elem.underlying_storage(renderer),
|
CosmicElement::Dnd(elem) => elem.underlying_storage(renderer),
|
||||||
CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer),
|
CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer),
|
||||||
CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer),
|
CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer),
|
||||||
|
CosmicElement::Mirror(elem) => {
|
||||||
|
let glow_renderer = renderer.glow_renderer_mut();
|
||||||
|
match elem.underlying_storage(glow_renderer) {
|
||||||
|
Some(UnderlyingStorage::Wayland(buffer)) => {
|
||||||
|
Some(UnderlyingStorage::Wayland(buffer))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
CosmicElement::Egui(elem) => {
|
CosmicElement::Egui(elem) => {
|
||||||
let glow_renderer = renderer.glow_renderer_mut();
|
let glow_renderer = renderer.glow_renderer_mut();
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,18 @@ impl From<Output> for OutputInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_enabled() -> bool {
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||||
true
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum OutputState {
|
||||||
|
#[serde(rename = "true")]
|
||||||
|
Enabled,
|
||||||
|
#[serde(rename = "false")]
|
||||||
|
Disabled,
|
||||||
|
Mirroring(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_enabled() -> OutputState {
|
||||||
|
OutputState::Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||||
|
|
@ -122,7 +132,7 @@ pub struct OutputConfig {
|
||||||
pub transform: Transform,
|
pub transform: Transform,
|
||||||
pub position: (i32, i32),
|
pub position: (i32, i32),
|
||||||
#[serde(default = "default_enabled")]
|
#[serde(default = "default_enabled")]
|
||||||
pub enabled: bool,
|
pub enabled: OutputState,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub max_bpc: Option<u32>,
|
pub max_bpc: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +145,7 @@ impl Default for OutputConfig {
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
transform: Transform::Normal,
|
transform: Transform::Normal,
|
||||||
position: (0, 0),
|
position: (0, 0),
|
||||||
enabled: true,
|
enabled: OutputState::Enabled,
|
||||||
max_bpc: None,
|
max_bpc: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -257,8 +267,32 @@ impl Config {
|
||||||
fn load_outputs(path: &Option<PathBuf>) -> OutputsConfig {
|
fn load_outputs(path: &Option<PathBuf>) -> OutputsConfig {
|
||||||
if let Some(path) = path.as_ref() {
|
if let Some(path) = path.as_ref() {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) {
|
match ron::de::from_reader::<_, OutputsConfig>(
|
||||||
Ok(config) => return config,
|
OpenOptions::new().read(true).open(path).unwrap(),
|
||||||
|
) {
|
||||||
|
Ok(mut config) => {
|
||||||
|
for (info, config) in config.config.iter_mut() {
|
||||||
|
let config_clone = config.clone();
|
||||||
|
for conf in config.iter_mut() {
|
||||||
|
if let OutputState::Mirroring(conn) = &conf.enabled {
|
||||||
|
if let Some((j, _)) = info
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, info)| &info.connector == conn)
|
||||||
|
{
|
||||||
|
if config_clone[j].enabled != OutputState::Enabled {
|
||||||
|
warn!("Invalid Mirroring tag, overriding with `Enabled` instead");
|
||||||
|
conf.enabled = OutputState::Enabled;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Invalid Mirroring tag, overriding with `Enabled` instead");
|
||||||
|
conf.enabled = OutputState::Enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(?err, "Failed to read output_config, resetting..");
|
warn!(?err, "Failed to read output_config, resetting..");
|
||||||
if let Err(err) = std::fs::remove_file(path) {
|
if let Err(err) = std::fs::remove_file(path) {
|
||||||
|
|
@ -307,7 +341,7 @@ impl Config {
|
||||||
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
|
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
|
||||||
{
|
{
|
||||||
let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone();
|
let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone();
|
||||||
let enabled = output_config.enabled;
|
let enabled = output_config.enabled.clone();
|
||||||
*output
|
*output
|
||||||
.user_data()
|
.user_data()
|
||||||
.get::<RefCell<OutputConfig>>()
|
.get::<RefCell<OutputConfig>>()
|
||||||
|
|
@ -329,7 +363,7 @@ impl Config {
|
||||||
reset = true;
|
reset = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
if enabled {
|
if enabled == OutputState::Enabled {
|
||||||
output_state.enable_head(&output);
|
output_state.enable_head(&output);
|
||||||
} else {
|
} else {
|
||||||
output_state.disable_head(&output);
|
output_state.disable_head(&output);
|
||||||
|
|
@ -343,7 +377,7 @@ impl Config {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(known_good_configs.into_iter())
|
.zip(known_good_configs.into_iter())
|
||||||
{
|
{
|
||||||
let enabled = output_config.enabled;
|
let enabled = output_config.enabled.clone();
|
||||||
*output
|
*output
|
||||||
.user_data()
|
.user_data()
|
||||||
.get::<RefCell<OutputConfig>>()
|
.get::<RefCell<OutputConfig>>()
|
||||||
|
|
@ -359,7 +393,7 @@ impl Config {
|
||||||
) {
|
) {
|
||||||
error!(?err, "Failed to reset config for output {}.", output.name());
|
error!(?err, "Failed to reset config for output {}.", output.name());
|
||||||
} else {
|
} else {
|
||||||
if enabled {
|
if enabled == OutputState::Enabled {
|
||||||
output_state.enable_head(&output);
|
output_state.enable_head(&output);
|
||||||
} else {
|
} else {
|
||||||
output_state.disable_head(&output);
|
output_state.disable_head(&output);
|
||||||
|
|
@ -392,6 +426,7 @@ impl Config {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.borrow()
|
.borrow()
|
||||||
.enabled
|
.enabled
|
||||||
|
== OutputState::Enabled
|
||||||
{
|
{
|
||||||
output_state.enable_head(&output);
|
output_state.enable_head(&output);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use smithay::output::Output;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::OutputConfig,
|
config::{OutputConfig, OutputState},
|
||||||
state::State,
|
state::State,
|
||||||
wayland::protocols::output_configuration::{
|
wayland::protocols::output_configuration::{
|
||||||
delegate_output_configuration, ModeConfiguration, OutputConfiguration,
|
delegate_output_configuration, ModeConfiguration, OutputConfiguration,
|
||||||
|
|
@ -76,9 +76,9 @@ impl State {
|
||||||
if let Some(position) = position {
|
if let Some(position) = position {
|
||||||
current_config.position = (*position).into();
|
current_config.position = (*position).into();
|
||||||
}
|
}
|
||||||
current_config.enabled = true;
|
current_config.enabled = OutputState::Enabled;
|
||||||
} else {
|
} else {
|
||||||
current_config.enabled = false;
|
current_config.enabled = OutputState::Disabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue