Evan's Blog
EvanZhu0721 / 公开仓库

SBMS

镜像已同步

SBMS complete the calculation rules for the default logical desktop topology relationship and the physical dimensions of the displays in the Windows multi-screen collaboration system.

上游 GitHub
READ-ONLY MIRROR

README、源码与 Release 已保存到本站;源码视图固定到 commit bd87fa1e。

main/ root / src/ui
bd87fa1e· 固定源码快照
src/ui/geometry_view.rs15.9 KB
1use super::*;
2
3pub(super) fn update_geometry_preview(ui: &QuickAccess, displays: &[DisplayOption]) {
4 update_resolution_suggestions(ui);
5 ui.set_geometry_error("".into());
6 ui.set_geometry_valid(false);
7 let request = match geometry_request(ui, displays) {
8 Ok(Some(request)) => request,
9 Ok(None) => {
10 ui.set_geometry_result("Enter display measurements".into());
11 ui.set_geometry_result_detail("Enter pixels, diagonal inches, and aspect ratio".into());
12 return;
13 }
14 Err(error) => {
15 ui.set_geometry_result("Check the values".into());
16 ui.set_geometry_result_detail("".into());
17 ui.set_geometry_error(error.into());
18 return;
19 }
20 };
21 match request.calculate() {
22 Ok(result) => set_geometry_result(ui, result, false),
23 Err(error) => {
24 ui.set_geometry_result("Check the values".into());
25 ui.set_geometry_result_detail("".into());
26 ui.set_geometry_error(error.to_string().into());
27 }
28 }
29}
30
31pub(super) const COMMON_WIDTHS: &[&str] = &[
32 "1280", "1920", "2560", "3440", "3840", "4096", "5120", "6016", "7680",
33];
34pub(super) const COMMON_HEIGHTS: &[&str] = &[
35 "720", "1080", "1440", "1600", "2160", "2880", "3384", "4320",
36];
37pub(super) const DEFAULT_STREAM_ASPECT_WIDTH: u64 = 16;
38pub(super) const DEFAULT_STREAM_ASPECT_HEIGHT: u64 = 10;
39
40pub(super) fn resolution_suggestion(
41 input: &str,
42 candidates: &[&'static str],
43) -> Option<&'static str> {
44 let input = input.trim();
45 if input.is_empty() || !input.bytes().all(|byte| byte.is_ascii_digit()) {
46 return None;
47 }
48
49 let mut matches = candidates
50 .iter()
51 .copied()
52 .filter(|candidate| candidate.len() > input.len() && candidate.starts_with(input));
53 let suggestion = matches.next()?;
54 matches.next().is_none().then_some(suggestion)
55}
56
57pub(super) fn set_resolution_suggestion(
58 input: &str,
59 candidates: &[&'static str],
60 set_suggestion: impl FnOnce(SharedString),
61 set_suffix: impl FnOnce(SharedString),
62) {
63 let suggestion = resolution_suggestion(input, candidates);
64 set_suggestion(suggestion.unwrap_or_default().into());
65 set_suffix(
66 suggestion
67 .map(|candidate| &candidate[input.trim().len()..])
68 .unwrap_or_default()
69 .into(),
70 );
71}
72
73pub(super) fn set_stream_dimension_suggestion(
74 input: &str,
75 other_dimension: &str,
76 candidates: &[&'static str],
77 aspect_numerator: u64,
78 aspect_denominator: u64,
79 set_suggestion: impl FnOnce(SharedString),
80 set_suffix: impl FnOnce(SharedString),
81) {
82 let input = input.trim();
83 let suggestion = if input.is_empty() {
84 scaled_dimension_suggestion(other_dimension, aspect_numerator, aspect_denominator)
85 } else {
86 resolution_suggestion(input, candidates).map(str::to_owned)
87 };
88 let suffix = suggestion
89 .as_deref()
90 .and_then(|candidate| candidate.strip_prefix(input))
91 .unwrap_or_default();
92 set_suggestion(suggestion.clone().unwrap_or_default().into());
93 set_suffix(suffix.into());
94}
95
96pub(super) fn scaled_dimension_suggestion(
97 source: &str,
98 numerator: u64,
99 denominator: u64,
100) -> Option<String> {
101 let source = source.trim().parse::<u64>().ok()?;
102 if source == 0 || denominator == 0 {
103 return None;
104 }
105 let scaled = source
106 .checked_mul(numerator)?
107 .checked_add(denominator / 2)?
108 / denominator;
109 (scaled > 0 && scaled <= u64::from(MAX_VIRTUAL_DIMENSION)).then(|| scaled.to_string())
110}
111
112pub(super) fn stream_aspect_ratio(width: &str, height: &str) -> Option<String> {
113 let width = width.trim().parse::<u32>().ok()?;
114 let height = height.trim().parse::<u32>().ok()?;
115 if width == 0 || height == 0 {
116 return None;
117 }
118 let divisor = greatest_common_divisor(width, height);
119 let reduced_width = width / divisor;
120 let reduced_height = height / divisor;
121 if (reduced_width, reduced_height) == (8, 5) {
122 Some("16:10".into())
123 } else {
124 Some(format!("{reduced_width}:{reduced_height}"))
125 }
126}
127
128pub(super) fn greatest_common_divisor(mut left: u32, mut right: u32) -> u32 {
129 while right != 0 {
130 (left, right) = (right, left % right);
131 }
132 left
133}
134
135pub(super) fn update_resolution_suggestions(ui: &QuickAccess) {
136 set_resolution_suggestion(
137 ui.get_reference_width().as_str(),
138 COMMON_WIDTHS,
139 |value| ui.set_reference_width_suggestion(value),
140 |value| ui.set_reference_width_suffix(value),
141 );
142 set_resolution_suggestion(
143 ui.get_reference_height().as_str(),
144 COMMON_HEIGHTS,
145 |value| ui.set_reference_height_suggestion(value),
146 |value| ui.set_reference_height_suffix(value),
147 );
148}
149
150pub(super) fn calculate_geometry(ui: &QuickAccess, displays: &[DisplayOption]) {
151 update_geometry_preview(ui, displays);
152 if !ui.get_geometry_valid() {
153 if ui.get_page() != QuickAccessPage::Geometry && !ui.get_geometry_error().is_empty() {
154 surface_geometry_error(ui, ui.get_geometry_error().as_str());
155 }
156 return;
157 }
158 if ui.get_stream_only() {
159 validate_stream_fields(ui, displays);
160 return;
161 }
162 let request = match geometry_request(ui, displays) {
163 Ok(Some(request)) => request,
164 Ok(_) => return,
165 Err(error) => {
166 surface_geometry_error(ui, &error);
167 return;
168 }
169 };
170 let result = request
171 .calculate()
172 .expect("validated geometry request became invalid");
173 set_geometry_result(ui, result, true);
174}
175
176pub(super) fn selected_option<'a>(
177 ui: &QuickAccess,
178 displays: &'a [DisplayOption],
179 index: i32,
180) -> Option<&'a DisplayOption> {
181 let id = selected_display_id(ui, index)?;
182 (id != STREAM_ONLY_ID)
183 .then(|| displays.iter().find(|display| display.id == id))
184 .flatten()
185}
186
187pub(super) fn update_reference_action(ui: &QuickAccess, displays: &[DisplayOption]) {
188 let reference_source = selected_reference_source(ui);
189 let reference_manual = matches!(reference_source, Some(ReferenceSource::Manual));
190 ui.set_reference_manual(reference_manual);
191 let target_ready = if ui.get_stream_only() {
192 aspect_ratio_from_index(ui.get_stream_aspect_ratio_index())
193 .and_then(|aspect_ratio| {
194 let rotation = rotation_from_index(ui.get_stream_rotation())?;
195 stream_target_geometry(
196 ui.get_stream_width().as_str(),
197 ui.get_stream_height().as_str(),
198 ui.get_stream_diagonal().as_str(),
199 aspect_ratio,
200 rotation,
201 )
202 })
203 .is_ok()
204 } else {
205 automatic_target_geometry(ui, displays, "Target").is_ok()
206 };
207 let enabled = if reference_manual {
208 target_ready
209 } else {
210 target_ready
211 && selected_reference_display_index(ui, displays)
212 .and_then(|index| automatic_display_geometry(displays, index, "Reference").ok())
213 .is_some()
214 };
215 ui.set_reference_action_enabled(enabled);
216}
217
218pub(super) fn surface_geometry_error(ui: &QuickAccess, error: &str) {
219 ui.set_geometry_error(error.into());
220 if ui.get_page() != QuickAccessPage::Geometry {
221 ui.set_error_text(error.into());
222 ui.set_state_detail("Geometry calculation needs attention".into());
223 }
224}
225
226pub(super) fn geometry_request(
227 ui: &QuickAccess,
228 displays: &[DisplayOption],
229) -> Result<Option<SizingRequest>, String> {
230 let stream = ui
231 .get_stream_only()
232 .then(|| {
233 ValidatedStreamFields::parse(
234 ui.get_stream_width().as_str(),
235 ui.get_stream_height().as_str(),
236 ui.get_stream_diagonal().as_str(),
237 ui.get_stream_refresh().as_str(),
238 ui.get_stream_aspect_ratio_index(),
239 ui.get_stream_rotation(),
240 )
241 })
242 .transpose()?;
243 let target = if let Some(stream) = stream {
244 stream.target_geometry()?
245 } else {
246 automatic_target_geometry(ui, displays, "Target")?
247 };
248 let reference = if ui.get_reference_manual() {
249 let values = [
250 ui.get_reference_width(),
251 ui.get_reference_height(),
252 ui.get_reference_diagonal(),
253 ];
254 if values.iter().any(|value| value.trim().is_empty()) {
255 return Ok(None);
256 }
257 DisplayGeometry {
258 native_pixels: PixelSize {
259 width: parse_u32(&values[0], "Reference width")?,
260 height: parse_u32(&values[1], "Reference height")?,
261 },
262 physical: PhysicalMeasurement::DiagonalMm(
263 parse_f64(&values[2], "Reference diagonal")? * MILLIMETERS_PER_INCH,
264 ),
265 aspect_ratio: Some(aspect_ratio_from_index(ui.get_reference_aspect_ratio())?),
266 rotation: rotation_from_index(ui.get_reference_rotation())?,
267 }
268 } else {
269 let reference_index = selected_reference_display_index(ui, displays)
270 .ok_or_else(|| "Reference display is not selected".to_string())?;
271 automatic_display_geometry(displays, reference_index, "Reference")?
272 };
273 Ok(Some(SizingRequest {
274 reference,
275 target,
276 strategy: sizing_strategy_from_index(ui.get_sizing_strategy())?,
277 alignment: 2,
278 preferred_refresh_millihz: Some(
279 stream
280 .map(|fields| fields.screen.refresh_millihz)
281 .unwrap_or(240_000),
282 ),
283 }))
284}
285
286pub(super) fn stream_target_geometry(
287 width: &str,
288 height: &str,
289 diagonal: &str,
290 aspect_ratio: AspectRatio,
291 rotation: Rotation,
292) -> Result<DisplayGeometry, String> {
293 ValidatedStreamFields::parse(
294 width,
295 height,
296 diagonal,
297 "60",
298 aspect_ratio_option_index(aspect_ratio),
299 rotation_index(rotation),
300 )?
301 .target_geometry()
302}
303
304pub(super) fn automatic_display_geometry(
305 displays: &[DisplayOption],
306 index: i32,
307 role: &str,
308) -> Result<DisplayGeometry, String> {
309 let display = (index >= 0)
310 .then(|| displays.get(index as usize))
311 .flatten()
312 .ok_or_else(|| format!("{role} display is not selected"))?;
313 let (Some(width_mm), Some(height_mm)) = (display.physical_width_mm, display.physical_height_mm)
314 else {
315 return Err(format!(
316 "{role} display physical size is unavailable; choose Manual or reconnect it directly"
317 ));
318 };
319 Ok(DisplayGeometry {
320 native_pixels: PixelSize {
321 width: display.native_width,
322 height: display.native_height,
323 },
324 physical: PhysicalMeasurement::DimensionsMm {
325 width: width_mm,
326 height: height_mm,
327 },
328 aspect_ratio: None,
329 rotation: display.rotation,
330 })
331}
332
333pub(super) fn automatic_target_geometry(
334 ui: &QuickAccess,
335 displays: &[DisplayOption],
336 role: &str,
337) -> Result<DisplayGeometry, String> {
338 let display = selected_option(ui, displays, ui.get_selected_display())
339 .ok_or_else(|| format!("{role} display is not selected"))?;
340 display_geometry(display, role)
341}
342
343pub(super) fn display_geometry(
344 display: &DisplayOption,
345 role: &str,
346) -> Result<DisplayGeometry, String> {
347 let (Some(width_mm), Some(height_mm)) = (display.physical_width_mm, display.physical_height_mm)
348 else {
349 return Err(format!(
350 "{role} display physical size is unavailable; choose Manual or reconnect it directly"
351 ));
352 };
353 Ok(DisplayGeometry {
354 native_pixels: PixelSize {
355 width: display.native_width,
356 height: display.native_height,
357 },
358 physical: PhysicalMeasurement::DimensionsMm {
359 width: width_mm,
360 height: height_mm,
361 },
362 aspect_ratio: None,
363 rotation: display.rotation,
364 })
365}
366
367pub(super) fn selected_reference_display_index(
368 ui: &QuickAccess,
369 displays: &[DisplayOption],
370) -> Option<i32> {
371 let Some(ReferenceSource::Display(id)) = selected_reference_source(ui) else {
372 return None;
373 };
374 displays
375 .iter()
376 .position(|display| display.id == id)
377 .map(|index| index as i32)
378}
379
380pub(super) fn parse_f64(value: &str, name: &str) -> Result<f64, String> {
381 let inches = value
382 .trim()
383 .parse()
384 .map_err(|_| format!("{name} must be a number in inches"))?;
385 if !valid_physical_millimeters(inches * MILLIMETERS_PER_INCH) {
386 return Err(format!("{name} must be between 0.4 and 393.7 inches"));
387 }
388 Ok(inches)
389}
390
391pub(super) fn sizing_strategy_from_index(index: i32) -> Result<SizingStrategy, String> {
392 match index {
393 0 => Ok(SizingStrategy::MatchPhysicalSize),
394 1 => Ok(SizingStrategy::RoundedScale),
395 2 => Ok(SizingStrategy::IntegerScale),
396 _ => Err("Choose a supported sizing strategy".into()),
397 }
398}
399
400pub(super) fn sizing_strategy_index(strategy: SizingStrategy) -> i32 {
401 match strategy {
402 SizingStrategy::MatchPhysicalSize => 0,
403 SizingStrategy::RoundedScale => 1,
404 SizingStrategy::IntegerScale => 2,
405 }
406}
407
408pub(super) fn populate_geometry_fields(ui: &QuickAccess, request: SizingRequest) {
409 ui.set_reference_width(request.reference.native_pixels.width.to_string().into());
410 ui.set_reference_height(request.reference.native_pixels.height.to_string().into());
411 ui.set_reference_diagonal(
412 format!(
413 "{:.1}",
414 diagonal_mm(request.reference) / MILLIMETERS_PER_INCH
415 )
416 .into(),
417 );
418 ui.set_reference_aspect_ratio(aspect_ratio_index(request.reference));
419 ui.set_reference_rotation(rotation_index(request.reference.rotation));
420 ui.set_sizing_strategy(sizing_strategy_index(request.strategy));
421}
422
423pub(super) fn diagonal_mm(display: DisplayGeometry) -> f64 {
424 match display.physical {
425 PhysicalMeasurement::DiagonalMm(value) => value,
426 PhysicalMeasurement::DimensionsMm { width, height } => width.hypot(height),
427 }
428}
429
430pub(super) fn aspect_ratio_index(display: DisplayGeometry) -> i32 {
431 let ratio = display.aspect_ratio.unwrap_or(AspectRatio {
432 width: display.native_pixels.width,
433 height: display.native_pixels.height,
434 });
435 aspect_ratio_option_index(ratio)
436}
437
438pub(super) fn screen_override_aspect_ratio(index: i32) -> Result<Option<AspectRatio>, String> {
439 if index == 0 {
440 Ok(None)
441 } else {
442 aspect_ratio_from_index(index - 1).map(Some)
443 }
444}
445
446pub(super) fn set_geometry_result(ui: &QuickAccess, result: SizingResult, configured: bool) {
447 let mode = format!(
448 "{} × {}",
449 result.virtual_mode.width, result.virtual_mode.height
450 );
451 ui.set_geometry_result(mode.as_str().into());
452 ui.set_geometry_result_detail(
453 format!("Scale {:.2}× × {:.2}×", result.scale_x, result.scale_y).into(),
454 );
455 ui.set_geometry_valid(!ui.get_geometry_save_blocked());
456 ui.set_geometry_error("".into());
457 if configured {
458 let refresh = result
459 .preferred_refresh_millihz
460 .map(format_refresh_millihz)
461 .unwrap_or_else(|| "Driver refresh".into());
462 ui.set_geometry_configured(true);
463 ui.set_geometry_summary(mode.as_str().into());
464 ui.set_geometry_summary_detail(format!("Planned · {refresh}").into());
465 ui.set_state_detail("Ready to start".into());
466 }
467}
468
469pub(super) fn format_refresh_millihz(refresh: u32) -> String {
470 if refresh.is_multiple_of(1_000) {
471 format!("{} Hz", refresh / 1_000)
472 } else {
473 format!("{:.2} Hz", f64::from(refresh) / 1_000.0)
474 }
475}
476
477pub(super) fn set_geometry_unconfigured(ui: &QuickAccess) {
478 ui.set_geometry_valid(false);
479 ui.set_geometry_configured(false);
480 ui.set_geometry_summary("3840 × 2160".into());
481 ui.set_geometry_summary_detail("Driver · 240 Hz".into());
482 ui.set_geometry_result("Enter display measurements".into());
483 ui.set_geometry_result_detail("Enter pixels, diagonal inches, and aspect ratio".into());
484}
485