aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraxtloss <axtlos@getcryst.al>2024-01-28 14:25:40 +0100
committerIvan Molodetskikh <yalterz@gmail.com>2024-01-31 23:02:38 +0400
commit962e159db61dc6c7822aa899b1d9dc86fb6a0de5 (patch)
tree9ebdc148d411230cf33e5b4abcd1880af484d8e7
parent11bff3a2f1aa069a998cd6710dd06467acb73920 (diff)
downloadniri-962e159db61dc6c7822aa899b1d9dc86fb6a0de5.tar.gz
niri-962e159db61dc6c7822aa899b1d9dc86fb6a0de5.tar.bz2
niri-962e159db61dc6c7822aa899b1d9dc86fb6a0de5.zip
Add option to rotate outputs
-rw-r--r--niri-config/src/lib.rs54
-rw-r--r--resources/default-config.kdl4
-rw-r--r--src/niri.rs97
-rw-r--r--src/pw_utils.rs2
-rw-r--r--src/screenshot_ui.rs11
5 files changed, 131 insertions, 37 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index 03b39390..98d2097e 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -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,
diff --git a/resources/default-config.kdl b/resources/default-config.kdl
index 19d69d4b..4aae7cc4 100644
--- a/resources/default-config.kdl
+++ b/resources/default-config.kdl
@@ -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
diff --git a/src/niri.rs b/src/niri.rs
index 268dd3f4..2ed62c99 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -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 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 before adding to the layout since that will read the output size.
- output.change_current_state(None, None, Some(output::Scale::Integer(scale)), None);
+ // 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,
diff --git a/src/pw_utils.rs b/src/pw_utils.rs
index 097cfdd7..45ccc559 100644
--- a/src/pw_utils.rs
+++ b/src/pw_utils.rs
@@ -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())
diff --git a/src/screenshot_ui.rs b/src/screenshot_ui.rs
index 2d097a24..f50e84d6 100644
--- a/src/screenshot_ui.rs
+++ b/src/screenshot_ui.rs
@@ -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
}