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, RAW_SCALE_FACTOR_CHANGED_EVENT};
6use zng_app::window::{MonitorId, WindowId};
7use zng_app_context::app_local;
8use zng_layout::unit::{Dip, DipRect, DipSize, DipToPx, Factor, FactorUnits, Px, PxDensity, PxPoint, PxRect, PxSize, PxToDip};
9use zng_txt::{ToTxt, Txt};
10use zng_unique_id::IdMap;
11use zng_var::{Var, VarValue, impl_from_and_into_var, var};
12use zng_view_api::window::VideoMode;
13
14use crate::WINDOWS;
15
16app_local! {
17 static MONITORS_SV: MonitorsService = MonitorsService::new();
18}
19
20pub struct MONITORS;
52impl MONITORS {
53 pub fn monitor(&self, monitor_id: MonitorId) -> Option<MonitorInfo> {
57 MONITORS_SV.read().monitors.with(|m| m.get(&monitor_id).cloned())
58 }
59
60 pub fn available_monitors(&self) -> Var<Vec<MonitorInfo>> {
64 MONITORS_SV.read().monitors.map(|w| {
65 let mut list: Vec<_> = w.values().cloned().collect();
66 list.sort_by(|a, b| a.name.with(|a| b.name.with(|b| a.cmp(b))));
67 list
68 })
69 }
70
71 pub fn primary_monitor(&self) -> Var<Option<MonitorInfo>> {
73 MONITORS_SV
74 .read()
75 .monitors
76 .map(|w| w.values().find(|m| m.is_primary().get()).cloned())
77 }
78}
79
80struct MonitorsService {
81 monitors: Var<IdMap<MonitorId, MonitorInfo>>,
82}
83impl MonitorsService {
84 fn new() -> Self {
85 RAW_SCALE_FACTOR_CHANGED_EVENT
87 .hook(|args| {
88 MONITORS_SV.read().monitors.with(|a| {
89 if let Some(m) = a.get(&args.monitor_id) {
90 tracing::trace!("monitor scale factor changed, {:?} {:?}", args.monitor_id, args.scale_factor);
91 m.scale_factor.set(args.scale_factor);
92 }
93 });
94 true
95 })
96 .perm();
97
98 RAW_MONITORS_CHANGED_EVENT
100 .hook(|args| {
101 let mut available_monitors: IdMap<_, _> = args.available_monitors.iter().cloned().collect();
102 let event_ts = args.timestamp;
103 let event_propagation = args.propagation.clone();
104
105 MONITORS_SV.read().monitors.modify(move |m| {
106 let mut removed = vec![];
107 let mut changed = vec![];
108
109 m.retain(|key, value| {
110 if let Some(new) = available_monitors.remove(key) {
111 if value.update(new) {
112 tracing::trace!(
113 "monitor update, {:?} {:?}",
114 key,
115 (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
116 );
117 changed.push(*key);
118 }
119 true
120 } else {
121 tracing::trace!(
122 "monitor removed, {:?} {:?}",
123 key,
124 (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
125 );
126 removed.push(*key);
127 false
128 }
129 });
130
131 let mut added = Vec::with_capacity(available_monitors.len());
132
133 for (id, info) in available_monitors {
134 added.push(id);
135 tracing::trace!(
136 "monitor added, {:?} {:?}",
137 id,
138 (&info.name, info.position, info.size, info.scale_factor)
139 );
140 m.insert(id, MonitorInfo::from_gen(id, info));
141 }
142
143 if !removed.is_empty() || !added.is_empty() || !changed.is_empty() {
144 let args = MonitorsChangedArgs::new(event_ts, event_propagation, removed, added, changed);
145 MONITORS_CHANGED_EVENT.notify(args);
146 }
147 });
148 true
149 })
150 .perm();
151
152 Self {
153 monitors: var(IdMap::new()),
154 }
155 }
156}
157
158#[derive(Clone, Copy, PartialEq)]
162#[non_exhaustive]
163pub struct HeadlessMonitor {
164 pub scale_factor: Option<Factor>,
172
173 pub size: DipSize,
180
181 pub density: PxDensity,
183}
184impl fmt::Debug for HeadlessMonitor {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 if f.alternate() || self.density != PxDensity::default() {
187 f.debug_struct("HeadlessMonitor")
188 .field("scale_factor", &self.scale_factor)
189 .field("screen_size", &self.size)
190 .field("density", &self.density)
191 .finish()
192 } else {
193 write!(f, "({:?}, ({}, {}))", self.scale_factor, self.size.width, self.size.height)
194 }
195 }
196}
197impl HeadlessMonitor {
198 pub fn new(size: DipSize) -> Self {
200 HeadlessMonitor {
201 scale_factor: None,
202 size,
203 density: PxDensity::default(),
204 }
205 }
206
207 pub fn new_scaled(size: DipSize, scale_factor: Factor) -> Self {
209 HeadlessMonitor {
210 scale_factor: Some(scale_factor),
211 size,
212 density: PxDensity::default(),
213 }
214 }
215
216 pub fn new_scale(scale_factor: Factor) -> Self {
218 HeadlessMonitor {
219 scale_factor: Some(scale_factor),
220 ..Self::default()
221 }
222 }
223}
224impl Default for HeadlessMonitor {
225 fn default() -> Self {
227 (1920, 1080).into()
228 }
229}
230impl_from_and_into_var! {
231 fn from<W: Into<Dip>, H: Into<Dip>>((width, height): (W, H)) -> HeadlessMonitor {
232 HeadlessMonitor::new(DipSize::new(width.into(), height.into()))
233 }
234 fn from<W: Into<Dip>, H: Into<Dip>, F: Into<Factor>>((width, height, scale): (W, H, F)) -> HeadlessMonitor {
235 HeadlessMonitor::new_scaled(DipSize::new(width.into(), height.into()), scale.into())
236 }
237}
238
239#[derive(Clone)]
241pub struct MonitorInfo {
242 id: MonitorId,
243 is_primary: Var<bool>,
244 name: Var<Txt>,
245 position: Var<PxPoint>,
246 size: Var<PxSize>,
247 video_modes: Var<Vec<VideoMode>>,
248 scale_factor: Var<Factor>,
249 density: Var<PxDensity>,
250}
251impl fmt::Debug for MonitorInfo {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 f.debug_struct("MonitorInfo")
254 .field("id", &self.id)
255 .field("name", &self.name.get())
256 .field("position", &self.position.get())
257 .field("size", &self.size.get())
258 .finish_non_exhaustive()
259 }
260}
261impl PartialEq for MonitorInfo {
262 fn eq(&self, other: &Self) -> bool {
263 self.id == other.id && self.name.var_eq(&other.name)
264 }
265}
266impl MonitorInfo {
267 fn from_gen(id: MonitorId, info: zng_view_api::window::MonitorInfo) -> Self {
269 MonitorInfo {
270 id,
271 is_primary: var(info.is_primary),
272 name: var(info.name.to_txt()),
273 position: var(info.position),
274 size: var(info.size),
275 scale_factor: var(info.scale_factor),
276 video_modes: var(info.video_modes),
277 density: var(PxDensity::default()),
278 }
279 }
280
281 fn update(&self, info: zng_view_api::window::MonitorInfo) -> bool {
284 fn check_set<T: VarValue + PartialEq>(var: &Var<T>, value: T) -> bool {
285 let ne = var.with(|v| v != &value);
286 var.set(value);
287 ne
288 }
289
290 check_set(&self.is_primary, info.is_primary)
291 | check_set(&self.name, info.name.to_txt())
292 | check_set(&self.position, info.position)
293 | check_set(&self.size, info.size)
294 | check_set(&self.scale_factor, info.scale_factor)
295 | check_set(&self.video_modes, info.video_modes)
296 }
297
298 pub fn id(&self) -> MonitorId {
300 self.id
301 }
302
303 pub fn is_primary(&self) -> Var<bool> {
305 self.is_primary.read_only()
306 }
307
308 pub fn name(&self) -> Var<Txt> {
310 self.name.read_only()
311 }
312 pub fn position(&self) -> Var<PxPoint> {
314 self.position.read_only()
315 }
316 pub fn size(&self) -> Var<PxSize> {
318 self.size.read_only()
319 }
320
321 pub fn video_modes(&self) -> Var<Vec<VideoMode>> {
323 self.video_modes.read_only()
324 }
325
326 pub fn scale_factor(&self) -> Var<Factor> {
330 self.scale_factor.read_only()
331 }
332 pub fn density(&self) -> Var<PxDensity> {
334 self.density.clone()
335 }
336
337 pub fn px_rect(&self) -> PxRect {
339 let pos = self.position.get();
340 let size = self.size.get();
341
342 PxRect::new(pos, size)
343 }
344
345 pub fn dip_rect(&self) -> DipRect {
347 let pos = self.position.get();
348 let size = self.size.get();
349 let factor = self.scale_factor.get();
350
351 PxRect::new(pos, size).to_dip(factor)
352 }
353
354 pub fn fallback() -> Self {
358 let defaults = HeadlessMonitor::default();
359 let fct = 1.fct();
360
361 Self {
362 id: MonitorId::fallback(),
363 is_primary: var(false),
364 name: var("<fallback>".into()),
365 position: var(PxPoint::zero()),
366 size: var(defaults.size.to_px(fct)),
367 video_modes: var(vec![]),
368 scale_factor: var(fct),
369 density: var(PxDensity::default()),
370 }
371 }
372}
373
374#[derive(Clone, Default)]
376pub enum MonitorQuery {
377 #[default]
384 ParentOrPrimary,
385
386 Primary,
388 Query(Arc<dyn Fn() -> Option<MonitorInfo> + Send + Sync>),
394}
395impl std::fmt::Debug for MonitorQuery {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 if f.alternate() {
398 write!(f, "MonitorQuery::")?;
399 }
400 match self {
401 Self::ParentOrPrimary => write!(f, "ParentOrPrimary"),
402 Self::Primary => write!(f, "Primary"),
403 Self::Query(_) => write!(f, "Query(_)"),
404 }
405 }
406}
407impl MonitorQuery {
408 pub fn new(query: impl Fn() -> Option<MonitorInfo> + Send + Sync + 'static) -> Self {
410 Self::Query(Arc::new(query))
411 }
412
413 pub fn select(&self, window_id: WindowId) -> Option<MonitorInfo> {
415 match self {
416 MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
417 MonitorQuery::Primary => Self::primary_query(),
418 MonitorQuery::Query(q) => q(),
419 }
420 }
421
422 pub fn select_fallback(&self, window_id: WindowId) -> MonitorInfo {
424 match self {
425 MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
426 MonitorQuery::Primary => Self::primary_query(),
427 MonitorQuery::Query(q) => q().or_else(Self::primary_query),
428 }
429 .unwrap_or_else(Self::fallback)
430 }
431
432 fn fallback() -> MonitorInfo {
433 MONITORS_SV.read().monitors.with(|m| {
434 let mut best = None;
435 let mut best_area = Px(0);
436 for m in m.values() {
437 let m_area = m.px_rect().area();
438 if m_area > best_area {
439 best = Some(m);
440 best_area = m_area;
441 }
442 }
443 best.cloned().unwrap_or_else(MonitorInfo::fallback)
444 })
445 }
446
447 fn parent_or_primary_query(win_id: WindowId) -> Option<MonitorInfo> {
448 if let Some(parent) = WINDOWS.vars(win_id).unwrap().parent().get()
449 && let Some(w) = WINDOWS.vars(parent)
450 {
451 return if let Some(monitor) = w.actual_monitor().get() {
452 MONITORS.monitor(monitor)
453 } else {
454 w.monitor().get().select(parent)
455 };
456 }
457 MONITORS.primary_monitor().get()
458 }
459
460 fn primary_query() -> Option<MonitorInfo> {
461 MONITORS.primary_monitor().get()
462 }
463}
464impl PartialEq for MonitorQuery {
465 fn eq(&self, other: &Self) -> bool {
467 matches!((self, other), (Self::Primary, Self::Primary))
468 }
469}
470
471event_args! {
472 pub struct MonitorsChangedArgs {
474 pub removed: Vec<MonitorId>,
476
477 pub added: Vec<MonitorId>,
481
482 pub modified: Vec<MonitorId>,
486
487 ..
488
489 fn is_in_target(&self, _id: WidgetId) -> bool {
491 true
492 }
493 }
494}
495
496event! {
497 pub static MONITORS_CHANGED_EVENT: MonitorsChangedArgs;
499}