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 RAW_MONITORS_CHANGED_EVENT
89 .hook(|args| {
90 let mut available_monitors: IdMap<_, _> = args.available_monitors.iter().cloned().collect();
91 let event_ts = args.timestamp;
92 let event_propagation = args.propagation.clone();
93
94 MONITORS_SV.read().monitors.modify(move |m| {
95 let mut removed = vec![];
96 let mut changed = vec![];
97
98 m.retain(|key, value| {
99 if let Some(new) = available_monitors.remove(key) {
100 if value.update(new) {
101 tracing::trace!(
102 "monitor update, {:?} {:?}",
103 key,
104 (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
105 );
106 changed.push(*key);
107 }
108 true
109 } else {
110 tracing::trace!(
111 "monitor removed, {:?} {:?}",
112 key,
113 (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
114 );
115 removed.push(*key);
116 false
117 }
118 });
119
120 let mut added = Vec::with_capacity(available_monitors.len());
121
122 for (id, info) in available_monitors {
123 added.push(id);
124 tracing::trace!(
125 "monitor added, {:?} {:?}",
126 id,
127 (&info.name, info.position, info.size, info.scale_factor)
128 );
129 m.insert(id, MonitorInfo::from_gen(id, info));
130 }
131
132 if !removed.is_empty() || !added.is_empty() || !changed.is_empty() {
133 let args = MonitorsChangedArgs::new(event_ts, event_propagation, removed, added, changed);
134 MONITORS_CHANGED_EVENT.notify(args);
135 }
136 });
137 true
138 })
139 .perm();
140
141 Self {
142 monitors: var(IdMap::new()),
143 }
144 }
145}
146
147#[derive(Clone, Copy, PartialEq)]
151#[non_exhaustive]
152pub struct HeadlessMonitor {
153 pub scale_factor: Option<Factor>,
161
162 pub size: DipSize,
169
170 pub density: PxDensity,
172}
173impl fmt::Debug for HeadlessMonitor {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 if f.alternate() || self.density != PxDensity::default() {
176 f.debug_struct("HeadlessMonitor")
177 .field("scale_factor", &self.scale_factor)
178 .field("screen_size", &self.size)
179 .field("density", &self.density)
180 .finish()
181 } else {
182 write!(f, "({:?}, ({}, {}))", self.scale_factor, self.size.width, self.size.height)
183 }
184 }
185}
186impl HeadlessMonitor {
187 pub fn new(size: DipSize) -> Self {
189 HeadlessMonitor {
190 scale_factor: None,
191 size,
192 density: PxDensity::default(),
193 }
194 }
195
196 pub fn new_scaled(size: DipSize, scale_factor: Factor) -> Self {
198 HeadlessMonitor {
199 scale_factor: Some(scale_factor),
200 size,
201 density: PxDensity::default(),
202 }
203 }
204
205 pub fn new_scale(scale_factor: Factor) -> Self {
207 HeadlessMonitor {
208 scale_factor: Some(scale_factor),
209 ..Self::default()
210 }
211 }
212}
213impl Default for HeadlessMonitor {
214 fn default() -> Self {
216 (1920, 1080).into()
217 }
218}
219impl_from_and_into_var! {
220 fn from<W: Into<Dip>, H: Into<Dip>>((width, height): (W, H)) -> HeadlessMonitor {
221 HeadlessMonitor::new(DipSize::new(width.into(), height.into()))
222 }
223 fn from<W: Into<Dip>, H: Into<Dip>, F: Into<Factor>>((width, height, scale): (W, H, F)) -> HeadlessMonitor {
224 HeadlessMonitor::new_scaled(DipSize::new(width.into(), height.into()), scale.into())
225 }
226}
227
228#[derive(Clone)]
230pub struct MonitorInfo {
231 id: MonitorId,
232 is_primary: Var<bool>,
233 name: Var<Txt>,
234 position: Var<PxPoint>,
235 size: Var<PxSize>,
236 video_modes: Var<Vec<VideoMode>>,
237 scale_factor: Var<Factor>,
238 density: Var<PxDensity>,
239 refresh_rate: Var<Frequency>,
240}
241impl fmt::Debug for MonitorInfo {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 f.debug_struct("MonitorInfo")
244 .field("id", &self.id)
245 .field("name", &self.name.get())
246 .field("position", &self.position.get())
247 .field("size", &self.size.get())
248 .field("refresh_rate", &self.refresh_rate.get())
249 .finish_non_exhaustive()
250 }
251}
252impl PartialEq for MonitorInfo {
253 fn eq(&self, other: &Self) -> bool {
254 self.id == other.id && self.name.var_eq(&other.name)
255 }
256}
257impl MonitorInfo {
258 fn from_gen(id: MonitorId, info: zng_view_api::window::MonitorInfo) -> Self {
260 MonitorInfo {
261 id,
262 is_primary: var(info.is_primary),
263 name: var(info.name.to_txt()),
264 position: var(info.position),
265 size: var(info.size),
266 scale_factor: var(info.scale_factor),
267 video_modes: var(info.video_modes),
268 refresh_rate: var(info.refresh_rate),
269 density: var(PxDensity::default()),
270 }
271 }
272
273 fn update(&self, info: zng_view_api::window::MonitorInfo) -> bool {
276 fn check_set<T: VarValue + PartialEq>(var: &Var<T>, value: T) -> bool {
277 let ne = var.with(|v| v != &value);
278 var.set(value);
279 ne
280 }
281
282 check_set(&self.is_primary, info.is_primary)
283 | check_set(&self.name, info.name.to_txt())
284 | check_set(&self.position, info.position)
285 | check_set(&self.size, info.size)
286 | check_set(&self.scale_factor, info.scale_factor)
287 | check_set(&self.video_modes, info.video_modes)
288 | check_set(&self.refresh_rate, info.refresh_rate)
289 }
290
291 pub fn id(&self) -> MonitorId {
293 self.id
294 }
295
296 pub fn is_primary(&self) -> Var<bool> {
298 self.is_primary.read_only()
299 }
300
301 pub fn name(&self) -> Var<Txt> {
303 self.name.read_only()
304 }
305 pub fn position(&self) -> Var<PxPoint> {
307 self.position.read_only()
308 }
309 pub fn size(&self) -> Var<PxSize> {
311 self.size.read_only()
312 }
313
314 pub fn video_modes(&self) -> Var<Vec<VideoMode>> {
316 self.video_modes.read_only()
317 }
318
319 pub fn scale_factor(&self) -> Var<Factor> {
323 self.scale_factor.read_only()
324 }
325 pub fn density(&self) -> Var<PxDensity> {
327 self.density.clone()
328 }
329
330 pub fn refresh_rate(&self) -> Var<Frequency> {
332 self.refresh_rate.read_only()
333 }
334
335 pub fn px_rect(&self) -> PxRect {
337 let pos = self.position.get();
338 let size = self.size.get();
339
340 PxRect::new(pos, size)
341 }
342
343 pub fn dip_rect(&self) -> DipRect {
345 let pos = self.position.get();
346 let size = self.size.get();
347 let factor = self.scale_factor.get();
348
349 PxRect::new(pos, size).to_dip(factor)
350 }
351
352 pub fn fallback() -> Self {
356 let defaults = HeadlessMonitor::default();
357 let fct = 1.fct();
358
359 Self {
360 id: MonitorId::fallback(),
361 is_primary: var(false),
362 name: var("<fallback>".into()),
363 position: var(PxPoint::zero()),
364 size: var(defaults.size.to_px(fct)),
365 video_modes: var(vec![]),
366 scale_factor: var(fct),
367 density: var(PxDensity::default()),
368 refresh_rate: var(60.hertz()),
369 }
370 }
371}
372
373#[derive(Clone, Default)]
375pub enum MonitorQuery {
376 #[default]
383 ParentOrPrimary,
384
385 Primary,
387 Query(Arc<dyn Fn() -> Option<MonitorInfo> + Send + Sync>),
393}
394impl std::fmt::Debug for MonitorQuery {
395 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396 if f.alternate() {
397 write!(f, "MonitorQuery::")?;
398 }
399 match self {
400 Self::ParentOrPrimary => write!(f, "ParentOrPrimary"),
401 Self::Primary => write!(f, "Primary"),
402 Self::Query(_) => write!(f, "Query(_)"),
403 }
404 }
405}
406impl MonitorQuery {
407 pub fn new(query: impl Fn() -> Option<MonitorInfo> + Send + Sync + 'static) -> Self {
409 Self::Query(Arc::new(query))
410 }
411
412 pub fn select(&self, window_id: WindowId) -> Option<MonitorInfo> {
414 match self {
415 MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
416 MonitorQuery::Primary => Self::primary_query(),
417 MonitorQuery::Query(q) => q(),
418 }
419 }
420
421 pub fn select_fallback(&self, window_id: WindowId) -> MonitorInfo {
423 match self {
424 MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
425 MonitorQuery::Primary => Self::primary_query(),
426 MonitorQuery::Query(q) => q().or_else(Self::primary_query),
427 }
428 .unwrap_or_else(Self::fallback)
429 }
430
431 fn fallback() -> MonitorInfo {
432 MONITORS_SV.read().monitors.with(|m| {
433 let mut best = None;
434 let mut best_area = Px(0);
435 for m in m.values() {
436 let m_area = m.px_rect().area();
437 if m_area > best_area {
438 best = Some(m);
439 best_area = m_area;
440 }
441 }
442 best.cloned().unwrap_or_else(MonitorInfo::fallback)
443 })
444 }
445
446 fn parent_or_primary_query(win_id: WindowId) -> Option<MonitorInfo> {
447 if let Some(parent) = WINDOWS.vars(win_id).unwrap().parent().get()
448 && let Some(w) = WINDOWS.vars(parent)
449 {
450 return if let Some(monitor) = w.actual_monitor().get() {
451 MONITORS.monitor(monitor)
452 } else {
453 w.monitor().get().select(parent)
454 };
455 }
456 MONITORS.primary_monitor().get()
457 }
458
459 fn primary_query() -> Option<MonitorInfo> {
460 MONITORS.primary_monitor().get()
461 }
462}
463impl PartialEq for MonitorQuery {
464 fn eq(&self, other: &Self) -> bool {
465 match (self, other) {
466 (Self::Query(l0), Self::Query(r0)) => Arc::ptr_eq(l0, r0),
467 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
468 }
469 }
470}
471impl_from_and_into_var! {
472 fn from(id: MonitorId) -> MonitorQuery {
473 MonitorQuery::new(move || MONITORS.monitor(id))
474 }
475}
476
477event_args! {
478 pub struct MonitorsChangedArgs {
480 pub removed: Vec<MonitorId>,
482
483 pub added: Vec<MonitorId>,
487
488 pub modified: Vec<MonitorId>,
492
493 ..
494
495 fn is_in_target(&self, _id: WidgetId) -> bool {
497 true
498 }
499 }
500}
501
502event! {
503 pub static MONITORS_CHANGED_EVENT: MonitorsChangedArgs;
505}