1use core::fmt;
2use std::sync::Arc;
3
4use zng_app::event::{event, event_args};
5use zng_app::view_process::raw_events::RAW_MONITORS_CHANGED_EVENT;
6use zng_app::window::{MonitorId, WindowId};
7use zng_app_context::app_local;
8use zng_layout::unit::{
9 Dip, DipRect, DipSize, DipToPx, Factor, FactorUnits, Frequency, FrequencyUnits as _, Px, PxDensity, PxPoint, PxRect, PxSize, PxToDip,
10};
11use zng_txt::{ToTxt, Txt};
12use zng_unique_id::IdMap;
13use zng_var::{Var, VarValue, impl_from_and_into_var, var};
14use zng_view_api::window::VideoMode;
15
16use crate::WINDOWS;
17
18app_local! {
19 static MONITORS_SV: MonitorsService = MonitorsService::new();
20}
21
22pub struct MONITORS;
54impl MONITORS {
55 pub fn monitor(&self, monitor_id: MonitorId) -> Option<MonitorInfo> {
59 MONITORS_SV.read().monitors.with(|m| m.get(&monitor_id).cloned())
60 }
61
62 pub fn available_monitors(&self) -> Var<Vec<MonitorInfo>> {
66 MONITORS_SV.read().monitors.map(|w| {
67 let mut list: Vec<_> = w.values().cloned().collect();
68 list.sort_by(|a, b| a.name.with(|a| b.name.with(|b| a.cmp(b))));
69 list
70 })
71 }
72
73 pub fn primary_monitor(&self) -> Var<Option<MonitorInfo>> {
75 MONITORS_SV
76 .read()
77 .monitors
78 .map(|w| w.values().find(|m| m.is_primary().get()).cloned())
79 }
80}
81
82struct MonitorsService {
83 monitors: Var<IdMap<MonitorId, MonitorInfo>>,
84}
85impl MonitorsService {
86 fn new() -> Self {
87 #[allow(deprecated)]
89 zng_app::view_process::raw_events::RAW_SCALE_FACTOR_CHANGED_EVENT
90 .hook(|args| {
91 MONITORS_SV.read().monitors.with(|a| {
92 if let Some(m) = a.get(&args.monitor_id) {
93 tracing::trace!("monitor scale factor changed, {:?} {:?}", args.monitor_id, args.scale_factor);
94 m.scale_factor.set(args.scale_factor);
95 }
96 });
97 true
98 })
99 .perm();
100
101 RAW_MONITORS_CHANGED_EVENT
103 .hook(|args| {
104 let mut available_monitors: IdMap<_, _> = args.available_monitors.iter().cloned().collect();
105 let event_ts = args.timestamp;
106 let event_propagation = args.propagation.clone();
107
108 MONITORS_SV.read().monitors.modify(move |m| {
109 let mut removed = vec![];
110 let mut changed = vec![];
111
112 m.retain(|key, value| {
113 if let Some(new) = available_monitors.remove(key) {
114 if value.update(new) {
115 tracing::trace!(
116 "monitor update, {:?} {:?}",
117 key,
118 (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
119 );
120 changed.push(*key);
121 }
122 true
123 } else {
124 tracing::trace!(
125 "monitor removed, {:?} {:?}",
126 key,
127 (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
128 );
129 removed.push(*key);
130 false
131 }
132 });
133
134 let mut added = Vec::with_capacity(available_monitors.len());
135
136 for (id, info) in available_monitors {
137 added.push(id);
138 tracing::trace!(
139 "monitor added, {:?} {:?}",
140 id,
141 (&info.name, info.position, info.size, info.scale_factor)
142 );
143 m.insert(id, MonitorInfo::from_gen(id, info));
144 }
145
146 if !removed.is_empty() || !added.is_empty() || !changed.is_empty() {
147 let args = MonitorsChangedArgs::new(event_ts, event_propagation, removed, added, changed);
148 MONITORS_CHANGED_EVENT.notify(args);
149 }
150 });
151 true
152 })
153 .perm();
154
155 Self {
156 monitors: var(IdMap::new()),
157 }
158 }
159}
160
161#[derive(Clone, Copy, PartialEq)]
165#[non_exhaustive]
166pub struct HeadlessMonitor {
167 pub scale_factor: Option<Factor>,
175
176 pub size: DipSize,
183
184 pub density: PxDensity,
186}
187impl fmt::Debug for HeadlessMonitor {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 if f.alternate() || self.density != PxDensity::default() {
190 f.debug_struct("HeadlessMonitor")
191 .field("scale_factor", &self.scale_factor)
192 .field("screen_size", &self.size)
193 .field("density", &self.density)
194 .finish()
195 } else {
196 write!(f, "({:?}, ({}, {}))", self.scale_factor, self.size.width, self.size.height)
197 }
198 }
199}
200impl HeadlessMonitor {
201 pub fn new(size: DipSize) -> Self {
203 HeadlessMonitor {
204 scale_factor: None,
205 size,
206 density: PxDensity::default(),
207 }
208 }
209
210 pub fn new_scaled(size: DipSize, scale_factor: Factor) -> Self {
212 HeadlessMonitor {
213 scale_factor: Some(scale_factor),
214 size,
215 density: PxDensity::default(),
216 }
217 }
218
219 pub fn new_scale(scale_factor: Factor) -> Self {
221 HeadlessMonitor {
222 scale_factor: Some(scale_factor),
223 ..Self::default()
224 }
225 }
226}
227impl Default for HeadlessMonitor {
228 fn default() -> Self {
230 (1920, 1080).into()
231 }
232}
233impl_from_and_into_var! {
234 fn from<W: Into<Dip>, H: Into<Dip>>((width, height): (W, H)) -> HeadlessMonitor {
235 HeadlessMonitor::new(DipSize::new(width.into(), height.into()))
236 }
237 fn from<W: Into<Dip>, H: Into<Dip>, F: Into<Factor>>((width, height, scale): (W, H, F)) -> HeadlessMonitor {
238 HeadlessMonitor::new_scaled(DipSize::new(width.into(), height.into()), scale.into())
239 }
240}
241
242#[derive(Clone)]
244pub struct MonitorInfo {
245 id: MonitorId,
246 is_primary: Var<bool>,
247 name: Var<Txt>,
248 position: Var<PxPoint>,
249 size: Var<PxSize>,
250 video_modes: Var<Vec<VideoMode>>,
251 scale_factor: Var<Factor>,
252 density: Var<PxDensity>,
253 refresh_rate: Var<Frequency>,
254}
255impl fmt::Debug for MonitorInfo {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 f.debug_struct("MonitorInfo")
258 .field("id", &self.id)
259 .field("name", &self.name.get())
260 .field("position", &self.position.get())
261 .field("size", &self.size.get())
262 .field("refresh_rate", &self.refresh_rate.get())
263 .finish_non_exhaustive()
264 }
265}
266impl PartialEq for MonitorInfo {
267 fn eq(&self, other: &Self) -> bool {
268 self.id == other.id && self.name.var_eq(&other.name)
269 }
270}
271impl MonitorInfo {
272 fn from_gen(id: MonitorId, info: zng_view_api::window::MonitorInfo) -> Self {
274 MonitorInfo {
275 id,
276 is_primary: var(info.is_primary),
277 name: var(info.name.to_txt()),
278 position: var(info.position),
279 size: var(info.size),
280 scale_factor: var(info.scale_factor),
281 video_modes: var(info.video_modes),
282 refresh_rate: var(info.refresh_rate),
283 density: var(PxDensity::default()),
284 }
285 }
286
287 fn update(&self, info: zng_view_api::window::MonitorInfo) -> bool {
290 fn check_set<T: VarValue + PartialEq>(var: &Var<T>, value: T) -> bool {
291 let ne = var.with(|v| v != &value);
292 var.set(value);
293 ne
294 }
295
296 check_set(&self.is_primary, info.is_primary)
297 | check_set(&self.name, info.name.to_txt())
298 | check_set(&self.position, info.position)
299 | check_set(&self.size, info.size)
300 | check_set(&self.scale_factor, info.scale_factor)
301 | check_set(&self.video_modes, info.video_modes)
302 | check_set(&self.refresh_rate, info.refresh_rate)
303 }
304
305 pub fn id(&self) -> MonitorId {
307 self.id
308 }
309
310 pub fn is_primary(&self) -> Var<bool> {
312 self.is_primary.read_only()
313 }
314
315 pub fn name(&self) -> Var<Txt> {
317 self.name.read_only()
318 }
319 pub fn position(&self) -> Var<PxPoint> {
321 self.position.read_only()
322 }
323 pub fn size(&self) -> Var<PxSize> {
325 self.size.read_only()
326 }
327
328 pub fn video_modes(&self) -> Var<Vec<VideoMode>> {
330 self.video_modes.read_only()
331 }
332
333 pub fn scale_factor(&self) -> Var<Factor> {
337 self.scale_factor.read_only()
338 }
339 pub fn density(&self) -> Var<PxDensity> {
341 self.density.clone()
342 }
343
344 pub fn refresh_rate(&self) -> Var<Frequency> {
346 self.refresh_rate.read_only()
347 }
348
349 pub fn px_rect(&self) -> PxRect {
351 let pos = self.position.get();
352 let size = self.size.get();
353
354 PxRect::new(pos, size)
355 }
356
357 pub fn dip_rect(&self) -> DipRect {
359 let pos = self.position.get();
360 let size = self.size.get();
361 let factor = self.scale_factor.get();
362
363 PxRect::new(pos, size).to_dip(factor)
364 }
365
366 pub fn fallback() -> Self {
370 let defaults = HeadlessMonitor::default();
371 let fct = 1.fct();
372
373 Self {
374 id: MonitorId::fallback(),
375 is_primary: var(false),
376 name: var("<fallback>".into()),
377 position: var(PxPoint::zero()),
378 size: var(defaults.size.to_px(fct)),
379 video_modes: var(vec![]),
380 scale_factor: var(fct),
381 density: var(PxDensity::default()),
382 refresh_rate: var(60.hertz()),
383 }
384 }
385}
386
387#[derive(Clone, Default)]
389pub enum MonitorQuery {
390 #[default]
397 ParentOrPrimary,
398
399 Primary,
401 Query(Arc<dyn Fn() -> Option<MonitorInfo> + Send + Sync>),
407}
408impl std::fmt::Debug for MonitorQuery {
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410 if f.alternate() {
411 write!(f, "MonitorQuery::")?;
412 }
413 match self {
414 Self::ParentOrPrimary => write!(f, "ParentOrPrimary"),
415 Self::Primary => write!(f, "Primary"),
416 Self::Query(_) => write!(f, "Query(_)"),
417 }
418 }
419}
420impl MonitorQuery {
421 pub fn new(query: impl Fn() -> Option<MonitorInfo> + Send + Sync + 'static) -> Self {
423 Self::Query(Arc::new(query))
424 }
425
426 pub fn select(&self, window_id: WindowId) -> Option<MonitorInfo> {
428 match self {
429 MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
430 MonitorQuery::Primary => Self::primary_query(),
431 MonitorQuery::Query(q) => q(),
432 }
433 }
434
435 pub fn select_fallback(&self, window_id: WindowId) -> MonitorInfo {
437 match self {
438 MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
439 MonitorQuery::Primary => Self::primary_query(),
440 MonitorQuery::Query(q) => q().or_else(Self::primary_query),
441 }
442 .unwrap_or_else(Self::fallback)
443 }
444
445 fn fallback() -> MonitorInfo {
446 MONITORS_SV.read().monitors.with(|m| {
447 let mut best = None;
448 let mut best_area = Px(0);
449 for m in m.values() {
450 let m_area = m.px_rect().area();
451 if m_area > best_area {
452 best = Some(m);
453 best_area = m_area;
454 }
455 }
456 best.cloned().unwrap_or_else(MonitorInfo::fallback)
457 })
458 }
459
460 fn parent_or_primary_query(win_id: WindowId) -> Option<MonitorInfo> {
461 if let Some(parent) = WINDOWS.vars(win_id).unwrap().parent().get()
462 && let Some(w) = WINDOWS.vars(parent)
463 {
464 return if let Some(monitor) = w.actual_monitor().get() {
465 MONITORS.monitor(monitor)
466 } else {
467 w.monitor().get().select(parent)
468 };
469 }
470 MONITORS.primary_monitor().get()
471 }
472
473 fn primary_query() -> Option<MonitorInfo> {
474 MONITORS.primary_monitor().get()
475 }
476}
477impl PartialEq for MonitorQuery {
478 fn eq(&self, other: &Self) -> bool {
480 matches!((self, other), (Self::Primary, Self::Primary))
481 }
482}
483impl_from_and_into_var! {
484 fn from(id: MonitorId) -> MonitorQuery {
485 MonitorQuery::new(move || MONITORS.monitor(id))
486 }
487}
488
489event_args! {
490 pub struct MonitorsChangedArgs {
492 pub removed: Vec<MonitorId>,
494
495 pub added: Vec<MonitorId>,
499
500 pub modified: Vec<MonitorId>,
504
505 ..
506
507 fn is_in_target(&self, _id: WidgetId) -> bool {
509 true
510 }
511 }
512}
513
514event! {
515 pub static MONITORS_CHANGED_EVENT: MonitorsChangedArgs;
517}