1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use core::ptr::NonNull;
use log::{debug, error, info, warn};
use objc2_core_graphics::{
CGDirectDisplayID, CGDisplayChangeSummaryFlags, CGDisplayRegisterReconfigurationCallback,
CGDisplayRemoveReconfigurationCallback, CGError,
};
use std::ffi::c_void;
use stdext::function_name;
use crate::errors::{Error, Result};
use crate::events::{Event, EventSender};
use crate::util::Cleanuper;
/// `DisplayHandler` manages callbacks for macOS display reconfiguration events.
/// It dispatches `Event`s related to display changes (e.g., addition, removal, resizing) to the event loop.
pub(super) struct DisplayHandler {
/// The `EventSender` for dispatching display-related events.
events: EventSender,
/// An optional `Cleanuper` to manage the unregistration of the display reconfiguration callback.
cleanup: Option<Cleanuper>,
}
impl DisplayHandler {
/// Creates a new `DisplayHandler` instance.
///
/// # Arguments
///
/// * `events` - An `EventSender` to send display-related events.
///
/// # Returns
///
/// A new `DisplayHandler`.
pub(super) fn new(events: EventSender) -> Self {
Self {
events,
cleanup: None,
}
}
/// Starts the display handler by registering a callback for display reconfiguration events.
/// This function uses `CGDisplayRegisterReconfigurationCallback` to receive notifications about display changes.
///
/// # Returns
///
/// `Ok(())` if the callback is registered successfully, otherwise `Err(Error)` if permission is denied.
///
/// # Side Effects
///
/// - Registers a `CGDisplayReconfigurationCallback`, which will be unregistered when `cleanup` is dropped.
pub(super) fn start(&mut self) -> Result<()> {
info!("{}: Registering display handler", function_name!());
let this = unsafe { NonNull::new_unchecked(self).as_ptr() };
let result =
unsafe { CGDisplayRegisterReconfigurationCallback(Some(Self::callback), this.cast()) };
if result != CGError::Success {
return Err(Error::PermissionDenied(format!(
"{}: registering display handler callback: {result:?}",
function_name!()
)));
}
self.cleanup = Some(Cleanuper::new(Box::new(move || unsafe {
info!("{}: Unregistering display handler", function_name!());
CGDisplayRemoveReconfigurationCallback(Some(Self::callback), this.cast());
})));
Ok(())
}
/// The C-callback function for `CGDisplayReconfigurationCallback`. It dispatches to the `display_handler` method.
/// This function is declared as `extern "C-unwind"`.
///
/// # Arguments
///
/// * `display_id` - The `CGDirectDisplayID` of the display that changed.
/// * `flags` - The `CGDisplayChangeSummaryFlags` indicating the type of change.
/// * `context` - A raw pointer to the `DisplayHandler` instance.
extern "C-unwind" fn callback(
display_id: CGDirectDisplayID,
flags: CGDisplayChangeSummaryFlags,
context: *mut c_void,
) {
match NonNull::new(context).map(|this| unsafe { this.cast::<DisplayHandler>().as_mut() }) {
Some(this) => this.display_handler(display_id, flags),
_ => error!("Zero passed to Display Handler."),
}
}
/// Handles display change events and sends corresponding `Event`s (e.g., `DisplayAdded`, `DisplayRemoved`).
/// This function maps the `CGDisplayChangeSummaryFlags` to specific `Event` types and dispatches them.
///
/// # Arguments
///
/// * `display_id` - The `CGDirectDisplayID` of the display that changed.
/// * `flags` - The `CGDisplayChangeSummaryFlags` indicating the type of change.
fn display_handler(
&mut self,
display_id: CGDirectDisplayID,
flags: CGDisplayChangeSummaryFlags,
) {
debug!("display_handler: display change {display_id:?}");
let event = if flags.contains(CGDisplayChangeSummaryFlags::AddFlag) {
Event::DisplayAdded { display_id }
} else if flags.contains(CGDisplayChangeSummaryFlags::RemoveFlag) {
Event::DisplayRemoved { display_id }
} else if flags.contains(CGDisplayChangeSummaryFlags::MovedFlag) {
Event::DisplayMoved { display_id }
} else if flags.contains(CGDisplayChangeSummaryFlags::DesktopShapeChangedFlag) {
Event::DisplayResized { display_id }
} else if flags.contains(CGDisplayChangeSummaryFlags::BeginConfigurationFlag) {
Event::DisplayConfigured { display_id }
} else {
warn!("{}: unknown flag {flags:?}.", function_name!());
return;
};
_ = self
.events
.send(event)
.inspect_err(|err| warn!("{}: error sending event: {err}", function_name!()));
}
}