diff options
| -rw-r--r-- | resources/default-config.kdl | 11 | ||||
| -rw-r--r-- | src/config.rs | 13 | ||||
| -rw-r--r-- | src/niri.rs | 70 |
3 files changed, 85 insertions, 9 deletions
diff --git a/resources/default-config.kdl b/resources/default-config.kdl index 5ed8e7ce..8c5a9527 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -33,6 +33,17 @@ input { /-output "eDP-1" { // Scale is a floating-point number, but at the moment only integer values work. scale 2.0 + + // Position of the output in the global coordinate space. + // This affects directional monitor actions like "focus-monitor-left", and cursor movement. + // The cursor can only move between directly adjacent outputs. + // Output scale has to be taken into account for positioning: + // outputs are sized in logical, or scaled, pixels. + // For example, a 3840×2160 output with scale 2.0 will have a logical size of 1920×1080, + // so to put another output directly adjacent to it on the right, set its x to 1920. + // It the position is unset or results in an overlap, the output is instead placed + // automatically. + position x=1280 y=0 } // Add lines like this to spawn processes at startup. diff --git a/src/config.rs b/src/config.rs index 82ff7fb2..a261c64c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -77,6 +77,8 @@ pub struct Output { pub name: String, #[knuffel(child, unwrap(argument), default = 1.)] pub scale: f64, + #[knuffel(child)] + pub position: Option<Position>, } impl Default for Output { @@ -84,11 +86,20 @@ impl Default for Output { Self { name: String::new(), scale: 1., + position: None, } } } #[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)] +pub struct Position { + #[knuffel(property)] + pub x: i32, + #[knuffel(property)] + pub y: i32, +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)] pub struct SpawnAtStartup { #[knuffel(arguments)] pub command: Vec<String>, @@ -342,6 +353,7 @@ mod tests { output "eDP-1" { scale 2.0 + position x=10 y=20 } spawn-at-startup "alacritty" "-e" "fish" @@ -387,6 +399,7 @@ mod tests { outputs: vec![Output { name: "eDP-1".to_owned(), scale: 2., + position: Some(Position { x: 10, y: 20 }), }], spawn_at_startup: vec![SpawnAtStartup { command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()], diff --git a/src/niri.rs b/src/niri.rs index 6f152a00..48761702 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -74,7 +74,7 @@ use crate::dbus::mutter_display_config::DisplayConfig; use crate::dbus::mutter_screen_cast::{self, ScreenCast, ToNiriMsg}; use crate::dbus::mutter_service_channel::ServiceChannel; use crate::frame_clock::FrameClock; -use crate::layout::{MonitorRenderElement, MonitorSet}; +use crate::layout::{output_size, MonitorRenderElement, MonitorSet}; use crate::pw_utils::{Cast, PipeWire}; use crate::utils::{center, get_monotonic_time, load_default_cursor, make_screenshot_path}; @@ -668,15 +668,67 @@ impl Niri { pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) { let global = output.create_global::<State>(&self.display_handle); - let x = self - .global_space - .outputs() - .map(|output| self.global_space.output_geometry(output).unwrap()) - .map(|geom| geom.loc.x + geom.size.w) - .max() - .unwrap_or(0); + let name = output.name(); + let config = self + .config + .borrow() + .outputs + .iter() + .find(|o| o.name == name) + .cloned() + .unwrap_or_default(); + + let size = output_size(&output); + let position = config + .position + .map(|pos| Point::from((pos.x, pos.y))) + .filter(|pos| { + // Ensure that the requested position does not overlap any existing output. + let target_geom = Rectangle::from_loc_and_size(*pos, size); + + let overlap = self + .global_space + .outputs() + .map(|output| self.global_space.output_geometry(output).unwrap()) + .find(|geom| geom.overlaps(target_geom)); + + if let Some(overlap) = overlap { + warn!( + "new output {name} at x={} y={} sized {}x{} \ + overlaps an existing output at x={} y={} sized {}x{}, \ + falling back to automatic placement", + pos.x, + pos.y, + size.w, + size.h, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + + false + } else { + true + } + }) + .unwrap_or_else(|| { + let x = self + .global_space + .outputs() + .map(|output| self.global_space.output_geometry(output).unwrap()) + .map(|geom| geom.loc.x + geom.size.w) + .max() + .unwrap_or(0); + + Point::from((x, 0)) + }); - self.global_space.map_output(&output, (x, 0)); + debug!( + "putting new output {name} at x={} y={}", + position.x, position.y + ); + self.global_space.map_output(&output, position); self.monitor_set.add_output(output.clone()); let state = OutputState { |
