mirror of
https://github.com/YaLTeR/niri.git
synced 2024-09-11 12:35:58 +03:00
Implement ideal scale factor guessing
This commit is contained in:
parent
f9b008163c
commit
dc1d2b706c
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -668,6 +668,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.6"
|
||||
@ -814,6 +824,12 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@ -1789,6 +1805,23 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k9"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "088bcebb5b68b1b14b64d7f05b0f802719250b97fdc0338ec42529ea777ed614"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
"diff",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"proc-macro2",
|
||||
"regex",
|
||||
"syn 2.0.55",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyframe"
|
||||
version = "1.1.1"
|
||||
@ -2174,6 +2207,7 @@ dependencies = [
|
||||
"git-version",
|
||||
"glam",
|
||||
"input",
|
||||
"k9",
|
||||
"keyframe",
|
||||
"libc",
|
||||
"log",
|
||||
@ -3308,6 +3342,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
|
||||
dependencies = [
|
||||
"rustix 0.37.27",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
|
@ -96,6 +96,7 @@ features = [
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
k9 = "0.12.0"
|
||||
proptest = "1.4.0"
|
||||
proptest-derive = "0.4.0"
|
||||
xshell = "0.2.5"
|
||||
|
13
src/niri.rs
13
src/niri.rs
@ -116,6 +116,7 @@ use crate::ui::config_error_notification::ConfigErrorNotification;
|
||||
use crate::ui::exit_confirm_dialog::ExitConfirmDialog;
|
||||
use crate::ui::hotkey_overlay::HotkeyOverlay;
|
||||
use crate::ui::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
|
||||
use crate::utils::scale::guess_monitor_scale;
|
||||
use crate::utils::spawning::CHILD_ENV;
|
||||
use crate::utils::{
|
||||
center, center_f64, get_monotonic_time, ipc_transform_to_smithay, logical_output,
|
||||
@ -934,7 +935,11 @@ impl State {
|
||||
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 = 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
|
||||
@ -1565,7 +1570,11 @@ impl Niri {
|
||||
|
||||
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 = c.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 = c
|
||||
.map(|c| ipc_transform_to_smithay(c.transform))
|
||||
|
@ -15,6 +15,7 @@ use smithay::reexports::rustix::time::{clock_gettime, ClockId};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size, Transform};
|
||||
|
||||
pub mod id;
|
||||
pub mod scale;
|
||||
pub mod spawning;
|
||||
pub mod watcher;
|
||||
|
||||
|
104
src/utils/scale.rs
Normal file
104
src/utils/scale.rs
Normal file
@ -0,0 +1,104 @@
|
||||
//! Default monitor scale calculation.
|
||||
//!
|
||||
//! This module follows logic and tests from Mutter:
|
||||
//! https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-46/src/backends/meta-monitor.c
|
||||
|
||||
use smithay::utils::{Physical, Raw, Size};
|
||||
|
||||
const MIN_SCALE: i32 = 1;
|
||||
const MAX_SCALE: i32 = 4;
|
||||
const MIN_LOGICAL_AREA: i32 = 800 * 480;
|
||||
|
||||
const MOBILE_TARGET_DPI: f64 = 135.;
|
||||
const LARGE_TARGET_DPI: f64 = 110.;
|
||||
const LARGE_MIN_SIZE_INCHES: f64 = 20.;
|
||||
|
||||
/// Calculates the ideal scale for a monitor.
|
||||
pub fn guess_monitor_scale(size_mm: Size<i32, Raw>, resolution: Size<i32, Physical>) -> f64 {
|
||||
if size_mm.w == 0 || size_mm.h == 0 {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
let diag_inches = f64::from(size_mm.w * size_mm.w + size_mm.h * size_mm.h).sqrt() / 25.4;
|
||||
|
||||
let target_dpi = if diag_inches < LARGE_MIN_SIZE_INCHES {
|
||||
MOBILE_TARGET_DPI
|
||||
} else {
|
||||
LARGE_TARGET_DPI
|
||||
};
|
||||
|
||||
let physical_dpi =
|
||||
f64::from(resolution.w * resolution.w + resolution.h * resolution.h).sqrt() / diag_inches;
|
||||
let perfect_scale = physical_dpi / target_dpi;
|
||||
|
||||
// For integer scaling factors (we currently only do integer), bias the perfect scale down.
|
||||
let perfect_scale = perfect_scale - 0.15;
|
||||
|
||||
supported_scales(resolution)
|
||||
.map(|scale| (scale, (scale - perfect_scale).abs()))
|
||||
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
|
||||
.map_or(1., |(scale, _)| scale)
|
||||
}
|
||||
|
||||
fn supported_scales(resolution: Size<i32, Physical>) -> impl Iterator<Item = f64> {
|
||||
(MIN_SCALE..=MAX_SCALE)
|
||||
.filter(move |scale| is_valid_for_resolution(resolution, *scale))
|
||||
.map(f64::from)
|
||||
}
|
||||
|
||||
fn is_valid_for_resolution(resolution: Size<i32, Physical>, scale: i32) -> bool {
|
||||
let logical = resolution.to_logical(scale);
|
||||
logical.w * logical.h >= MIN_LOGICAL_AREA
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use k9::snapshot;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn check(size_mm: (i32, i32), resolution: (i32, i32)) -> f64 {
|
||||
guess_monitor_scale(Size::from(size_mm), Size::from(resolution))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_monitor_scale() {
|
||||
// Librem 5; not enough logical area when scaled
|
||||
snapshot!(check((65, 129), (720, 1440)), "1.0");
|
||||
// OnePlus 6
|
||||
snapshot!(check((68, 144), (1080, 2280)), "2.0");
|
||||
// Google Pixel 6a
|
||||
snapshot!(check((64, 142), (1080, 2400)), "2.0");
|
||||
// 13" MacBook Retina
|
||||
snapshot!(check((286, 179), (2560, 1600)), "2.0");
|
||||
// Surface Laptop Studio
|
||||
snapshot!(check((303, 202), (2400, 1600)), "1.0");
|
||||
// Dell XPS 9320
|
||||
snapshot!(check((290, 180), (3840, 2400)), "2.0");
|
||||
// Lenovo ThinkPad X1 Yoga Gen 6
|
||||
snapshot!(check((300, 190), (3840, 2400)), "2.0");
|
||||
// Generic 23" 1080p
|
||||
snapshot!(check((509, 286), (1920, 1080)), "1.0");
|
||||
// Generic 23" 4K
|
||||
snapshot!(check((509, 286), (3840, 2160)), "2.0");
|
||||
// Generic 27" 4K
|
||||
snapshot!(check((598, 336), (3840, 2160)), "1.0");
|
||||
// Generic 32" 4K
|
||||
snapshot!(check((708, 398), (3840, 2160)), "1.0");
|
||||
// Generic 25" 4K; ideal scale is 1.60, should round to 1.5 and 1.0
|
||||
snapshot!(check((554, 312), (3840, 2160)), "1.0");
|
||||
// Generic 23.5" 4K; ideal scale is 1.70, should round to 1.75 and 2.0
|
||||
snapshot!(check((522, 294), (3840, 2160)), "2.0");
|
||||
// Lenovo Legion 7 Gen 7 AMD 16"
|
||||
snapshot!(check((340, 210), (2560, 1600)), "1.0");
|
||||
// Acer Nitro XV320QU LV 31.5"
|
||||
snapshot!(check((700, 390), (2560, 1440)), "1.0");
|
||||
// Surface Pro 6
|
||||
snapshot!(check((260, 170), (2736, 1824)), "2.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guess_monitor_scale_unknown_size() {
|
||||
assert_eq!(check((0, 0), (1920, 1080)), 1.);
|
||||
}
|
||||
}
|
@ -68,6 +68,8 @@ Set the scale of the monitor.
|
||||
|
||||
This is a floating-point number to enable fractional scaling in the future, but at the moment only integer scale values will work.
|
||||
|
||||
<sup>Since: 0.1.6</sup> If scale is unset, niri will guess an appropriate scale based on the physical dimensions and the resolution of the monitor.
|
||||
|
||||
```
|
||||
output "eDP-1" {
|
||||
scale 2.0
|
||||
|
Loading…
Reference in New Issue
Block a user