aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/tty.rs2
-rw-r--r--src/cli.rs17
-rw-r--r--src/ipc/client.rs9
-rw-r--r--src/ipc/server.rs6
-rw-r--r--src/niri.rs161
5 files changed, 147 insertions, 48 deletions
diff --git a/src/backend/tty.rs b/src/backend/tty.rs
index 327a5c97..4e7055ee 100644
--- a/src/backend/tty.rs
+++ b/src/backend/tty.rs
@@ -2111,7 +2111,7 @@ fn queue_estimated_vblank_timer(
fn pick_mode(
connector: &connector::Info,
- target: Option<niri_config::Mode>,
+ target: Option<niri_ipc::ConfiguredMode>,
) -> Option<(control::Mode, bool)> {
let mut mode = None;
let mut fallback = false;
diff --git a/src/cli.rs b/src/cli.rs
index 78f9fc0e..65e2fd14 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -2,7 +2,7 @@ use std::ffi::OsString;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
-use niri_ipc::Action;
+use niri_ipc::{Action, OutputAction};
use crate::utils::version;
@@ -63,6 +63,21 @@ pub enum Msg {
#[command(subcommand)]
action: Action,
},
+ /// Change output configuration temporarily.
+ ///
+ /// The configuration is changed temporarily and not saved into the config file. If the output
+ /// configuration subsequently changes in the config file, these temporary changes will be
+ /// forgotten.
+ Output {
+ /// Output name.
+ ///
+ /// Run `niri msg outputs` to see the output names.
+ #[arg()]
+ output: String,
+ /// Configuration to apply.
+ #[command(subcommand)]
+ action: OutputAction,
+ },
/// Request an error from the running niri instance.
RequestError,
}
diff --git a/src/ipc/client.rs b/src/ipc/client.rs
index 1704adfb..3aa5eb22 100644
--- a/src/ipc/client.rs
+++ b/src/ipc/client.rs
@@ -11,6 +11,10 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
Msg::Outputs => Request::Outputs,
Msg::FocusedWindow => Request::FocusedWindow,
Msg::Action { action } => Request::Action(action.clone()),
+ Msg::Output { output, action } => Request::Output {
+ output: output.clone(),
+ action: action.clone(),
+ },
Msg::RequestError => Request::ReturnError,
};
@@ -237,6 +241,11 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
bail!("unexpected response: expected Handled, got {response:?}");
};
}
+ Msg::Output { .. } => {
+ let Response::Handled = response else {
+ bail!("unexpected response: expected Handled, got {response:?}");
+ };
+ }
}
Ok(())
diff --git a/src/ipc/server.rs b/src/ipc/server.rs
index 5e18c16a..59f929d8 100644
--- a/src/ipc/server.rs
+++ b/src/ipc/server.rs
@@ -169,6 +169,12 @@ fn process(ctx: &ClientCtx, request: Request) -> Reply {
});
Response::Handled
}
+ Request::Output { output, action } => {
+ ctx.event_loop.insert_idle(move |state| {
+ state.apply_transient_output_config(&output, action);
+ });
+ Response::Handled
+ }
};
Ok(response)
diff --git a/src/niri.rs b/src/niri.rs
index b1e2d917..b6bfb84d 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -138,6 +138,13 @@ const FRAME_CALLBACK_THROTTLE: Option<Duration> = Some(Duration::from_millis(995
pub struct Niri {
pub config: Rc<RefCell<Config>>,
+ /// Output config from the config file.
+ ///
+ /// This does not include transient output config changes done via IPC. It is only used when
+ /// reloading the config from disk to determine if the output configuration should be reloaded
+ /// (and transient changes dropped).
+ pub config_file_output_config: Vec<niri_config::Output>,
+
pub event_loop: LoopHandle<'static, State>,
pub scheduler: Scheduler<()>,
pub stop_signal: LoopSignal,
@@ -858,6 +865,7 @@ impl State {
let mut reload_xkb = None;
let mut libinput_config_changed = false;
let mut output_config_changed = false;
+ let mut preserved_output_config = None;
let mut window_rules_changed = false;
let mut debug_config_changed = false;
let mut shaders_changed = false;
@@ -894,8 +902,15 @@ impl State {
libinput_config_changed = true;
}
- if config.outputs != old_config.outputs {
+ if config.outputs != self.niri.config_file_output_config {
output_config_changed = true;
+ self.niri
+ .config_file_output_config
+ .clone_from(&config.outputs);
+ } else {
+ // Output config did not change from the last disk load, so we need to preserve the
+ // transient changes.
+ preserved_output_config = Some(mem::take(&mut old_config.outputs));
}
if config.binds != old_config.binds {
@@ -926,6 +941,10 @@ impl State {
*old_config = config;
+ if let Some(outputs) = preserved_output_config {
+ old_config.outputs = outputs;
+ }
+
// Release the borrow.
drop(old_config);
@@ -945,51 +964,7 @@ impl State {
}
if output_config_changed {
- let mut resized_outputs = vec![];
- for output in self.niri.global_space.outputs() {
- let name = output.name();
- 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_else(|| {
- let size_mm = output.physical_properties().size;
- let resolution = output.current_mode().unwrap().size;
- guess_monitor_scale(size_mm, resolution)
- });
- let scale = scale.clamp(1., 10.).ceil() as i32;
-
- let mut transform = config
- .map(|c| ipc_transform_to_smithay(c.transform))
- .unwrap_or(Transform::Normal);
- // FIXME: fix winit damage on other transforms.
- if name == "winit" {
- transform = Transform::Flipped180;
- }
-
- if output.current_scale().integer_scale() != scale
- || output.current_transform() != transform
- {
- output.change_current_state(
- None,
- Some(transform),
- Some(output::Scale::Integer(scale)),
- None,
- );
- self.niri.ipc_outputs_changed = true;
- resized_outputs.push(output.clone());
- }
- }
- for output in resized_outputs {
- self.niri.output_resized(&output);
- }
-
- self.backend.on_output_config_changed(&mut self.niri);
-
- self.niri.reposition_outputs(None);
-
- if let Some(touch) = self.niri.seat.get_touch() {
- touch.cancel(self);
- }
+ self.reload_output_config();
}
if debug_config_changed {
@@ -1032,6 +1007,98 @@ impl State {
self.niri.queue_redraw_all();
}
+ fn reload_output_config(&mut self) {
+ let mut resized_outputs = vec![];
+ for output in self.niri.global_space.outputs() {
+ let name = output.name();
+ 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_else(|| {
+ let size_mm = output.physical_properties().size;
+ let resolution = output.current_mode().unwrap().size;
+ guess_monitor_scale(size_mm, resolution)
+ });
+ let scale = scale.clamp(1., 10.).ceil() as i32;
+
+ let mut transform = config
+ .map(|c| ipc_transform_to_smithay(c.transform))
+ .unwrap_or(Transform::Normal);
+ // FIXME: fix winit damage on other transforms.
+ if name == "winit" {
+ transform = Transform::Flipped180;
+ }
+
+ if output.current_scale().integer_scale() != scale
+ || output.current_transform() != transform
+ {
+ output.change_current_state(
+ None,
+ Some(transform),
+ Some(output::Scale::Integer(scale)),
+ None,
+ );
+ self.niri.ipc_outputs_changed = true;
+ resized_outputs.push(output.clone());
+ }
+ }
+ for output in resized_outputs {
+ self.niri.output_resized(&output);
+ }
+
+ self.backend.on_output_config_changed(&mut self.niri);
+
+ self.niri.reposition_outputs(None);
+
+ if let Some(touch) = self.niri.seat.get_touch() {
+ touch.cancel(self);
+ }
+ }
+
+ pub fn apply_transient_output_config(&mut self, name: &str, action: niri_ipc::OutputAction) {
+ {
+ let mut config = self.niri.config.borrow_mut();
+ let config = if let Some(config) = config.outputs.iter_mut().find(|o| o.name == name) {
+ config
+ } else {
+ config.outputs.push(niri_config::Output {
+ name: String::from(name),
+ ..Default::default()
+ });
+ config.outputs.last_mut().unwrap()
+ };
+
+ match action {
+ niri_ipc::OutputAction::Off => config.off = true,
+ niri_ipc::OutputAction::On => config.off = false,
+ niri_ipc::OutputAction::Mode { mode } => {
+ config.mode = match mode {
+ niri_ipc::ModeToSet::Automatic => None,
+ niri_ipc::ModeToSet::Specific(mode) => Some(mode),
+ }
+ }
+ niri_ipc::OutputAction::Scale { scale } => config.scale = scale,
+ niri_ipc::OutputAction::Transform { transform } => config.transform = transform,
+ niri_ipc::OutputAction::Position { position } => {
+ config.position = match position {
+ niri_ipc::PositionToSet::Automatic => None,
+ niri_ipc::PositionToSet::Specific(position) => {
+ Some(niri_config::Position {
+ x: position.x,
+ y: position.y,
+ })
+ }
+ }
+ }
+ niri_ipc::OutputAction::Vrr { enable } => {
+ config.variable_refresh_rate = enable;
+ }
+ }
+ }
+
+ self.reload_output_config();
+ }
+
pub fn refresh_ipc_outputs(&mut self) {
if !self.niri.ipc_outputs_changed {
return;
@@ -1175,6 +1242,7 @@ impl Niri {
let display_handle = display.handle();
let config_ = config.borrow();
+ let config_file_output_config = config_.outputs.clone();
let layout = Layout::new(&config_);
@@ -1353,6 +1421,7 @@ impl Niri {
drop(config_);
Self {
config,
+ config_file_output_config,
event_loop,
scheduler,