mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Make output position configurable
Implements https://github.com/YaLTeR/niri/issues/14
This commit is contained in:
@@ -33,6 +33,17 @@ input {
|
|||||||
/-output "eDP-1" {
|
/-output "eDP-1" {
|
||||||
// Scale is a floating-point number, but at the moment only integer values work.
|
// Scale is a floating-point number, but at the moment only integer values work.
|
||||||
scale 2.0
|
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.
|
// Add lines like this to spawn processes at startup.
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ pub struct Output {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
#[knuffel(child, unwrap(argument), default = 1.)]
|
#[knuffel(child, unwrap(argument), default = 1.)]
|
||||||
pub scale: f64,
|
pub scale: f64,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub position: Option<Position>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Output {
|
impl Default for Output {
|
||||||
@@ -84,10 +86,19 @@ impl Default for Output {
|
|||||||
Self {
|
Self {
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
scale: 1.,
|
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)]
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SpawnAtStartup {
|
pub struct SpawnAtStartup {
|
||||||
#[knuffel(arguments)]
|
#[knuffel(arguments)]
|
||||||
@@ -342,6 +353,7 @@ mod tests {
|
|||||||
|
|
||||||
output "eDP-1" {
|
output "eDP-1" {
|
||||||
scale 2.0
|
scale 2.0
|
||||||
|
position x=10 y=20
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn-at-startup "alacritty" "-e" "fish"
|
spawn-at-startup "alacritty" "-e" "fish"
|
||||||
@@ -387,6 +399,7 @@ mod tests {
|
|||||||
outputs: vec![Output {
|
outputs: vec![Output {
|
||||||
name: "eDP-1".to_owned(),
|
name: "eDP-1".to_owned(),
|
||||||
scale: 2.,
|
scale: 2.,
|
||||||
|
position: Some(Position { x: 10, y: 20 }),
|
||||||
}],
|
}],
|
||||||
spawn_at_startup: vec![SpawnAtStartup {
|
spawn_at_startup: vec![SpawnAtStartup {
|
||||||
command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()],
|
command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()],
|
||||||
|
|||||||
+61
-9
@@ -74,7 +74,7 @@ use crate::dbus::mutter_display_config::DisplayConfig;
|
|||||||
use crate::dbus::mutter_screen_cast::{self, ScreenCast, ToNiriMsg};
|
use crate::dbus::mutter_screen_cast::{self, ScreenCast, ToNiriMsg};
|
||||||
use crate::dbus::mutter_service_channel::ServiceChannel;
|
use crate::dbus::mutter_service_channel::ServiceChannel;
|
||||||
use crate::frame_clock::FrameClock;
|
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::pw_utils::{Cast, PipeWire};
|
||||||
use crate::utils::{center, get_monotonic_time, load_default_cursor, make_screenshot_path};
|
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>) {
|
pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) {
|
||||||
let global = output.create_global::<State>(&self.display_handle);
|
let global = output.create_global::<State>(&self.display_handle);
|
||||||
|
|
||||||
let x = self
|
let name = output.name();
|
||||||
.global_space
|
let config = self
|
||||||
.outputs()
|
.config
|
||||||
.map(|output| self.global_space.output_geometry(output).unwrap())
|
.borrow()
|
||||||
.map(|geom| geom.loc.x + geom.size.w)
|
.outputs
|
||||||
.max()
|
.iter()
|
||||||
.unwrap_or(0);
|
.find(|o| o.name == name)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
self.global_space.map_output(&output, (x, 0));
|
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))
|
||||||
|
});
|
||||||
|
|
||||||
|
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());
|
self.monitor_set.add_output(output.clone());
|
||||||
|
|
||||||
let state = OutputState {
|
let state = OutputState {
|
||||||
|
|||||||
Reference in New Issue
Block a user