use log::*;
use serde::Serialize;
use std::collections::{BTreeMap, HashMap};
use std::hash::{Hash, Hasher};
use crate::core_graphics::*;
use crate::displays::*;
pub fn cg_error_to_error(cg_error: CGError, context: &str) -> Error {
assert_ne!(
cg_error,
CGError::success,
"cg_error_to_error should not be used CGError::success"
);
Error::Internal(context.to_owned())
}
pub fn cg_error_to_result(cg_error: CGError, context: &str) -> Result<(), Error> {
match cg_error {
CGError::success => Ok(()),
_ => Err(cg_error_to_error(cg_error, context)),
}
}
#[derive(Debug, Clone, Serialize)]
pub struct RealDisplayMode {
#[serde(skip_serializing)]
display_id: DisplayID,
#[serde(skip_serializing)]
mode: i32,
pub scaled: bool,
pub color_depth: usize,
pub frequency: usize,
pub extents: Point,
}
impl PartialEq for RealDisplayMode {
fn eq(&self, other: &Self) -> bool {
self.scaled() == other.scaled()
&& self.color_depth == other.color_depth
&& self.frequency == other.frequency
&& self.extents == other.extents
}
}
impl Eq for RealDisplayMode {}
impl Hash for RealDisplayMode {
fn hash<H: Hasher>(&self, state: &mut H) {
self.scaled.hash(state);
self.color_depth.hash(state);
self.frequency.hash(state);
self.extents.hash(state);
}
}
impl RealDisplayMode {
fn new(display_id: DisplayID, mode_desc: CGSDisplayModeDescription) -> Self {
if mode_desc.depth == 0 {
warn!(
"Encountered a display mode with a bit depth of zero: {:?} {:?}",
display_id, mode_desc
);
}
RealDisplayMode {
display_id,
mode: mode_desc.mode,
scaled: mode_desc.scale > 1.0,
color_depth: mode_desc.depth as usize,
frequency: mode_desc.freq.into(),
extents: Point {
x: mode_desc.width.into(),
y: mode_desc.height.into(),
},
}
}
}
impl DisplayMode for RealDisplayMode {
fn scaled(&self) -> bool {
self.scaled
}
fn color_depth(&self) -> usize {
self.color_depth
}
fn frequency(&self) -> usize {
self.frequency
}
fn extents(&self) -> &Point {
&self.extents
}
}
pub struct RealDisplayConfigTransaction {
displays: BTreeMap<String, DisplayID>,
rotations: HashMap<DisplayID, Rotation>,
brightness_map: HashMap<DisplayID, f32>,
config_ref: CGDisplayConfigRef,
dropped: bool,
}
impl RealDisplayConfigTransaction {
fn new(real_display_map: &BTreeMap<String, RealDisplay>) -> Result<Self, Error> {
let config_ref = cg_begin_display_configuration().map_err(|cg_error| {
cg_error_to_error(
cg_error,
"While attempting begin a configuration transaction",
)
})?;
Ok(Self {
displays: real_display_map
.iter()
.map(|(uuid, real_display)| (uuid.clone(), real_display.display_id))
.collect(),
rotations: HashMap::new(),
brightness_map: HashMap::new(),
config_ref,
dropped: false,
})
}
fn display_id(&self, uuid: &str) -> Result<DisplayID, Error> {
self.displays
.get(uuid)
.cloned()
.ok_or(Error::UnknownUUID(uuid.to_owned()))
}
fn move_config(&mut self) -> CGDisplayConfigRef {
let mut config_ref = std::ptr::null_mut();
std::mem::swap(&mut config_ref, &mut self.config_ref);
config_ref
}
}
impl DisplayConfigTransaction for RealDisplayConfigTransaction {
type DisplayModeType = RealDisplayMode;
fn set_mode(&mut self, uuid: &str, mode: &Self::DisplayModeType) -> Result<(), Error> {
if self.dropped {
return Err(Error::InvalidTransactionState);
}
let display_id = self.display_id(uuid)?;
if mode.display_id != display_id {
panic!(
"Tried using a display mode for display {:?} with display {:?}",
mode.display_id, display_id
);
}
cg_error_to_result(
cgs_configure_display_mode(&self.config_ref, display_id, mode.mode),
format!("While attempting to set the mode of {}", uuid,).as_str(),
)
}
fn set_rotation(&mut self, uuid: &str, rotation: Rotation) -> Result<(), Error> {
if self.dropped {
return Err(Error::InvalidTransactionState);
}
let display_id = self.display_id(uuid)?;
if self.rotations.contains_key(&display_id) {
return Err(Error::DuplicateConfiguration(uuid.to_owned()));
}
self.rotations.insert(display_id, rotation);
Ok(())
}
fn set_brightness(&mut self, uuid: &str, brightness: f32) -> Result<(), Error> {
if self.dropped {
return Err(Error::InvalidTransactionState);
} else if brightness < 0.0 || brightness > 1.0 {
return Err(Error::InvalidBrightness(brightness));
}
let display_id = self.display_id(uuid)?;
if self.brightness_map.contains_key(&display_id) {
return Err(Error::DuplicateConfiguration(uuid.to_owned()));
}
self.brightness_map.insert(display_id, brightness);
Ok(())
}
fn set_origin(&mut self, uuid: &str, point: &Point) -> Result<(), Error> {
if self.dropped {
return Err(Error::InvalidTransactionState);
}
let display_id = self.display_id(uuid)?;
cg_error_to_result(
cg_configure_display_origin(
&self.config_ref,
display_id,
point.x as i32,
point.y as i32,
),
format!("While attempting to set the origin of {}", uuid).as_str(),
)
}
fn set_enabled(&mut self, uuid: &str, enabled: bool) -> Result<(), Error> {
if self.dropped {
return Err(Error::InvalidTransactionState);
}
let display_id = self.display_id(uuid)?;
if !enabled {
cg_error_to_result(
cgs_configure_display_enabled(&self.config_ref, display_id, enabled),
format!("While attempting to adjust the enablement of {}", uuid).as_str(),
)
} else {
Ok(())
}
}
fn set_mirroring(&mut self, uuid: &str, mirror_of_uuid: Option<&str>) -> Result<(), Error> {
if self.dropped {
return Err(Error::InvalidTransactionState);
}
let display_id = self.display_id(uuid)?;
let master_id = mirror_of_uuid
.map(|uuid| self.display_id(uuid))
.transpose()?;
cg_error_to_result(
cg_configure_display_mirror_of_display(&self.config_ref, display_id, master_id),
match mirror_of_uuid {
Some(mirror_uuid) => format!(
"While attempting to set display {} mirroring to {}",
uuid, mirror_uuid
),
None => format!("While attempting to disable mirroring for display {}", uuid),
}
.as_str(),
)
}
fn commit(mut self) -> Result<(), Error> {
if self.dropped {
return Err(Error::InvalidTransactionState);
}
cg_error_to_result(
cg_complete_display_configuration(
self.move_config(),
CGConfigureOption::kCGConfigurePermanently,
),
"While attempting to commit the configuration transaction",
)?;
for (&display_id, &rotation) in &self.rotations {
cg_error_to_result(
sls_set_display_rotation(display_id, rotation.into()),
format!(
"While attempting to set display rotation of {:?} to {:?}",
display_id, rotation
)
.as_str(),
)?;
}
for (&display_id, &brightness) in &self.brightness_map {
cg_error_to_result(
display_services_set_brightness(display_id, brightness),
format!(
"While attempting to set display brightness of {:?} to {:?}",
display_id, brightness
)
.as_str(),
)?;
}
self.dropped = true;
Ok(())
}
}
impl Drop for RealDisplayConfigTransaction {
fn drop(&mut self) {
if !self.dropped {
if cg_cancel_display_configuration(self.move_config()) != CGError::success {
error!("Failed to cancel the configuration transaction");
}
self.dropped = true;
}
}
}
#[derive(Debug)]
pub struct RealDisplay {
display_id: DisplayID,
uuid: String,
mirror_of: Option<String>,
enabled: bool,
origin: Point,
rotation: Rotation,
mode: RealDisplayMode,
modes: Vec<RealDisplayMode>,
brightness: Option<f32>,
}
pub fn undo_display_rotation(point: Point, rotation: Rotation) -> Point {
match rotation {
Rotation::Zero | Rotation::OneEighty => point,
Rotation::Ninety | Rotation::TwoSeventy => Point {
x: point.y,
y: point.x,
},
}
}
impl RealDisplay {
fn compute_uuid(display_id: DisplayID) -> String {
let cfuuid = cg_display_create_uuid_from_display_id(display_id);
let cfstring = cf_uuid_create_string(kCFAllocatorDefault, cfuuid);
let mut buffer: [u8; 37] = [0; 37];
if !cf_string_get_cstring(
cfstring,
&mut buffer,
CFStringBuiltInEncodings::ASCII.into(),
) {
panic!("Buffer to receive UUID is too small.")
}
cf_release(cfstring);
cf_release(cfuuid);
String::from_utf8(buffer[0..36].to_vec())
.unwrap()
.to_lowercase()
.replace('-', "")
}
fn new(display_id: DisplayID) -> Result<Self, Error> {
let uuid = RealDisplay::compute_uuid(display_id);
let mirror_of =
cg_display_mirrors_display(display_id).map(|did| RealDisplay::compute_uuid(did));
let mut num_modes = 0;
cg_error_to_result(
cgs_get_number_of_display_modes(display_id, &mut num_modes),
format!(
"While attempting to obtain the number of display modes on {}",
uuid
)
.as_str(),
)?;
let float_rotation = cg_display_rotation(display_id);
let rotation = Rotation::try_from(float_rotation)
.expect(format!("Unexpected display rotation angle: {}", float_rotation).as_str());
let mut current_mode_num = 0;
cg_error_to_result(
cgs_get_current_display_mode(display_id, &mut current_mode_num),
format!(
"While attempting to obtain the current display mode on {}",
uuid
)
.as_str(),
)?;
let mut current_mode = None;
let mut mode_buckets: HashMap<RealDisplayMode, Vec<CGSDisplayModeDescription>> =
HashMap::new();
let mut possible_modes = Vec::new();
for mode_num in 0..num_modes {
let mut desc = CGSDisplayModeDescription::default();
cg_error_to_result(
cgs_get_display_mode_description(display_id, mode_num, &mut desc),
format!("While attempting to obtain a mode description on {}", uuid).as_str(),
)?;
let mut mode = RealDisplayMode::new(display_id, desc.clone());
mode.extents = undo_display_rotation(mode.extents, rotation);
if current_mode_num == mode_num {
current_mode = Some(mode.clone());
}
match mode_buckets.get_mut(&mode) {
Some(descs) => descs.push(desc),
None => {
mode_buckets.insert(mode.clone(), vec![desc]);
}
}
possible_modes.push(mode);
}
for (mode, descs) in &mode_buckets {
if descs.len() > 1 {
warn!(
"Encountered display modes with identical properties {:?}: {:?}",
mode, descs
);
}
}
assert!(current_mode.is_some());
let enabled = cg_display_is_active(display_id) || cg_display_is_in_mirror_set(display_id);
let cg_point = cg_display_bounds(display_id).origin;
let mut brightness = 0.0;
let brightness = match cg_error_to_result(
display_services_get_brightness(display_id, &mut brightness),
"Error obtaining display brightness",
) {
Ok(()) => Some(brightness),
Err(Error::Internal(_)) => None, Err(e) => return Err(e), };
Ok(RealDisplay {
display_id,
uuid,
mirror_of,
enabled,
origin: Point {
x: cg_point.x as i64,
y: cg_point.y as i64,
},
rotation,
mode: current_mode.unwrap(),
modes: mode_buckets.into_keys().collect::<Vec<RealDisplayMode>>(),
brightness,
})
}
}
impl Display for RealDisplay {
fn uuid(&self) -> &str {
self.uuid.as_str()
}
fn enabled(&self) -> bool {
self.enabled
}
fn origin(&self) -> &Point {
&self.origin
}
fn rotation(&self) -> Rotation {
self.rotation
}
fn brightness(&self) -> Option<f32> {
self.brightness
}
type DisplayModeType = RealDisplayMode;
fn current_mode(&self) -> &Self::DisplayModeType {
&self.mode
}
fn possible_modes(&self) -> &[Self::DisplayModeType] {
self.modes.as_slice()
}
fn mirror_of(&self) -> Option<&str> {
self.mirror_of.as_deref()
}
}
#[derive(Debug)]
pub struct RealDisplayState {
displays: BTreeMap<String, RealDisplay>,
}
impl DisplayState for RealDisplayState {
fn current() -> Result<Self, Error> {
let mut display_ids: [DisplayID; 64] = [DisplayID::default(); 64];
let mut num_displays: u32 = 0;
cg_get_online_display_list(&mut display_ids, &mut num_displays);
assert!(
num_displays <= 64,
"Number of displays is more than the input array."
);
let mut displays = Vec::new();
for id in display_ids.into_iter().take(num_displays as usize) {
displays.push(RealDisplay::new(id)?);
}
Ok(RealDisplayState {
displays: displays
.into_iter()
.map(|d: RealDisplay| (d.uuid.clone(), d))
.collect(),
})
}
type DisplayModeType = RealDisplayMode;
type DisplayType = RealDisplay;
type DisplayConfigTransactionType = RealDisplayConfigTransaction;
fn get_displays(&self) -> &BTreeMap<String, Self::DisplayType> {
&self.displays
}
fn configure(&self) -> Result<Self::DisplayConfigTransactionType, Error> {
RealDisplayConfigTransaction::new(&self.displays)
}
}