use std::{
cell::RefCell,
fmt,
rc::{self, Rc},
sync::{self, Arc, Mutex},
};
use wayland_client::{
protocol::{
wl_output::{self, Event, WlOutput},
wl_registry,
},
Attached, DispatchData, Main,
};
use wayland_protocols::unstable::xdg_output::v1::client::{
zxdg_output_manager_v1::ZxdgOutputManagerV1,
zxdg_output_v1::{self, ZxdgOutputV1},
};
pub use wayland_client::protocol::wl_output::{Subpixel, Transform};
#[derive(Copy, Clone, Debug)]
pub struct Mode {
pub dimensions: (i32, i32),
pub refresh_rate: i32,
pub is_current: bool,
pub is_preferred: bool,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct OutputInfo {
pub id: u32,
pub model: String,
pub make: String,
pub name: String,
pub description: String,
pub location: (i32, i32),
pub physical_size: (i32, i32),
pub subpixel: Subpixel,
pub transform: Transform,
pub scale_factor: i32,
pub modes: Vec<Mode>,
pub obsolete: bool,
}
impl OutputInfo {
fn new(id: u32) -> OutputInfo {
OutputInfo {
id,
model: String::new(),
make: String::new(),
name: String::new(),
description: String::new(),
location: (0, 0),
physical_size: (0, 0),
subpixel: Subpixel::Unknown,
transform: Transform::Normal,
scale_factor: 1,
modes: Vec::new(),
obsolete: false,
}
}
}
type OutputCallback = dyn Fn(WlOutput, &OutputInfo, DispatchData) + Send + Sync;
enum OutputData {
Ready {
info: OutputInfo,
callbacks: Vec<sync::Weak<OutputCallback>>,
},
Pending {
id: u32,
has_xdg: bool,
events: Vec<Event>,
callbacks: Vec<sync::Weak<OutputCallback>>,
},
PendingXDG {
info: OutputInfo,
callbacks: Vec<sync::Weak<OutputCallback>>,
},
}
type OutputStatusCallback = dyn FnMut(WlOutput, &OutputInfo, DispatchData) + 'static;
pub struct OutputHandler {
outputs: Vec<(u32, Attached<WlOutput>)>,
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>>,
xdg_listener: Option<rc::Weak<RefCell<XdgOutputHandlerInner>>>,
}
impl OutputHandler {
pub fn new() -> OutputHandler {
OutputHandler {
outputs: Vec::new(),
status_listeners: Rc::new(RefCell::new(Vec::new())),
xdg_listener: None,
}
}
}
impl crate::environment::MultiGlobalHandler<WlOutput> for OutputHandler {
fn created(
&mut self,
registry: Attached<wl_registry::WlRegistry>,
id: u32,
version: u32,
_: DispatchData,
) {
let version = std::cmp::min(version, 3);
let output = registry.bind::<WlOutput>(version, id);
let has_xdg;
if let Some(xdg) = self.xdg_listener.as_ref().and_then(rc::Weak::upgrade) {
has_xdg = xdg.borrow_mut().new_xdg_output(&output, &self.status_listeners);
} else {
has_xdg = false;
}
if version > 1 {
output.as_ref().user_data().set_threadsafe(|| {
Mutex::new(OutputData::Pending { id, has_xdg, events: vec![], callbacks: vec![] })
});
} else {
output.as_ref().user_data().set_threadsafe(|| {
Mutex::new(OutputData::Ready { info: OutputInfo::new(id), callbacks: vec![] })
});
}
let status_listeners_handle = self.status_listeners.clone();
let xdg_listener_handle = self.xdg_listener.clone();
output.quick_assign(move |output, event, ddata| {
process_output_event(
output,
event,
ddata,
&status_listeners_handle,
&xdg_listener_handle,
)
});
self.outputs.push((id, (*output).clone()));
}
fn removed(&mut self, id: u32, mut ddata: DispatchData) {
let status_listeners_handle = &self.status_listeners;
let xdg_listener_handle = &self.xdg_listener;
self.outputs.retain(|(i, o)| {
if *i != id {
true
} else {
make_obsolete(o, ddata.reborrow(), status_listeners_handle, xdg_listener_handle);
false
}
});
}
fn get_all(&self) -> Vec<Attached<WlOutput>> {
self.outputs.iter().map(|(_, o)| o.clone()).collect()
}
}
impl fmt::Debug for OutputHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OutputHandler")
.field("outputs", &self.outputs)
.field("status_listeners", &"Fn() -> { ... }")
.field("xdg_listener", &self.xdg_listener)
.finish()
}
}
fn process_output_event(
output: Main<WlOutput>,
event: Event,
mut ddata: DispatchData,
listeners: &Rc<RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>>,
xdg_listener: &Option<rc::Weak<RefCell<XdgOutputHandlerInner>>>,
) {
let udata_mutex = output
.as_ref()
.user_data()
.get::<Mutex<OutputData>>()
.expect("SCTK: wl_output has invalid UserData");
let mut udata = udata_mutex.lock().unwrap();
if let Event::Done = event {
let (id, has_xdg, pending_events, mut callbacks) = match *udata {
OutputData::Pending { id, has_xdg, events: ref mut v, callbacks: ref mut cb } => {
(id, has_xdg, std::mem::take(v), std::mem::take(cb))
}
OutputData::PendingXDG { ref mut info, ref mut callbacks } => {
notify(&output, info, ddata.reborrow(), callbacks);
notify_status_listeners(&output, info, ddata, listeners);
let info = info.clone();
let callbacks = std::mem::take(callbacks);
*udata = OutputData::Ready { info, callbacks };
return;
}
OutputData::Ready { ref mut info, ref mut callbacks } => {
notify(&output, info, ddata, callbacks);
return;
}
};
let mut info = OutputInfo::new(id);
for evt in pending_events {
merge_event(&mut info, evt);
}
notify(&output, &info, ddata.reborrow(), &mut callbacks);
if let Some(xdg) = xdg_listener.as_ref().and_then(rc::Weak::upgrade) {
if has_xdg || xdg.borrow_mut().new_xdg_output(&output, listeners) {
*udata = OutputData::PendingXDG { info, callbacks };
return;
}
}
notify_status_listeners(&output, &info, ddata, listeners);
*udata = OutputData::Ready { info, callbacks };
} else {
match *udata {
OutputData::Pending { events: ref mut v, .. } => v.push(event),
OutputData::PendingXDG { ref mut info, .. }
| OutputData::Ready { ref mut info, .. } => {
merge_event(info, event);
}
}
}
}
fn make_obsolete(
output: &Attached<WlOutput>,
mut ddata: DispatchData,
listeners: &RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>,
xdg_listener: &Option<rc::Weak<RefCell<XdgOutputHandlerInner>>>,
) {
let udata_mutex = output
.as_ref()
.user_data()
.get::<Mutex<OutputData>>()
.expect("SCTK: wl_output has invalid UserData");
let mut udata = udata_mutex.lock().unwrap();
if let Some(xdg) = xdg_listener.as_ref().and_then(rc::Weak::upgrade) {
xdg.borrow_mut().destroy_xdg_output(output);
}
let (id, mut callbacks) = match *udata {
OutputData::PendingXDG { ref mut info, ref mut callbacks }
| OutputData::Ready { ref mut info, ref mut callbacks } => {
info.obsolete = true;
notify(output, info, ddata.reborrow(), callbacks);
notify_status_listeners(output, info, ddata, listeners);
return;
}
OutputData::Pending { id, callbacks: ref mut cb, .. } => (id, std::mem::take(cb)),
};
let mut info = OutputInfo::new(id);
info.obsolete = true;
notify(output, &info, ddata.reborrow(), &mut callbacks);
notify_status_listeners(output, &info, ddata, listeners);
*udata = OutputData::Ready { info, callbacks };
}
fn merge_event(info: &mut OutputInfo, event: Event) {
match event {
Event::Geometry {
x,
y,
physical_width,
physical_height,
subpixel,
model,
make,
transform,
} => {
info.location = (x, y);
info.physical_size = (physical_width, physical_height);
info.subpixel = subpixel;
info.transform = transform;
info.model = model;
info.make = make;
}
Event::Scale { factor } => {
info.scale_factor = factor;
}
Event::Mode { width, height, refresh, flags } => {
let mut found = false;
if let Some(mode) = info
.modes
.iter_mut()
.find(|m| m.dimensions == (width, height) && m.refresh_rate == refresh)
{
mode.is_preferred = flags.contains(wl_output::Mode::Preferred);
mode.is_current = flags.contains(wl_output::Mode::Current);
found = true;
}
if !found {
info.modes.push(Mode {
dimensions: (width, height),
refresh_rate: refresh,
is_preferred: flags.contains(wl_output::Mode::Preferred),
is_current: flags.contains(wl_output::Mode::Current),
})
}
}
_ => (),
}
}
fn notify(
output: &WlOutput,
info: &OutputInfo,
mut ddata: DispatchData,
callbacks: &mut Vec<sync::Weak<OutputCallback>>,
) {
callbacks.retain(|weak| {
if let Some(arc) = sync::Weak::upgrade(weak) {
(*arc)(output.clone(), info, ddata.reborrow());
true
} else {
false
}
});
}
fn notify_status_listeners(
output: &WlOutput,
info: &OutputInfo,
mut ddata: DispatchData,
listeners: &RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>,
) {
listeners.borrow_mut().retain(|lst| {
if let Some(cb) = rc::Weak::upgrade(lst) {
(&mut *cb.borrow_mut())(output.clone(), info, ddata.reborrow());
true
} else {
false
}
})
}
pub fn with_output_info<T, F: FnOnce(&OutputInfo) -> T>(output: &WlOutput, f: F) -> Option<T> {
if let Some(udata_mutex) = output.as_ref().user_data().get::<Mutex<OutputData>>() {
let udata = udata_mutex.lock().unwrap();
match *udata {
OutputData::PendingXDG { ref info, .. } | OutputData::Ready { ref info, .. } => {
Some(f(info))
}
OutputData::Pending { .. } => None,
}
} else {
None
}
}
pub fn add_output_listener<F: Fn(WlOutput, &OutputInfo, DispatchData) + Send + Sync + 'static>(
output: &WlOutput,
f: F,
) -> OutputListener {
let arc = Arc::new(f) as Arc<_>;
if let Some(udata_mutex) = output.as_ref().user_data().get::<Mutex<OutputData>>() {
let mut udata = udata_mutex.lock().unwrap();
match *udata {
OutputData::Pending { ref mut callbacks, .. }
| OutputData::PendingXDG { ref mut callbacks, .. }
| OutputData::Ready { ref mut callbacks, .. } => {
callbacks.push(Arc::downgrade(&arc));
}
}
}
OutputListener { _cb: arc }
}
pub struct OutputListener {
_cb: Arc<dyn Fn(WlOutput, &OutputInfo, DispatchData) + Send + Sync + 'static>,
}
impl fmt::Debug for OutputListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OutputListener").field("_cb", &"fn() -> { ... }").finish()
}
}
pub struct OutputStatusListener {
_cb: Rc<RefCell<OutputStatusCallback>>,
}
impl fmt::Debug for OutputStatusListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OutputStatusListener").field("_cb", &"fn() -> { ... }").finish()
}
}
pub trait OutputHandling {
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData) + 'static>(
&mut self,
f: F,
) -> OutputStatusListener;
}
impl OutputHandling for OutputHandler {
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData) + 'static>(
&mut self,
f: F,
) -> OutputStatusListener {
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
OutputStatusListener { _cb: rc }
}
}
impl<E: OutputHandling> crate::environment::Environment<E> {
#[must_use = "the returned OutputStatusListener keeps your callback alive, dropping it will disable it"]
pub fn listen_for_outputs<F: FnMut(WlOutput, &OutputInfo, DispatchData) + 'static>(
&self,
f: F,
) -> OutputStatusListener {
self.with_inner(move |inner| OutputHandling::listen(inner, f))
}
}
impl<E: crate::environment::MultiGlobalHandler<WlOutput>> crate::environment::Environment<E> {
pub fn get_all_outputs(&self) -> Vec<WlOutput> {
self.get_all_globals::<WlOutput>().into_iter().map(|o| o.detach()).collect()
}
}
#[derive(Debug)]
pub struct XdgOutputHandler {
inner: Rc<RefCell<XdgOutputHandlerInner>>,
}
#[derive(Debug)]
struct XdgOutputHandlerInner {
xdg_manager: Option<Attached<ZxdgOutputManagerV1>>,
outputs: Vec<(WlOutput, Attached<ZxdgOutputV1>)>,
}
impl XdgOutputHandler {
pub fn new(output_handler: &mut OutputHandler) -> Self {
let inner =
Rc::new(RefCell::new(XdgOutputHandlerInner { xdg_manager: None, outputs: Vec::new() }));
output_handler.xdg_listener = Some(Rc::downgrade(&inner));
XdgOutputHandler { inner }
}
pub fn new_output_handlers() -> (OutputHandler, Self) {
let mut oh = OutputHandler::new();
let xh = XdgOutputHandler::new(&mut oh);
(oh, xh)
}
}
impl XdgOutputHandlerInner {
fn new_xdg_output(
&mut self,
output: &WlOutput,
listeners: &Rc<RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>>,
) -> bool {
if let Some(xdg_manager) = &self.xdg_manager {
let xdg_main = xdg_manager.get_xdg_output(output);
let wl_out = output.clone();
let listeners = listeners.clone();
xdg_main.quick_assign(move |_xdg_out, event, ddata| {
process_xdg_event(&wl_out, event, ddata, &listeners)
});
self.outputs.push((output.clone(), xdg_main.into()));
true
} else {
false
}
}
fn destroy_xdg_output(&mut self, output: &WlOutput) {
self.outputs.retain(|(out, xdg_out)| {
if out.as_ref().is_alive() && out != output {
true
} else {
xdg_out.destroy();
false
}
});
}
}
fn process_xdg_event(
wl_out: &WlOutput,
event: zxdg_output_v1::Event,
mut ddata: DispatchData,
listeners: &RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>,
) {
use zxdg_output_v1::Event;
let udata_mutex = wl_out
.as_ref()
.user_data()
.get::<Mutex<OutputData>>()
.expect("SCTK: wl_output has invalid UserData");
let mut udata = udata_mutex.lock().unwrap();
let (info, callbacks, pending) = match &mut *udata {
OutputData::Ready { info, callbacks } => (info, callbacks, false),
OutputData::PendingXDG { info, callbacks } => (info, callbacks, true),
OutputData::Pending { .. } => unreachable!(),
};
match event {
Event::Name { name } => {
info.name = name;
}
Event::Description { description } => {
info.description = description;
}
Event::Done => {
notify(wl_out, info, ddata.reborrow(), callbacks);
if pending {
notify_status_listeners(wl_out, info, ddata, listeners);
let info = info.clone();
let callbacks = std::mem::take(callbacks);
*udata = OutputData::Ready { info, callbacks };
}
}
_ => (),
}
}
impl crate::environment::GlobalHandler<ZxdgOutputManagerV1> for XdgOutputHandler {
fn created(
&mut self,
registry: Attached<wl_registry::WlRegistry>,
id: u32,
version: u32,
_: DispatchData,
) {
let version = std::cmp::min(version, 3);
let mut inner = self.inner.borrow_mut();
let xdg_manager: Main<ZxdgOutputManagerV1> = registry.bind(version, id);
inner.xdg_manager = Some(xdg_manager.into());
}
fn get(&self) -> Option<Attached<ZxdgOutputManagerV1>> {
let inner = self.inner.borrow();
inner.xdg_manager.clone()
}
}