Add option to rotate outputs

This commit is contained in:
axtloss
2024-01-28 14:25:40 +01:00
committed by Ivan Molodetskikh
parent 11bff3a2f1
commit 962e159db6
5 changed files with 132 additions and 38 deletions
+54
View File
@@ -187,6 +187,8 @@ pub struct Output {
pub name: String,
#[knuffel(child, unwrap(argument), default = 1.)]
pub scale: f64,
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
pub transform: Transform,
#[knuffel(child)]
pub position: Option<Position>,
#[knuffel(child, unwrap(argument, str))]
@@ -199,12 +201,62 @@ impl Default for Output {
off: false,
name: String::new(),
scale: 1.,
transform: Transform::Normal,
position: None,
mode: None,
}
}
}
/// Output transform, which goes counter-clockwise.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Transform {
Normal,
_90,
_180,
_270,
Flipped,
Flipped90,
Flipped180,
Flipped270,
}
impl FromStr for Transform {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"normal" => Ok(Self::Normal),
"90" => Ok(Self::_90),
"180" => Ok(Self::_180),
"270" => Ok(Self::_270),
"flipped" => Ok(Self::Flipped),
"flipped-90" => Ok(Self::Flipped90),
"flipped-180" => Ok(Self::Flipped180),
"flipped-270" => Ok(Self::Flipped270),
_ => Err(miette!(concat!(
r#"invalid transform, can be "90", "180", "270", "#,
r#""flipped", "flipped-90", "flipped-180" or "flipped-270""#
))),
}
}
}
impl From<Transform> for smithay::utils::Transform {
fn from(value: Transform) -> Self {
match value {
Transform::Normal => Self::Normal,
Transform::_90 => Self::_90,
Transform::_180 => Self::_180,
Transform::_270 => Self::_270,
Transform::Flipped => Self::Flipped,
Transform::Flipped90 => Self::Flipped90,
Transform::Flipped180 => Self::Flipped180,
Transform::Flipped270 => Self::Flipped270,
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position {
#[knuffel(property)]
@@ -726,6 +778,7 @@ mod tests {
output "eDP-1" {
scale 2.0
transform "flipped-90"
position x=10 y=20
mode "1920x1080@144"
}
@@ -826,6 +879,7 @@ mod tests {
off: false,
name: "eDP-1".to_owned(),
scale: 2.,
transform: Transform::Flipped90,
position: Some(Position { x: 10, y: 20 }),
mode: Some(Mode {
width: 1920,
+4
View File
@@ -65,6 +65,10 @@ input {
// Scale is a floating-point number, but at the moment only integer values work.
scale 2.0
// Transform allows to rotate the output counter-clockwise, valid values are:
// normal, 90, 180, 270, flipped, flipped-90, flipped-180 and flipped-270.
transform "normal"
// Resolution and, optionally, refresh rate of the output.
// The format is "<width>x<height>" or "<width>x<height>@<refresh rate>".
// If the refresh rate is omitted, niri will pick the highest refresh rate
+65 -34
View File
@@ -648,20 +648,22 @@ impl State {
let mut resized_outputs = vec![];
for output in self.niri.global_space.outputs() {
let name = output.name();
let scale = self
.niri
.config
.borrow()
.outputs
.iter()
.find(|o| o.name == name)
.map(|c| c.scale)
.unwrap_or(1.);
let config = self.niri.config.borrow_mut();
let config = config.outputs.iter().find(|o| o.name == name);
let scale = config.map(|c| c.scale).unwrap_or(1.);
let scale = scale.clamp(1., 10.).ceil() as i32;
if output.current_scale().integer_scale() != scale {
let transform = config
.map(|c| c.transform.into())
.unwrap_or(Transform::Normal);
if output.current_scale().integer_scale() != scale
|| output.current_transform() != transform
{
output.change_current_state(
None,
None,
Some(transform),
Some(output::Scale::Integer(scale)),
None,
);
@@ -1172,18 +1174,21 @@ impl Niri {
let global = output.create_global::<State>(&self.display_handle);
let name = output.name();
let scale = self
.config
.borrow()
.outputs
.iter()
.find(|o| o.name == name)
.map(|c| c.scale)
.unwrap_or(1.);
let scale = scale.clamp(1., 10.).ceil() as i32;
// Set scale before adding to the layout since that will read the output size.
output.change_current_state(None, None, Some(output::Scale::Integer(scale)), None);
let config = self.config.borrow();
let c = config.outputs.iter().find(|o| o.name == name);
let scale = c.map(|c| c.scale).unwrap_or(1.);
let scale = scale.clamp(1., 10.).ceil() as i32;
let transform = c.map(|c| c.transform.into()).unwrap_or(Transform::Normal);
drop(config);
// Set scale and transform before adding to the layout since that will read the output size.
output.change_current_state(
None,
Some(transform),
Some(output::Scale::Integer(scale)),
None,
);
self.layout.add_output(output.clone());
@@ -1300,14 +1305,16 @@ impl Niri {
}
// If the output size changed with an open screenshot UI, close the screenshot UI.
if let Some((old_size, old_scale)) = self.screenshot_ui.output_size(&output) {
let output_transform = output.current_transform();
if let Some((old_size, old_scale, old_transform)) = self.screenshot_ui.output_size(&output)
{
let transform = output.current_transform();
let output_mode = output.current_mode().unwrap();
let size = output_transform.transform_size(output_mode.size);
let size = transform.transform_size(output_mode.size);
let scale = output.current_scale().integer_scale();
// FIXME: scale changes shouldn't matter but they currently do since I haven't quite
// figured out how to draw the screenshot textures in physical coordinates.
if old_size != size || old_scale != scale {
// FIXME: scale changes and transform flips shouldn't matter but they currently do since
// I haven't quite figured out how to draw the screenshot textures in
// physical coordinates.
if old_size != size || old_scale != scale || old_transform != transform {
self.screenshot_ui.close();
self.cursor_manager
.set_cursor_image(CursorImageStatus::default_named());
@@ -1747,7 +1754,9 @@ impl Niri {
// FIXME we basically need to pick the largest scale factor across the overlapping
// outputs, this is how it's usually done in clients as well.
let mut cursor_scale = 1;
let mut cursor_transform = Transform::Normal;
let mut dnd_scale = 1;
let mut dnd_transform = Transform::Normal;
for output in self.global_space.outputs() {
let geo = self.global_space.output_geometry(output).unwrap();
@@ -1755,6 +1764,9 @@ impl Niri {
if let Some(mut overlap) = geo.intersection(bbox) {
overlap.loc -= surface_pos;
cursor_scale = cursor_scale.max(output.current_scale().integer_scale());
// FIXME: using the largest overlapping or "primary" output transform would
// make more sense here.
cursor_transform = output.current_transform();
output_update(output, Some(overlap), surface);
} else {
output_update(output, None, surface);
@@ -1765,6 +1777,9 @@ impl Niri {
if let Some(mut overlap) = geo.intersection(bbox) {
overlap.loc -= surface_pos;
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
// FIXME: using the largest overlapping or "primary" output transform
// would make more sense here.
dnd_transform = output.current_transform();
output_update(output, Some(overlap), surface);
} else {
output_update(output, None, surface);
@@ -1773,11 +1788,11 @@ impl Niri {
}
with_states(surface, |data| {
send_surface_state(surface, data, cursor_scale, Transform::Normal);
send_surface_state(surface, data, cursor_scale, cursor_transform);
});
if let Some((surface, _)) = dnd {
with_states(surface, |data| {
send_surface_state(surface, data, dnd_scale, Transform::Normal);
send_surface_state(surface, data, dnd_scale, dnd_transform);
});
}
}
@@ -1794,6 +1809,7 @@ impl Niri {
};
let mut dnd_scale = 1;
let mut dnd_transform = Transform::Normal;
for output in self.global_space.outputs() {
let geo = self.global_space.output_geometry(output).unwrap();
@@ -1815,15 +1831,18 @@ impl Niri {
if let Some(mut overlap) = geo.intersection(bbox) {
overlap.loc -= surface_pos;
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
// FIXME: using the largest overlapping or "primary" output transform would
// make more sense here.
dnd_transform = output.current_transform();
output_update(output, Some(overlap), surface);
} else {
output_update(output, None, surface);
}
with_states(surface, |data| {
send_surface_state(surface, data, dnd_scale, Transform::Normal);
});
}
with_states(surface, |data| {
send_surface_state(surface, data, dnd_scale, dnd_transform);
});
}
}
}
@@ -2409,6 +2428,9 @@ impl Niri {
let _span = tracy_client::span!("Niri::render_for_screen_cast");
let size = output.current_mode().unwrap().size;
let transform = output.current_transform();
let size = transform.transform_size(size);
let scale = Scale::from(output.current_scale().fractional_scale());
let mut elements = None;
@@ -2531,6 +2553,9 @@ impl Niri {
.cloned()
.filter_map(|output| {
let size = output.current_mode().unwrap().size;
let transform = output.current_transform();
let size = transform.transform_size(size);
let scale = Scale::from(output.current_scale().fractional_scale());
let elements = self.render::<GlesRenderer>(renderer, &output, true);
@@ -2558,6 +2583,9 @@ impl Niri {
let _span = tracy_client::span!("Niri::screenshot");
let size = output.current_mode().unwrap().size;
let transform = output.current_transform();
let size = transform.transform_size(size);
let scale = Scale::from(output.current_scale().fractional_scale());
let elements = self.render::<GlesRenderer>(renderer, output, true);
let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, &elements)?;
@@ -2679,6 +2707,9 @@ impl Niri {
let geom = geom.to_physical(output_scale);
let size = geom.size;
let transform = output.current_transform();
let size = transform.transform_size(size);
let elements = self.render::<GlesRenderer>(renderer, &output, include_pointer);
let pixels = render_to_vec(
renderer,
+2
View File
@@ -112,6 +112,8 @@ impl PipeWire {
let mode = output.current_mode().unwrap();
let size = mode.size;
let transform = output.current_transform();
let size = transform.transform_size(size);
let refresh = mode.refresh;
let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new())
+7 -4
View File
@@ -42,6 +42,7 @@ pub enum ScreenshotUi {
pub struct OutputData {
size: Size<i32, Physical>,
scale: i32,
transform: Transform,
texture: GlesTexture,
texture_buffer: TextureBuffer<GlesTexture>,
buffers: [SolidColorBuffer; 8],
@@ -94,6 +95,7 @@ impl ScreenshotUi {
)
}
};
let scale = selection.0.current_scale().integer_scale();
let selection = (
selection.0,
@@ -104,9 +106,9 @@ impl ScreenshotUi {
let output_data = screenshots
.into_iter()
.map(|(output, texture)| {
let output_transform = output.current_transform();
let transform = output.current_transform();
let output_mode = output.current_mode().unwrap();
let size = output_transform.transform_size(output_mode.size);
let size = transform.transform_size(output_mode.size);
let scale = output.current_scale().integer_scale();
let texture_buffer = TextureBuffer::from_texture(
renderer,
@@ -129,6 +131,7 @@ impl ScreenshotUi {
let data = OutputData {
size,
scale,
transform,
texture,
texture_buffer,
buffers,
@@ -333,10 +336,10 @@ impl ScreenshotUi {
}
}
pub fn output_size(&self, output: &Output) -> Option<(Size<i32, Physical>, i32)> {
pub fn output_size(&self, output: &Output) -> Option<(Size<i32, Physical>, i32, Transform)> {
if let Self::Open { output_data, .. } = self {
let data = output_data.get(output)?;
Some((data.size, data.scale))
Some((data.size, data.scale, data.transform))
} else {
None
}