Honor output orientation in recording and screen sharing
This commit is contained in:
1 parent
3ad0108d80
commit
e1e25e46e6
1 file changed
+132
-16
+132
-16
@@ -33,6 +33,7 @@ struct Output {
|
|||||||
global: u32,
|
global: u32,
|
||||||
proxy: wl_output::WlOutput,
|
proxy: wl_output::WlOutput,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
transform: wl_output::Transform,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
@@ -110,7 +111,13 @@ impl Capture {
|
|||||||
self.state
|
self.state
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| o.source.clone())
|
.map(|o| {
|
||||||
|
let mut source = o.source.clone();
|
||||||
|
if swaps_axes(o.transform) {
|
||||||
|
std::mem::swap(&mut source.width, &mut source.height);
|
||||||
|
}
|
||||||
|
source
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +135,7 @@ impl Capture {
|
|||||||
.find(|o| o.source.name == name)
|
.find(|o| o.source.name == name)
|
||||||
.ok_or("Shared monitor disconnected")?;
|
.ok_or("Shared monitor disconnected")?;
|
||||||
|
|
||||||
|
let transform = output.transform;
|
||||||
self.state.result = None;
|
self.state.result = None;
|
||||||
self.state.flipped = false;
|
self.state.flipped = false;
|
||||||
|
|
||||||
@@ -154,22 +162,20 @@ impl Capture {
|
|||||||
.buffer
|
.buffer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or("Capture supplied no buffer")?;
|
.ok_or("Capture supplied no buffer")?;
|
||||||
pixels.resize(buffer.map.len(), 0);
|
let (width, height) = copy_oriented_pixels(
|
||||||
pixels.copy_from_slice(&buffer.map);
|
&buffer.map,
|
||||||
|
buffer.width,
|
||||||
if self.state.flipped {
|
buffer.height,
|
||||||
let stride = buffer.stride as usize;
|
buffer.stride,
|
||||||
for y in 0..buffer.height as usize / 2 {
|
transform,
|
||||||
let bottom = (buffer.height as usize - y - 1) * stride;
|
self.state.flipped,
|
||||||
let (top, tail) = pixels.split_at_mut(bottom);
|
&mut pixels,
|
||||||
top[y * stride..(y + 1) * stride].swap_with_slice(&mut tail[..stride]);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Frame {
|
Ok(Frame {
|
||||||
width: buffer.width,
|
width,
|
||||||
height: buffer.height,
|
height,
|
||||||
stride: buffer.stride,
|
stride: width * 4,
|
||||||
pixels,
|
pixels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -231,6 +237,7 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||||||
} => match interface.as_str() {
|
} => match interface.as_str() {
|
||||||
"wl_output" => state.outputs.push(Output {
|
"wl_output" => state.outputs.push(Output {
|
||||||
global: name,
|
global: name,
|
||||||
|
transform: wl_output::Transform::Normal,
|
||||||
proxy: registry.bind(name, version.min(4), qh, name),
|
proxy: registry.bind(name, version.min(4), qh, name),
|
||||||
source: Source {
|
source: Source {
|
||||||
name: format!("output-{name}"),
|
name: format!("output-{name}"),
|
||||||
@@ -270,7 +277,12 @@ impl Dispatch<wl_output::WlOutput, u32> for State {
|
|||||||
match event {
|
match event {
|
||||||
wl_output::Event::Name { name } => output.source.name = name,
|
wl_output::Event::Name { name } => output.source.name = name,
|
||||||
wl_output::Event::Description { description } => output.source.label = description,
|
wl_output::Event::Description { description } => output.source.label = description,
|
||||||
wl_output::Event::Geometry { x, y, .. } => {
|
wl_output::Event::Geometry {
|
||||||
|
x, y, transform, ..
|
||||||
|
} => {
|
||||||
|
if let WEnum::Value(transform) = transform {
|
||||||
|
output.transform = transform;
|
||||||
|
}
|
||||||
output.source.x = x;
|
output.source.x = x;
|
||||||
output.source.y = y;
|
output.source.y = y;
|
||||||
}
|
}
|
||||||
@@ -380,6 +392,70 @@ impl Dispatch<frame::ZwlrScreencopyFrameV1, ()> for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn swaps_axes(transform: wl_output::Transform) -> bool {
|
||||||
|
matches!(
|
||||||
|
transform,
|
||||||
|
wl_output::Transform::_90
|
||||||
|
| wl_output::Transform::_270
|
||||||
|
| wl_output::Transform::Flipped90
|
||||||
|
| wl_output::Transform::Flipped270
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screencopy supplies output-buffer coordinates. Normalize both the output's
|
||||||
|
// presentation transform and the independent YInvert storage flag before
|
||||||
|
// publishing upright, tightly packed BGRx frames to PipeWire.
|
||||||
|
fn copy_oriented_pixels(
|
||||||
|
source: &[u8],
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
stride: u32,
|
||||||
|
transform: wl_output::Transform,
|
||||||
|
y_invert: bool,
|
||||||
|
pixels: &mut Vec<u8>,
|
||||||
|
) -> (u32, u32) {
|
||||||
|
use wl_output::Transform;
|
||||||
|
let (out_width, out_height) = if swaps_axes(transform) {
|
||||||
|
(height, width)
|
||||||
|
} else {
|
||||||
|
(width, height)
|
||||||
|
};
|
||||||
|
let row = out_width as usize * 4;
|
||||||
|
pixels.resize(row * out_height as usize, 0);
|
||||||
|
|
||||||
|
// The usual DRM and nested outputs need only one contiguous copy per row.
|
||||||
|
if matches!(transform, Transform::Normal | Transform::Flipped180) {
|
||||||
|
let flip = y_invert ^ (transform == Transform::Flipped180);
|
||||||
|
for (y, dst) in pixels.chunks_exact_mut(row).enumerate() {
|
||||||
|
let sy = if flip { height as usize - 1 - y } else { y };
|
||||||
|
let start = sy * stride as usize;
|
||||||
|
dst.copy_from_slice(&source[start..start + row]);
|
||||||
|
}
|
||||||
|
return (out_width, out_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
for y in 0..out_height {
|
||||||
|
for x in 0..out_width {
|
||||||
|
let (sx, mut sy) = match transform {
|
||||||
|
Transform::_90 => (y, height - 1 - x),
|
||||||
|
Transform::_180 => (width - 1 - x, height - 1 - y),
|
||||||
|
Transform::_270 => (width - 1 - y, x),
|
||||||
|
Transform::Flipped => (width - 1 - x, y),
|
||||||
|
Transform::Flipped90 => (y, x),
|
||||||
|
Transform::Flipped270 => (width - 1 - y, height - 1 - x),
|
||||||
|
_ => (x, y),
|
||||||
|
};
|
||||||
|
if y_invert {
|
||||||
|
sy = height - 1 - sy;
|
||||||
|
}
|
||||||
|
let src = sy as usize * stride as usize + sx as usize * 4;
|
||||||
|
let dst = y as usize * row + x as usize * 4;
|
||||||
|
pixels[dst..dst + 4].copy_from_slice(&source[src..src + 4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(out_width, out_height)
|
||||||
|
}
|
||||||
|
|
||||||
fn buffer_size(width: u32, height: u32, stride: u32) -> Result<usize, String> {
|
fn buffer_size(width: u32, height: u32, stride: u32) -> Result<usize, String> {
|
||||||
let row = width.checked_mul(4).ok_or("Capture dimensions overflow")?;
|
let row = width.checked_mul(4).ok_or("Capture dimensions overflow")?;
|
||||||
let bytes = stride
|
let bytes = stride
|
||||||
@@ -414,6 +490,46 @@ wayland_client::delegate_noop!(State: ignore manager::ZwlrScreencopyManagerV1);
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn capture_orientation_matches_presentation_for_all_transforms() {
|
||||||
|
use wl_output::Transform::*;
|
||||||
|
// Asymmetric 3x2 image with row padding and distinct four-byte pixels.
|
||||||
|
let cases = [
|
||||||
|
(Normal, (3, 2), "abcdef"),
|
||||||
|
(_90, (2, 3), "daebfc"),
|
||||||
|
(_180, (3, 2), "fedcba"),
|
||||||
|
(_270, (2, 3), "cfbead"),
|
||||||
|
(Flipped, (3, 2), "cbafed"),
|
||||||
|
(Flipped90, (2, 3), "adbecf"),
|
||||||
|
(Flipped180, (3, 2), "defabc"),
|
||||||
|
(Flipped270, (2, 3), "fcebda"),
|
||||||
|
];
|
||||||
|
let pixel = |value: u8| [value, value + 1, value + 2, 255];
|
||||||
|
let mut pixels = vec![0; 100];
|
||||||
|
for (transform, size, expected) in cases {
|
||||||
|
for y_invert in [false, true] {
|
||||||
|
let mut source = Vec::new();
|
||||||
|
let rows = if y_invert {
|
||||||
|
[b"def", b"abc"]
|
||||||
|
} else {
|
||||||
|
[b"abc", b"def"]
|
||||||
|
};
|
||||||
|
for row in rows {
|
||||||
|
for &value in row {
|
||||||
|
source.extend_from_slice(&pixel(value));
|
||||||
|
}
|
||||||
|
source.extend_from_slice(&[0; 4]);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
copy_oriented_pixels(&source, 3, 2, 16, transform, y_invert, &mut pixels),
|
||||||
|
size,
|
||||||
|
);
|
||||||
|
let expected: Vec<_> = expected.bytes().flat_map(pixel).collect();
|
||||||
|
assert_eq!(pixels, expected, "{transform:?}, YInvert={y_invert}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_unbounded_and_invalid_buffers() {
|
fn rejects_unbounded_and_invalid_buffers() {
|
||||||
assert_eq!(buffer_size(640, 480, 2560).unwrap(), 1228800);
|
assert_eq!(buffer_size(640, 480, 2560).unwrap(), 1228800);
|
||||||
|
|||||||
Reference in new issue
Block a user