1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
//! 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 STEPS: 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;
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)
}
pub fn supported_scales(resolution: Size<i32, Physical>) -> impl Iterator<Item = f64> {
(MIN_SCALE * STEPS..=MAX_SCALE * STEPS)
.map(|x| f64::from(x) / f64::from(STEPS))
.filter(move |scale| is_valid_for_resolution(resolution, *scale))
}
fn is_valid_for_resolution(resolution: Size<i32, Physical>, scale: f64) -> bool {
let logical = resolution.to_f64().to_logical(scale).to_i32_round::<i32>();
logical.w * logical.h >= MIN_LOGICAL_AREA
}
/// Adjusts the scale to the closest exactly-representable value.
pub fn closest_representable_scale(scale: f64) -> f64 {
// Current fractional-scale Wayland protocol can only represent N / 120 scales.
const FRACTIONAL_SCALE_DENOM: f64 = 120.;
(scale * FRACTIONAL_SCALE_DENOM).round() / FRACTIONAL_SCALE_DENOM
}
#[cfg(test)]
mod tests {
use insta::assert_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
assert_snapshot!(check((65, 129), (720, 1440)), @"1.5");
// OnePlus 6
assert_snapshot!(check((68, 144), (1080, 2280)), @"2.5");
// Google Pixel 6a
assert_snapshot!(check((64, 142), (1080, 2400)), @"2.5");
// 13" MacBook Retina
assert_snapshot!(check((286, 179), (2560, 1600)), @"1.75");
// Surface Laptop Studio
assert_snapshot!(check((303, 202), (2400, 1600)), @"1.5");
// Dell XPS 9320
assert_snapshot!(check((290, 180), (3840, 2400)), @"2.5");
// Lenovo ThinkPad X1 Yoga Gen 6
assert_snapshot!(check((300, 190), (3840, 2400)), @"2.5");
// Generic 23" 1080p
assert_snapshot!(check((509, 286), (1920, 1080)), @"1");
// Generic 23" 4K
assert_snapshot!(check((509, 286), (3840, 2160)), @"1.75");
// Generic 27" 4K
assert_snapshot!(check((598, 336), (3840, 2160)), @"1.5");
// Generic 32" 4K
assert_snapshot!(check((708, 398), (3840, 2160)), @"1.25");
// Generic 25" 4K; ideal scale is 1.60, should round to 1.5 and 1.0
assert_snapshot!(check((554, 312), (3840, 2160)), @"1.5");
// Generic 23.5" 4K; ideal scale is 1.70, should round to 1.75 and 2.0
assert_snapshot!(check((522, 294), (3840, 2160)), @"1.75");
// Lenovo Legion 7 Gen 7 AMD 16"
assert_snapshot!(check((340, 210), (2560, 1600)), @"1.5");
// Acer Nitro XV320QU LV 31.5"
assert_snapshot!(check((700, 390), (2560, 1440)), @"1");
// Surface Pro 6
assert_snapshot!(check((260, 170), (2736, 1824)), @"2");
}
#[test]
fn guess_monitor_scale_unknown_size() {
assert_eq!(check((0, 0), (1920, 1080)), 1.);
}
#[test]
fn test_round_scale() {
assert_snapshot!(closest_representable_scale(1.3), @"1.3");
assert_snapshot!(closest_representable_scale(1.31), @"1.3083333333333333");
assert_snapshot!(closest_representable_scale(1.32), @"1.3166666666666667");
assert_snapshot!(closest_representable_scale(1.33), @"1.3333333333333333");
assert_snapshot!(closest_representable_scale(1.34), @"1.3416666666666666");
assert_snapshot!(closest_representable_scale(1.35), @"1.35");
}
}
|