use std::hash::Hash;
use rustc_hash::FxHashMap;
use smithay_client_toolkit::output::{OutputHandler, OutputState};
use super::wayland::WaylandState;
use crate::outputs::{self, OutputId, OutputInfo};
use smithay_client_toolkit::reexports::client::{
Connection, Proxy, QueueHandle, protocol::wl_output,
};
pub struct OutputRegistry<K> {
output_ids: FxHashMap<K, OutputId>,
next_output_id: u32,
}
impl<K> Default for OutputRegistry<K> {
fn default() -> Self {
Self {
output_ids: FxHashMap::default(),
next_output_id: 0,
}
}
}
impl<K: Eq + Hash> OutputRegistry<K> {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn add(&mut self, key: K) -> OutputId {
let id = OutputId::from_raw(self.next_output_id);
self.next_output_id += 1;
self.output_ids.insert(key, id);
id
}
pub(crate) fn id_for(&self, key: &K) -> Option<OutputId> {
self.output_ids.get(key).copied()
}
pub(crate) fn remove(&mut self, key: &K) -> Option<OutputId> {
self.output_ids.remove(key)
}
pub(crate) fn connected<D>(
&self,
live: impl IntoIterator<Item = (K, D)>,
describe: impl Fn(D, OutputId) -> Option<OutputInfo>,
) -> Vec<OutputInfo> {
let mut list: Vec<OutputInfo> = live
.into_iter()
.filter_map(|(key, global)| {
let id = self.id_for(&key)?;
describe(global, id)
})
.collect();
list.sort_by_key(|o| o.id);
list
}
}
impl WaylandState {
pub(super) fn wl_output_for(&self, id: OutputId) -> Option<wl_output::WlOutput> {
self.output_state
.outputs()
.find(|o| self.outputs.id_for(&o.id()) == Some(id))
}
fn sync_outputs(&mut self) {
let list = self.outputs.connected(
self.output_state.outputs().map(|o| (o.id(), o)),
|global, id| {
let info = self.output_state.info(&global)?;
Some(OutputInfo {
id,
name: info.name,
description: info.description,
make: info.make,
model: info.model,
scale_factor: info.scale_factor,
logical_size: info.logical_size,
logical_position: info.logical_position,
})
},
);
outputs::sync_outputs(list);
}
}
impl OutputHandler for WaylandState {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
output: wl_output::WlOutput,
) {
let id = self.outputs.add(output.id());
log::info!(
"Output {:?} connected: {:?}",
id,
self.output_state.info(&output).and_then(|i| i.name)
);
self.sync_outputs();
}
fn update_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
self.sync_outputs();
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
output: wl_output::WlOutput,
) {
if let Some(id) = self.outputs.remove(&output.id()) {
log::info!("Output {:?} disconnected", id);
outputs::output_removed(id);
}
self.sync_outputs();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn info(id: OutputId, name: &str) -> OutputInfo {
OutputInfo {
model: "LG HDR 4K".to_string(),
..OutputInfo::named(id, name)
}
}
#[test]
fn an_unplugged_monitor_leaves_no_phantom_behind() {
let mut registry = OutputRegistry::new();
let laptop = registry.add("eDP-1");
let external = registry.add("DP-2");
assert_eq!(registry.remove(&"DP-2"), Some(external));
let list = registry.connected([("eDP-1", "eDP-1"), ("DP-2", "DP-2")], |name, id| {
Some(info(id, name))
});
assert_eq!(list, vec![info(laptop, "eDP-1")]);
assert_eq!(
registry.next_output_id, 2,
"rebuilding the list minted an id; only a new global may do that"
);
}
}