use core::fmt;
use std::sync::Arc;
use zng_app::event::{event, event_args};
use zng_app::view_process::raw_events::RAW_MONITORS_CHANGED_EVENT;
use zng_app::window::{MonitorId, WindowId};
use zng_app_context::app_local;
use zng_layout::unit::{
Dip, DipRect, DipSize, DipToPx, Factor, FactorUnits, Frequency, FrequencyUnits as _, Px, PxDensity, PxPoint, PxRect, PxSize, PxToDip,
};
use zng_txt::{ToTxt, Txt};
use zng_unique_id::IdMap;
use zng_var::{Var, VarValue, impl_from_and_into_var, var};
use zng_view_api::window::VideoMode;
use crate::WINDOWS;
app_local! {
static MONITORS_SV: MonitorsService = MonitorsService::new();
}
pub struct MONITORS;
impl MONITORS {
pub fn monitor(&self, monitor_id: MonitorId) -> Option<MonitorInfo> {
MONITORS_SV.read().monitors.with(|m| m.get(&monitor_id).cloned())
}
pub fn available_monitors(&self) -> Var<Vec<MonitorInfo>> {
MONITORS_SV.read().monitors.map(|w| {
let mut list: Vec<_> = w.values().cloned().collect();
list.sort_by(|a, b| a.name.with(|a| b.name.with(|b| a.cmp(b))));
list
})
}
pub fn primary_monitor(&self) -> Var<Option<MonitorInfo>> {
MONITORS_SV
.read()
.monitors
.map(|w| w.values().find(|m| m.is_primary().get()).cloned())
}
}
struct MonitorsService {
monitors: Var<IdMap<MonitorId, MonitorInfo>>,
}
impl MonitorsService {
fn new() -> Self {
#[allow(deprecated)]
zng_app::view_process::raw_events::RAW_SCALE_FACTOR_CHANGED_EVENT
.hook(|args| {
MONITORS_SV.read().monitors.with(|a| {
if let Some(m) = a.get(&args.monitor_id) {
tracing::trace!("monitor scale factor changed, {:?} {:?}", args.monitor_id, args.scale_factor);
m.scale_factor.set(args.scale_factor);
}
});
true
})
.perm();
RAW_MONITORS_CHANGED_EVENT
.hook(|args| {
let mut available_monitors: IdMap<_, _> = args.available_monitors.iter().cloned().collect();
let event_ts = args.timestamp;
let event_propagation = args.propagation.clone();
MONITORS_SV.read().monitors.modify(move |m| {
let mut removed = vec![];
let mut changed = vec![];
m.retain(|key, value| {
if let Some(new) = available_monitors.remove(key) {
if value.update(new) {
tracing::trace!(
"monitor update, {:?} {:?}",
key,
(&value.name.get(), value.position.get(), value.size.get(), value.density.get())
);
changed.push(*key);
}
true
} else {
tracing::trace!(
"monitor removed, {:?} {:?}",
key,
(&value.name.get(), value.position.get(), value.size.get(), value.density.get())
);
removed.push(*key);
false
}
});
let mut added = Vec::with_capacity(available_monitors.len());
for (id, info) in available_monitors {
added.push(id);
tracing::trace!(
"monitor added, {:?} {:?}",
id,
(&info.name, info.position, info.size, info.scale_factor)
);
m.insert(id, MonitorInfo::from_gen(id, info));
}
if !removed.is_empty() || !added.is_empty() || !changed.is_empty() {
let args = MonitorsChangedArgs::new(event_ts, event_propagation, removed, added, changed);
MONITORS_CHANGED_EVENT.notify(args);
}
});
true
})
.perm();
Self {
monitors: var(IdMap::new()),
}
}
}
#[derive(Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct HeadlessMonitor {
pub scale_factor: Option<Factor>,
pub size: DipSize,
pub density: PxDensity,
}
impl fmt::Debug for HeadlessMonitor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() || self.density != PxDensity::default() {
f.debug_struct("HeadlessMonitor")
.field("scale_factor", &self.scale_factor)
.field("screen_size", &self.size)
.field("density", &self.density)
.finish()
} else {
write!(f, "({:?}, ({}, {}))", self.scale_factor, self.size.width, self.size.height)
}
}
}
impl HeadlessMonitor {
pub fn new(size: DipSize) -> Self {
HeadlessMonitor {
scale_factor: None,
size,
density: PxDensity::default(),
}
}
pub fn new_scaled(size: DipSize, scale_factor: Factor) -> Self {
HeadlessMonitor {
scale_factor: Some(scale_factor),
size,
density: PxDensity::default(),
}
}
pub fn new_scale(scale_factor: Factor) -> Self {
HeadlessMonitor {
scale_factor: Some(scale_factor),
..Self::default()
}
}
}
impl Default for HeadlessMonitor {
fn default() -> Self {
(1920, 1080).into()
}
}
impl_from_and_into_var! {
fn from<W: Into<Dip>, H: Into<Dip>>((width, height): (W, H)) -> HeadlessMonitor {
HeadlessMonitor::new(DipSize::new(width.into(), height.into()))
}
fn from<W: Into<Dip>, H: Into<Dip>, F: Into<Factor>>((width, height, scale): (W, H, F)) -> HeadlessMonitor {
HeadlessMonitor::new_scaled(DipSize::new(width.into(), height.into()), scale.into())
}
}
#[derive(Clone)]
pub struct MonitorInfo {
id: MonitorId,
is_primary: Var<bool>,
name: Var<Txt>,
position: Var<PxPoint>,
size: Var<PxSize>,
video_modes: Var<Vec<VideoMode>>,
scale_factor: Var<Factor>,
density: Var<PxDensity>,
refresh_rate: Var<Frequency>,
}
impl fmt::Debug for MonitorInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorInfo")
.field("id", &self.id)
.field("name", &self.name.get())
.field("position", &self.position.get())
.field("size", &self.size.get())
.field("refresh_rate", &self.refresh_rate.get())
.finish_non_exhaustive()
}
}
impl PartialEq for MonitorInfo {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.name.var_eq(&other.name)
}
}
impl MonitorInfo {
fn from_gen(id: MonitorId, info: zng_view_api::window::MonitorInfo) -> Self {
MonitorInfo {
id,
is_primary: var(info.is_primary),
name: var(info.name.to_txt()),
position: var(info.position),
size: var(info.size),
scale_factor: var(info.scale_factor),
video_modes: var(info.video_modes),
refresh_rate: var(info.refresh_rate),
density: var(PxDensity::default()),
}
}
fn update(&self, info: zng_view_api::window::MonitorInfo) -> bool {
fn check_set<T: VarValue + PartialEq>(var: &Var<T>, value: T) -> bool {
let ne = var.with(|v| v != &value);
var.set(value);
ne
}
check_set(&self.is_primary, info.is_primary)
| check_set(&self.name, info.name.to_txt())
| check_set(&self.position, info.position)
| check_set(&self.size, info.size)
| check_set(&self.scale_factor, info.scale_factor)
| check_set(&self.video_modes, info.video_modes)
| check_set(&self.refresh_rate, info.refresh_rate)
}
pub fn id(&self) -> MonitorId {
self.id
}
pub fn is_primary(&self) -> Var<bool> {
self.is_primary.read_only()
}
pub fn name(&self) -> Var<Txt> {
self.name.read_only()
}
pub fn position(&self) -> Var<PxPoint> {
self.position.read_only()
}
pub fn size(&self) -> Var<PxSize> {
self.size.read_only()
}
pub fn video_modes(&self) -> Var<Vec<VideoMode>> {
self.video_modes.read_only()
}
pub fn scale_factor(&self) -> Var<Factor> {
self.scale_factor.read_only()
}
pub fn density(&self) -> Var<PxDensity> {
self.density.clone()
}
pub fn refresh_rate(&self) -> Var<Frequency> {
self.refresh_rate.read_only()
}
pub fn px_rect(&self) -> PxRect {
let pos = self.position.get();
let size = self.size.get();
PxRect::new(pos, size)
}
pub fn dip_rect(&self) -> DipRect {
let pos = self.position.get();
let size = self.size.get();
let factor = self.scale_factor.get();
PxRect::new(pos, size).to_dip(factor)
}
pub fn fallback() -> Self {
let defaults = HeadlessMonitor::default();
let fct = 1.fct();
Self {
id: MonitorId::fallback(),
is_primary: var(false),
name: var("<fallback>".into()),
position: var(PxPoint::zero()),
size: var(defaults.size.to_px(fct)),
video_modes: var(vec![]),
scale_factor: var(fct),
density: var(PxDensity::default()),
refresh_rate: var(60.hertz()),
}
}
}
#[derive(Clone, Default)]
pub enum MonitorQuery {
#[default]
ParentOrPrimary,
Primary,
Query(Arc<dyn Fn() -> Option<MonitorInfo> + Send + Sync>),
}
impl std::fmt::Debug for MonitorQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f, "MonitorQuery::")?;
}
match self {
Self::ParentOrPrimary => write!(f, "ParentOrPrimary"),
Self::Primary => write!(f, "Primary"),
Self::Query(_) => write!(f, "Query(_)"),
}
}
}
impl MonitorQuery {
pub fn new(query: impl Fn() -> Option<MonitorInfo> + Send + Sync + 'static) -> Self {
Self::Query(Arc::new(query))
}
pub fn select(&self, window_id: WindowId) -> Option<MonitorInfo> {
match self {
MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
MonitorQuery::Primary => Self::primary_query(),
MonitorQuery::Query(q) => q(),
}
}
pub fn select_fallback(&self, window_id: WindowId) -> MonitorInfo {
match self {
MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
MonitorQuery::Primary => Self::primary_query(),
MonitorQuery::Query(q) => q().or_else(Self::primary_query),
}
.unwrap_or_else(Self::fallback)
}
fn fallback() -> MonitorInfo {
MONITORS_SV.read().monitors.with(|m| {
let mut best = None;
let mut best_area = Px(0);
for m in m.values() {
let m_area = m.px_rect().area();
if m_area > best_area {
best = Some(m);
best_area = m_area;
}
}
best.cloned().unwrap_or_else(MonitorInfo::fallback)
})
}
fn parent_or_primary_query(win_id: WindowId) -> Option<MonitorInfo> {
if let Some(parent) = WINDOWS.vars(win_id).unwrap().parent().get()
&& let Some(w) = WINDOWS.vars(parent)
{
return if let Some(monitor) = w.actual_monitor().get() {
MONITORS.monitor(monitor)
} else {
w.monitor().get().select(parent)
};
}
MONITORS.primary_monitor().get()
}
fn primary_query() -> Option<MonitorInfo> {
MONITORS.primary_monitor().get()
}
}
impl PartialEq for MonitorQuery {
fn eq(&self, other: &Self) -> bool {
matches!((self, other), (Self::Primary, Self::Primary))
}
}
impl_from_and_into_var! {
fn from(id: MonitorId) -> MonitorQuery {
MonitorQuery::new(move || MONITORS.monitor(id))
}
}
event_args! {
pub struct MonitorsChangedArgs {
pub removed: Vec<MonitorId>,
pub added: Vec<MonitorId>,
pub modified: Vec<MonitorId>,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
true
}
}
}
event! {
pub static MONITORS_CHANGED_EVENT: MonitorsChangedArgs;
}