use serde::Serialize;
const FULL_SCALE: i64 = 65535;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct VirtualDesktop {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}
impl VirtualDesktop {
fn last_pixel(self) -> (i64, i64) {
(i64::from(self.width) - 1, i64::from(self.height) - 1)
}
fn contains(self, x: i32, y: i32) -> bool {
let right = i64::from(self.x) + i64::from(self.width);
let bottom = i64::from(self.y) + i64::from(self.height);
i64::from(x) >= i64::from(self.x)
&& i64::from(y) >= i64::from(self.y)
&& i64::from(x) < right
&& i64::from(y) < bottom
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormalizeError {
Outside {
x: i32,
y: i32,
desktop: VirtualDesktop,
},
Empty { desktop: VirtualDesktop },
}
impl std::fmt::Display for NormalizeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Outside { x, y, desktop } => write!(
f,
"({x}, {y}) is not a point on this machine's desktop, which spans \
{} × {} from ({}, {}). Windows would clamp an absolute event to the \
nearest edge and click there, so this is refused instead. Re-mark the \
region with pixelcoords on this machine",
desktop.width, desktop.height, desktop.x, desktop.y
),
Self::Empty { desktop } => write!(
f,
"Windows reports a virtual desktop of {} × {}, so there is no screen to \
aim at. A disconnected RDP session or a machine with no attached \
display looks like this",
desktop.width, desktop.height
),
}
}
}
impl std::error::Error for NormalizeError {}
pub fn normalize(desktop: VirtualDesktop, x: i32, y: i32) -> Result<(i32, i32), NormalizeError> {
if desktop.width <= 0 || desktop.height <= 0 {
return Err(NormalizeError::Empty { desktop });
}
if !desktop.contains(x, y) {
return Err(NormalizeError::Outside { x, y, desktop });
}
let (last_x, last_y) = desktop.last_pixel();
Ok((
scale(i64::from(x) - i64::from(desktop.x), last_x),
scale(i64::from(y) - i64::from(desktop.y), last_y),
))
}
fn scale(offset: i64, span: i64) -> i32 {
if span <= 0 {
return 0;
}
((offset * FULL_SCALE + span / 2) / span) as i32
}
#[cfg(test)]
fn denormalize(desktop: VirtualDesktop, dx: i32, dy: i32) -> (i32, i32) {
let (last_x, last_y) = desktop.last_pixel();
let back = |value: i32, span: i64| {
if span <= 0 {
return 0;
}
((i64::from(value) * span + FULL_SCALE / 2) / FULL_SCALE) as i32
};
(desktop.x + back(dx, last_x), desktop.y + back(dy, last_y))
}
#[cfg(test)]
mod tests {
use super::*;
const MIXED: VirtualDesktop = VirtualDesktop {
x: -1920,
y: -120,
width: 3840,
height: 1200,
};
const PRIMARY_ONLY: VirtualDesktop = VirtualDesktop {
x: 0,
y: 0,
width: 1920,
height: 1080,
};
#[test]
fn the_far_corner_is_reachable_at_all() {
let (x, y) = normalize(PRIMARY_ONLY, 0, 0).expect("the origin is a pixel");
assert_eq!((x, y), (0, 0));
let (x, y) = normalize(PRIMARY_ONLY, 1919, 1079).expect("the last pixel is a pixel");
assert_eq!(
(x, y),
(65535, 65535),
"the rightmost column and bottom row must be addressable"
);
}
#[test]
fn a_display_left_of_the_primary_normalizes_from_the_desktop_origin() {
assert_eq!(
normalize(MIXED, -1920, -120).expect("the desktop's own corner"),
(0, 0)
);
assert_eq!(
normalize(MIXED, 1919, 1079).expect("the far corner"),
(65535, 65535)
);
let (x, y) = normalize(MIXED, 0, 0).expect("the primary's origin");
assert_eq!((x, y), (32776, 6559));
assert_eq!(denormalize(MIXED, x, y), (0, 0));
}
#[test]
fn every_pixel_maps_back_to_itself() {
for desktop in [PRIMARY_ONLY, MIXED] {
for step in 0..desktop.width {
let x = desktop.x + step;
let (dx, dy) = normalize(desktop, x, desktop.y).expect("on the desktop");
assert_eq!(
denormalize(desktop, dx, dy).0,
x,
"x={x} came back wrong on {desktop:?}"
);
}
for step in 0..desktop.height {
let y = desktop.y + step;
let (dx, dy) = normalize(desktop, desktop.x, y).expect("on the desktop");
assert_eq!(
denormalize(desktop, dx, dy).1,
y,
"y={y} came back wrong on {desktop:?}"
);
}
}
}
#[test]
fn rounding_is_to_the_nearest_not_toward_zero() {
let (dx, _) = normalize(PRIMARY_ONLY, 1000, 0).expect("on screen");
assert_eq!(dx, 34151);
assert_eq!(denormalize(PRIMARY_ONLY, dx, 0).0, 1000);
}
#[test]
fn a_point_off_the_desktop_is_refused_by_name() {
for (x, y) in [(1920, 0), (0, 1080), (-1, 0), (0, -1)] {
let error = normalize(PRIMARY_ONLY, x, y).expect_err("off the desktop");
let message = error.to_string();
assert!(message.contains(&format!("({x}, {y})")), "{message}");
assert!(message.contains("clamp"), "says why it refused: {message}");
assert!(matches!(error, NormalizeError::Outside { .. }));
}
assert!(normalize(MIXED, -1, 0).is_ok());
}
#[test]
fn a_desktop_with_no_pixels_is_refused_rather_than_divided_by() {
for desktop in [
VirtualDesktop {
x: 0,
y: 0,
width: 0,
height: 0,
},
VirtualDesktop {
x: 0,
y: 0,
width: 1920,
height: 0,
},
] {
let error = normalize(desktop, 0, 0).expect_err("nothing to aim at");
assert!(matches!(error, NormalizeError::Empty { .. }));
assert!(error.to_string().contains("no screen to aim at"));
}
}
#[test]
fn a_single_pixel_axis_has_one_reachable_coordinate() {
let sliver = VirtualDesktop {
x: 0,
y: 0,
width: 1,
height: 1,
};
assert_eq!(normalize(sliver, 0, 0).expect("the only pixel"), (0, 0));
assert!(normalize(sliver, 1, 0).is_err());
}
#[test]
fn a_desktop_at_full_scale_does_not_overflow() {
let huge = VirtualDesktop {
x: 0,
y: 0,
width: 65536,
height: 65536,
};
assert_eq!(normalize(huge, 0, 0).expect("the origin"), (0, 0));
assert_eq!(
normalize(huge, 65535, 65535).expect("the far corner"),
(65535, 65535)
);
}
}