mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Implement niri msg output
This commit is contained in:
+11
-53
@@ -11,7 +11,7 @@ use bitflags::bitflags;
|
|||||||
use knuffel::errors::DecodeError;
|
use knuffel::errors::DecodeError;
|
||||||
use knuffel::Decode as _;
|
use knuffel::Decode as _;
|
||||||
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
|
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
|
||||||
use niri_ipc::{LayoutSwitchTarget, SizeChange, Transform};
|
use niri_ipc::{ConfiguredMode, LayoutSwitchTarget, SizeChange, Transform};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
|
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
|
||||||
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
|
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
|
||||||
@@ -250,7 +250,7 @@ pub struct Output {
|
|||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub position: Option<Position>,
|
pub position: Option<Position>,
|
||||||
#[knuffel(child, unwrap(argument, str))]
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
pub mode: Option<Mode>,
|
pub mode: Option<ConfiguredMode>,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub variable_refresh_rate: bool,
|
pub variable_refresh_rate: bool,
|
||||||
}
|
}
|
||||||
@@ -277,13 +277,6 @@ pub struct Position {
|
|||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct Mode {
|
|
||||||
pub width: u16,
|
|
||||||
pub height: u16,
|
|
||||||
pub refresh: Option<f64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
#[knuffel(child, default)]
|
#[knuffel(child, default)]
|
||||||
@@ -1971,41 +1964,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Mode {
|
|
||||||
type Err = miette::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let Some((width, rest)) = s.split_once('x') else {
|
|
||||||
return Err(miette!("no 'x' separator found"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let (height, refresh) = match rest.split_once('@') {
|
|
||||||
Some((height, refresh)) => (height, Some(refresh)),
|
|
||||||
None => (rest, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let width = width
|
|
||||||
.parse()
|
|
||||||
.into_diagnostic()
|
|
||||||
.context("error parsing width")?;
|
|
||||||
let height = height
|
|
||||||
.parse()
|
|
||||||
.into_diagnostic()
|
|
||||||
.context("error parsing height")?;
|
|
||||||
let refresh = refresh
|
|
||||||
.map(str::parse)
|
|
||||||
.transpose()
|
|
||||||
.into_diagnostic()
|
|
||||||
.context("error parsing refresh rate")?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
refresh,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Key {
|
impl FromStr for Key {
|
||||||
type Err = miette::Error;
|
type Err = miette::Error;
|
||||||
|
|
||||||
@@ -2336,7 +2294,7 @@ mod tests {
|
|||||||
scale: 2.,
|
scale: 2.,
|
||||||
transform: Transform::Flipped90,
|
transform: Transform::Flipped90,
|
||||||
position: Some(Position { x: 10, y: 20 }),
|
position: Some(Position { x: 10, y: 20 }),
|
||||||
mode: Some(Mode {
|
mode: Some(ConfiguredMode {
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
refresh: Some(144.),
|
refresh: Some(144.),
|
||||||
@@ -2574,8 +2532,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_mode() {
|
fn parse_mode() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"2560x1600@165.004".parse::<Mode>().unwrap(),
|
"2560x1600@165.004".parse::<ConfiguredMode>().unwrap(),
|
||||||
Mode {
|
ConfiguredMode {
|
||||||
width: 2560,
|
width: 2560,
|
||||||
height: 1600,
|
height: 1600,
|
||||||
refresh: Some(165.004),
|
refresh: Some(165.004),
|
||||||
@@ -2583,18 +2541,18 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1920x1080".parse::<Mode>().unwrap(),
|
"1920x1080".parse::<ConfiguredMode>().unwrap(),
|
||||||
Mode {
|
ConfiguredMode {
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
refresh: None,
|
refresh: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!("1920".parse::<Mode>().is_err());
|
assert!("1920".parse::<ConfiguredMode>().is_err());
|
||||||
assert!("1920x".parse::<Mode>().is_err());
|
assert!("1920x".parse::<ConfiguredMode>().is_err());
|
||||||
assert!("1920x1080@".parse::<Mode>().is_err());
|
assert!("1920x1080@".parse::<ConfiguredMode>().is_err());
|
||||||
assert!("1920x1080@60Hz".parse::<Mode>().is_err());
|
assert!("1920x1080@60Hz".parse::<ConfiguredMode>().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ pub enum Request {
|
|||||||
FocusedWindow,
|
FocusedWindow,
|
||||||
/// Perform an action.
|
/// Perform an action.
|
||||||
Action(Action),
|
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.
|
||||||
|
output: String,
|
||||||
|
/// Configuration to apply.
|
||||||
|
action: OutputAction,
|
||||||
|
},
|
||||||
/// Respond with an error (for testing error handling).
|
/// Respond with an error (for testing error handling).
|
||||||
ReturnError,
|
ReturnError,
|
||||||
}
|
}
|
||||||
@@ -245,6 +256,103 @@ pub enum LayoutSwitchTarget {
|
|||||||
Prev,
|
Prev,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Output actions that niri can perform.
|
||||||
|
// Variants in this enum should match the spelling of the ones in niri-config. Most thigs from
|
||||||
|
// niri-config should be present here.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "clap", derive(clap::Parser))]
|
||||||
|
#[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))]
|
||||||
|
#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))]
|
||||||
|
pub enum OutputAction {
|
||||||
|
/// Turn off the output.
|
||||||
|
Off,
|
||||||
|
/// Turn on the output.
|
||||||
|
On,
|
||||||
|
/// Set the output mode.
|
||||||
|
Mode {
|
||||||
|
/// Mode to set, or "auto" for automatic selection.
|
||||||
|
///
|
||||||
|
/// Run `niri msg outputs` to see the avaliable modes.
|
||||||
|
#[cfg_attr(feature = "clap", arg())]
|
||||||
|
mode: ModeToSet,
|
||||||
|
},
|
||||||
|
/// Set the output scale.
|
||||||
|
Scale {
|
||||||
|
/// Scale factor to set.
|
||||||
|
#[cfg_attr(feature = "clap", arg())]
|
||||||
|
scale: f64,
|
||||||
|
},
|
||||||
|
/// Set the output transform.
|
||||||
|
Transform {
|
||||||
|
/// Transform to set, counter-clockwise.
|
||||||
|
#[cfg_attr(feature = "clap", arg())]
|
||||||
|
transform: Transform,
|
||||||
|
},
|
||||||
|
/// Set the output position.
|
||||||
|
Position {
|
||||||
|
/// Position to set, or "auto" for automatic selection.
|
||||||
|
#[cfg_attr(feature = "clap", command(subcommand))]
|
||||||
|
position: PositionToSet,
|
||||||
|
},
|
||||||
|
/// Toggle variable refresh rate.
|
||||||
|
Vrr {
|
||||||
|
/// Whether to enable variable refresh rate.
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "clap",
|
||||||
|
arg(
|
||||||
|
value_name = "ON|OFF",
|
||||||
|
action = clap::ArgAction::Set,
|
||||||
|
value_parser = clap::builder::BoolishValueParser::new(),
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
enable: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output mode to set.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum ModeToSet {
|
||||||
|
/// Niri will pick the mode automatically.
|
||||||
|
Automatic,
|
||||||
|
/// Specific mode.
|
||||||
|
Specific(ConfiguredMode),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output mode as set in the config file.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ConfiguredMode {
|
||||||
|
/// Width in physical pixels.
|
||||||
|
pub width: u16,
|
||||||
|
/// Height in physical pixels.
|
||||||
|
pub height: u16,
|
||||||
|
/// Refresh rate.
|
||||||
|
pub refresh: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output position to set.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "clap", derive(clap::Subcommand))]
|
||||||
|
#[cfg_attr(feature = "clap", command(subcommand_value_name = "POSITION"))]
|
||||||
|
#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Position Values"))]
|
||||||
|
pub enum PositionToSet {
|
||||||
|
/// Position the output automatically.
|
||||||
|
#[cfg_attr(feature = "clap", command(name = "auto"))]
|
||||||
|
Automatic,
|
||||||
|
/// Set a specific position.
|
||||||
|
#[cfg_attr(feature = "clap", command(name = "set"))]
|
||||||
|
Specific(ConfiguredPosition),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output position as set in the config file.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "clap", derive(clap::Args))]
|
||||||
|
pub struct ConfiguredPosition {
|
||||||
|
/// Logical X position.
|
||||||
|
pub x: i32,
|
||||||
|
/// Logical Y position.
|
||||||
|
pub y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Connected output.
|
/// Connected output.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
@@ -304,6 +412,7 @@ pub struct LogicalOutput {
|
|||||||
|
|
||||||
/// Output transform, which goes counter-clockwise.
|
/// Output transform, which goes counter-clockwise.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||||
pub enum Transform {
|
pub enum Transform {
|
||||||
/// Untransformed.
|
/// Untransformed.
|
||||||
Normal,
|
Normal,
|
||||||
@@ -319,10 +428,13 @@ pub enum Transform {
|
|||||||
/// Flipped horizontally.
|
/// Flipped horizontally.
|
||||||
Flipped,
|
Flipped,
|
||||||
/// Rotated by 90° and flipped horizontally.
|
/// Rotated by 90° and flipped horizontally.
|
||||||
|
#[cfg_attr(feature = "clap", value(name("flipped-90")))]
|
||||||
Flipped90,
|
Flipped90,
|
||||||
/// Flipped vertically.
|
/// Flipped vertically.
|
||||||
|
#[cfg_attr(feature = "clap", value(name("flipped-180")))]
|
||||||
Flipped180,
|
Flipped180,
|
||||||
/// Rotated by 270° and flipped horizontally.
|
/// Rotated by 270° and flipped horizontally.
|
||||||
|
#[cfg_attr(feature = "clap", value(name("flipped-270")))]
|
||||||
Flipped270,
|
Flipped270,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,3 +519,44 @@ impl FromStr for Transform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for ModeToSet {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s.eq_ignore_ascii_case("auto") {
|
||||||
|
return Ok(Self::Automatic);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode = s.parse()?;
|
||||||
|
Ok(Self::Specific(mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ConfiguredMode {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let Some((width, rest)) = s.split_once('x') else {
|
||||||
|
return Err("no 'x' separator found");
|
||||||
|
};
|
||||||
|
|
||||||
|
let (height, refresh) = match rest.split_once('@') {
|
||||||
|
Some((height, refresh)) => (height, Some(refresh)),
|
||||||
|
None => (rest, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = width.parse().map_err(|_| "error parsing width")?;
|
||||||
|
let height = height.parse().map_err(|_| "error parsing height")?;
|
||||||
|
let refresh = refresh
|
||||||
|
.map(str::parse)
|
||||||
|
.transpose()
|
||||||
|
.map_err(|_| "error parsing refresh rate")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
refresh,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -2111,7 +2111,7 @@ fn queue_estimated_vblank_timer(
|
|||||||
|
|
||||||
fn pick_mode(
|
fn pick_mode(
|
||||||
connector: &connector::Info,
|
connector: &connector::Info,
|
||||||
target: Option<niri_config::Mode>,
|
target: Option<niri_ipc::ConfiguredMode>,
|
||||||
) -> Option<(control::Mode, bool)> {
|
) -> Option<(control::Mode, bool)> {
|
||||||
let mut mode = None;
|
let mut mode = None;
|
||||||
let mut fallback = false;
|
let mut fallback = false;
|
||||||
|
|||||||
+16
-1
@@ -2,7 +2,7 @@ use std::ffi::OsString;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use niri_ipc::Action;
|
use niri_ipc::{Action, OutputAction};
|
||||||
|
|
||||||
use crate::utils::version;
|
use crate::utils::version;
|
||||||
|
|
||||||
@@ -63,6 +63,21 @@ pub enum Msg {
|
|||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
action: Action,
|
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.
|
/// Request an error from the running niri instance.
|
||||||
RequestError,
|
RequestError,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
|||||||
Msg::Outputs => Request::Outputs,
|
Msg::Outputs => Request::Outputs,
|
||||||
Msg::FocusedWindow => Request::FocusedWindow,
|
Msg::FocusedWindow => Request::FocusedWindow,
|
||||||
Msg::Action { action } => Request::Action(action.clone()),
|
Msg::Action { action } => Request::Action(action.clone()),
|
||||||
|
Msg::Output { output, action } => Request::Output {
|
||||||
|
output: output.clone(),
|
||||||
|
action: action.clone(),
|
||||||
|
},
|
||||||
Msg::RequestError => Request::ReturnError,
|
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:?}");
|
bail!("unexpected response: expected Handled, got {response:?}");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Msg::Output { .. } => {
|
||||||
|
let Response::Handled = response else {
|
||||||
|
bail!("unexpected response: expected Handled, got {response:?}");
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -169,6 +169,12 @@ fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
|||||||
});
|
});
|
||||||
Response::Handled
|
Response::Handled
|
||||||
}
|
}
|
||||||
|
Request::Output { output, action } => {
|
||||||
|
ctx.event_loop.insert_idle(move |state| {
|
||||||
|
state.apply_transient_output_config(&output, action);
|
||||||
|
});
|
||||||
|
Response::Handled
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
|||||||
+115
-46
@@ -138,6 +138,13 @@ const FRAME_CALLBACK_THROTTLE: Option<Duration> = Some(Duration::from_millis(995
|
|||||||
pub struct Niri {
|
pub struct Niri {
|
||||||
pub config: Rc<RefCell<Config>>,
|
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 event_loop: LoopHandle<'static, State>,
|
||||||
pub scheduler: Scheduler<()>,
|
pub scheduler: Scheduler<()>,
|
||||||
pub stop_signal: LoopSignal,
|
pub stop_signal: LoopSignal,
|
||||||
@@ -858,6 +865,7 @@ impl State {
|
|||||||
let mut reload_xkb = None;
|
let mut reload_xkb = None;
|
||||||
let mut libinput_config_changed = false;
|
let mut libinput_config_changed = false;
|
||||||
let mut output_config_changed = false;
|
let mut output_config_changed = false;
|
||||||
|
let mut preserved_output_config = None;
|
||||||
let mut window_rules_changed = false;
|
let mut window_rules_changed = false;
|
||||||
let mut debug_config_changed = false;
|
let mut debug_config_changed = false;
|
||||||
let mut shaders_changed = false;
|
let mut shaders_changed = false;
|
||||||
@@ -894,8 +902,15 @@ impl State {
|
|||||||
libinput_config_changed = true;
|
libinput_config_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.outputs != old_config.outputs {
|
if config.outputs != self.niri.config_file_output_config {
|
||||||
output_config_changed = true;
|
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 {
|
if config.binds != old_config.binds {
|
||||||
@@ -926,6 +941,10 @@ impl State {
|
|||||||
|
|
||||||
*old_config = config;
|
*old_config = config;
|
||||||
|
|
||||||
|
if let Some(outputs) = preserved_output_config {
|
||||||
|
old_config.outputs = outputs;
|
||||||
|
}
|
||||||
|
|
||||||
// Release the borrow.
|
// Release the borrow.
|
||||||
drop(old_config);
|
drop(old_config);
|
||||||
|
|
||||||
@@ -945,51 +964,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if output_config_changed {
|
if output_config_changed {
|
||||||
let mut resized_outputs = vec![];
|
self.reload_output_config();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug_config_changed {
|
if debug_config_changed {
|
||||||
@@ -1032,6 +1007,98 @@ impl State {
|
|||||||
self.niri.queue_redraw_all();
|
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) {
|
pub fn refresh_ipc_outputs(&mut self) {
|
||||||
if !self.niri.ipc_outputs_changed {
|
if !self.niri.ipc_outputs_changed {
|
||||||
return;
|
return;
|
||||||
@@ -1175,6 +1242,7 @@ impl Niri {
|
|||||||
|
|
||||||
let display_handle = display.handle();
|
let display_handle = display.handle();
|
||||||
let config_ = config.borrow();
|
let config_ = config.borrow();
|
||||||
|
let config_file_output_config = config_.outputs.clone();
|
||||||
|
|
||||||
let layout = Layout::new(&config_);
|
let layout = Layout::new(&config_);
|
||||||
|
|
||||||
@@ -1353,6 +1421,7 @@ impl Niri {
|
|||||||
drop(config_);
|
drop(config_);
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
|
config_file_output_config,
|
||||||
|
|
||||||
event_loop,
|
event_loop,
|
||||||
scheduler,
|
scheduler,
|
||||||
|
|||||||
Reference in New Issue
Block a user