///|
/// A rectangle in physical pixel coordinates with the top-left origin shared
/// across the facade.
pub struct Rect {
x : Int
y : Int
width : Int
height : Int
} derive(Debug, Eq)
///|
pub extend Rect with Eq::{not_equal, equal}
///|
pub extend Rect with Debug::{to_repr}
///|
/// A display geometry snapshot mirroring Electron's `Display`.
pub struct DisplayInfo {
id : Int
bounds : Rect
work_area : Rect
scale_factor : Double
is_primary : Bool
} derive(Debug, Eq)
///|
pub extend DisplayInfo with Eq::{not_equal, equal}
///|
pub extend DisplayInfo with Debug::{to_repr}
///|
/// A point in the root (physical pixel) coordinate space.
pub struct Point {
x : Int
y : Int
} derive(Debug, Eq)
///|
pub extend Point with Eq::{not_equal, equal}
///|
pub extend Point with Debug::{to_repr}
///|
/// Display topology events delivered by the watch backend, mirroring
/// Electron's `screen` module events.
pub enum ScreenMonitorEvent {
DisplayAdded
DisplayRemoved
DisplayMetricsChanged
} derive(Debug, Eq)
///|
pub extend ScreenMonitorEvent with Eq::{not_equal, equal}
///|
pub extend ScreenMonitorEvent with Debug::{to_repr}
///|
pub struct ScreenMonitor {
priv handle : State
}
///|
/// Wire shapes produced by the native backend's JSON payloads.
struct DisplaysWire {
displays : Array[DisplayWire]
} derive(FromJson)
///|
pub extend DisplaysWire with @json.FromJson::{from_json}
///|
struct DisplayWire {
id : Int
x : Int
y : Int
width : Int
height : Int
work_x : Int
work_y : Int
work_width : Int
work_height : Int
scale_factor_percent : Int
is_primary : Bool
} derive(FromJson)
///|
pub extend DisplayWire with @json.FromJson::{from_json}
///|
struct PointWire {
x : Int
y : Int
} derive(FromJson)
///|
pub extend PointWire with @json.FromJson::{from_json}
///|
fn display_from_wire(w : DisplayWire) -> DisplayInfo {
DisplayInfo::{
id: w.id,
bounds: Rect::{ x: w.x, y: w.y, width: w.width, height: w.height, },
work_area: Rect::{
x: w.work_x,
y: w.work_y,
width: w.work_width,
height: w.work_height,
},
scale_factor: w.scale_factor_percent.to_double() / 100.0,
is_primary: w.is_primary,
}
}
///|
fn decode_displays(bytes : Bytes) -> DisplaysWire? {
let text = @utf8.decode_lossy(bytes[:])
if text.is_empty() {
return None
}
let wire : DisplaysWire = @json.from_json(@json.parse(text)) catch {
_ => return None
}
Some(wire)
}
///|
fn decode_display(bytes : Bytes) -> DisplayWire? {
let text = @utf8.decode_lossy(bytes[:])
if text.is_empty() {
return None
}
let wire : DisplayWire = @json.from_json(@json.parse(text)) catch {
_ => return None
}
Some(wire)
}
///|
fn decode_point(bytes : Bytes) -> PointWire? {
let text = @utf8.decode_lossy(bytes[:])
if text.is_empty() {
return None
}
let wire : PointWire = @json.from_json(@json.parse(text)) catch {
_ => return None
}
Some(wire)
}
///|
fn decode_native_detail(bytes : Bytes) -> String {
if bytes.is_empty() {
return ""
}
@utf8.decode_lossy(bytes[:])
}
///|
/// Creates a new screen monitor handle. The native backend is verified during
/// creation so an unavailable platform surfaces immediately as
/// `BackendUnavailable`.
pub fn ScreenMonitor::ScreenMonitor() -> ScreenMonitor raise ScreenMonitorError {
let handle = native_create()
let monitor = ScreenMonitor::{ handle, }
let status = native_status(handle)
if status != native_status_ok {
raise monitor.classify_status(status, "create")
}
monitor
}
///|
fn ScreenMonitor::classify_status(
self : ScreenMonitor,
status : Int,
operation : String,
) -> ScreenMonitorError {
let detail = decode_native_detail(native_last_error(self.handle))
match status {
_ if status == native_status_backend_unavailable =>
BackendUnavailable(detail~)
_ if status == native_status_operation_failed =>
OperationFailed(operation~, detail~)
_ if status == native_status_empty => Empty
_ => OperationFailed(operation~, detail~)
}
}
///|
/// Returns every connected display, with the primary display listed first.
/// Corresponds to Electron's `screen.getAllDisplays()`.
pub fn ScreenMonitor::displays(
self : ScreenMonitor,
) -> Array[DisplayInfo] raise ScreenMonitorError {
let bytes = native_enumerate_json(self.handle)
let status = native_status(self.handle)
if status != native_status_ok {
raise self.classify_status(status, "displays")
}
match decode_displays(bytes) {
Some(wire) => wire.displays.map(fn(d) { display_from_wire(d) })
None => raise Empty
}
}
///|
/// Returns the primary display. Corresponds to Electron's
/// `screen.getPrimaryDisplay()`; raises `Empty` when no display is connected.
pub fn ScreenMonitor::primary_display(
self : ScreenMonitor,
) -> DisplayInfo raise ScreenMonitorError {
let all = self.displays()
for display in all {
if display.is_primary {
return display
}
}
if all.is_empty() {
raise Empty
} else {
all[0]
}
}
///|
/// Returns the display that is nearest to the given point, preferring a display
/// that actually contains the point. Corresponds to Electron's
/// `screen.getDisplayNearestPoint(point)`.
pub fn ScreenMonitor::display_nearest_point(
self : ScreenMonitor,
x : Int,
y : Int,
) -> DisplayInfo raise ScreenMonitorError {
let bytes = native_nearest_display_json(self.handle, x, y)
let status = native_status(self.handle)
if status != native_status_ok {
raise self.classify_status(status, "display_nearest_point")
}
match decode_display(bytes) {
Some(wire) => display_from_wire(wire)
None => raise Empty
}
}
///|
/// Returns the current cursor position in physical pixels with the top-left
/// origin. Corresponds to Electron's `screen.getCursorScreenPoint()`.
pub fn ScreenMonitor::cursor_point(
self : ScreenMonitor,
) -> Point raise ScreenMonitorError {
let bytes = native_cursor_point_json(self.handle)
let status = native_status(self.handle)
if status != native_status_ok {
raise self.classify_status(status, "cursor_point")
}
match decode_point(bytes) {
Some(wire) => Point::{ x: wire.x, y: wire.y, }
None => raise Empty
}
}
///|
/// Starts the event watch backend. The backend is best-effort: a platform that
/// cannot watch (for example Linux without an X11 display or RandR) records the
/// failure on the native side and returns `BackendUnavailable`, leaving polling
/// queries fully usable.
pub fn ScreenMonitor::start_watching(
self : ScreenMonitor,
) -> Unit raise ScreenMonitorError {
let status = native_start_watching(self.handle)
if status != native_status_ok {
raise self.classify_status(status, "start_watching")
}
}
///|
/// Stops the event watch backend and joins its thread. Idempotent.
pub fn ScreenMonitor::stop_watching(
self : ScreenMonitor,
) -> Unit raise ScreenMonitorError {
let status = native_stop_watching(self.handle)
if status != native_status_ok {
raise self.classify_status(status, "stop_watching")
}
}
///|
/// Drains every pending event from the queue. Returns an empty array when no
/// event is pending; never raises for an empty queue.
pub fn ScreenMonitor::drain_events(
self : ScreenMonitor,
) -> Array[ScreenMonitorEvent] {
let events : Array[ScreenMonitorEvent] = []
let event = Ref(0)
while true {
let status = native_take_event(self.handle, event)
if status == native_status_ok {
events.push(screen_monitor_event_from_native_code(event.val))
} else {
break
}
}
events
}
///|
fn screen_monitor_event_from_native_code(code : Int) -> ScreenMonitorEvent {
match code {
_ if code == native_event_added => DisplayAdded
_ if code == native_event_removed => DisplayRemoved
_ if code == native_event_metrics_changed => DisplayMetricsChanged
_ => DisplayMetricsChanged
}
}
///|
/// Releases the native backend and tears the watch thread down. Idempotent.
pub fn ScreenMonitor::destroy(self : ScreenMonitor) -> Unit {
native_destroy(self.handle)
}