///|
pub(all) enum PowerSource {
Unknown
AC
Battery
} derive(Debug, Eq)
///|
pub extend PowerSource with Eq::{not_equal, equal}
///|
pub extend PowerSource with Debug::{to_repr}
///|
pub(all) struct PowerSourceInfo {
source : PowerSource
battery_percent : Int32?
} derive(Debug, Eq)
///|
pub extend PowerSourceInfo with Eq::{not_equal, equal}
///|
pub extend PowerSourceInfo with Debug::{to_repr}
///|
/// System power and session events delivered by the watch backend.
pub enum PowerMonitorEvent {
Suspend
Resume
OnAC
OnBattery
LockScreen
UnlockScreen
} derive(Debug, Eq)
///|
pub extend PowerMonitorEvent with Eq::{not_equal, equal}
///|
pub extend PowerMonitorEvent with Debug::{to_repr}
///|
pub struct PowerMonitor {
priv handle : State
priv mut watching : Bool
}
///|
pub fn PowerMonitor::PowerMonitor() -> PowerMonitor raise PowerMonitorError {
let handle = native_create()
let monitor = PowerMonitor::{ handle, watching: false, }
monitor.finish_create()
monitor
}
///|
fn PowerMonitor::finish_create(
self : PowerMonitor,
) -> Unit raise PowerMonitorError {
let status = native_status(self.handle)
if status != native_status_ok {
raise self.classify_status(status, "create")
}
}
///|
pub fn PowerMonitor::idle_seconds(
self : PowerMonitor,
) -> Int32 raise PowerMonitorError {
let seconds : Ref[Int64] = Ref(0L)
let status = native_idle_seconds(self.handle, seconds)
if status != native_status_ok {
raise self.classify_status(status, "idle_seconds")
}
Int64::to_int(seconds.val)
}
///|
pub fn PowerMonitor::power_source(
self : PowerMonitor,
) -> PowerSourceInfo raise PowerMonitorError {
let source_code = Ref(0)
let percent = Ref(0)
let has_percent = Ref(0)
let status = native_source(self.handle, source_code, percent, has_percent)
if status != native_status_ok {
raise self.classify_status(status, "power_source")
}
PowerSourceInfo::{
source: power_source_from_native_code(source_code.val),
battery_percent: if has_percent.val != 0 {
Some(percent.val)
} else {
None
},
}
}
///|
/// Runs `body` after native event watching is ready. Returning, raising, or
/// cancelling always stops the watcher and awaits native cleanup. A second
/// watching scope on the same monitor is rejected rather than queued.
/// Linux connection setup and subscriptions are cancellable and have a five
/// second startup deadline. A disconnected bus fails the scope and cancels body.
pub async fn[T] PowerMonitor::with_watching(
self : PowerMonitor,
body : async () -> T,
) -> T {
if self.watching {
raise OperationFailed(
operation="with_watching",
detail="a watching scope is already active",
)
}
self.watching = true
// Keep self (and its native handle) alive until finish_watching has observed EOF.
defer {
self.watching = false
}
let completion = self.begin_watching()
defer self.finish_watching(completion)
match completion {
None => body()
Some(reader) =>
@async.any([
body,
() => {
ignore(reader.read_all())
raise self.classify_status(
native_watch_status(self.handle),
"with_watching",
)
},
])
}
}
///|
#cfg(platform="windows")
fn PowerMonitor::begin_watching(
self : PowerMonitor,
) -> @pipe.PipeRead? raise PowerMonitorError {
let status = native_start_watching(self.handle, -1)
if status != native_status_ok {
raise self.classify_status(status, "with_watching")
}
None
}
///|
#cfg(not(platform="windows"))
async fn PowerMonitor::begin_watching(
self : PowerMonitor,
) -> @pipe.PipeRead? raise PowerMonitorError {
if !(@async.platform is Linux) {
let status = native_start_watching(self.handle, -1)
if status != native_status_ok {
raise self.classify_status(status, "with_watching")
}
return None
}
let (reader, writer) = @pipe.pipe() catch {
error =>
raise OperationFailed(operation="with_watching", detail=error.to_string())
}
let status = native_start_watching(self.handle, writer.fd())
writer.close()
if status != native_status_ok {
reader.close()
raise self.classify_status(status, "with_watching")
}
let mut ready = false
defer (async fn() -> Unit raise PowerMonitorError + nocancel {
if !ready {
self.finish_watching(Some(reader))
}
})()
let notice = reader.read_some(max_len=1) catch {
error =>
raise OperationFailed(operation="with_watching", detail=error.to_string())
}
let status = native_watch_status(self.handle)
if notice is None || status != native_status_ok {
raise self.classify_status(status, "with_watching")
}
ready = true
Some(reader)
}
///|
async fn PowerMonitor::finish_watching(
self : PowerMonitor,
completion : @pipe.PipeRead?,
) -> Unit raise PowerMonitorError + nocancel {
ignore(native_stop_watching(self.handle))
if completion is Some(reader) {
defer reader.close()
ignore(@async.protect_from_cancel(() => reader.read_all())) catch {
error =>
raise OperationFailed(
operation="with_watching",
detail=error.to_string(),
)
}
}
}
///|
/// Drains every pending event from the queue. Returns an empty array when no
/// event is pending; never raises for an empty queue.
pub fn PowerMonitor::drain_events(
self : PowerMonitor,
) -> Array[PowerMonitorEvent] {
let events : Array[PowerMonitorEvent] = []
let event = Ref(0)
while true {
let status = native_take_event(self.handle, event)
if status == native_status_ok {
events.push(power_monitor_event_from_native_code(event.val))
} else {
break
}
}
events
}
///|
/// Installs the opaque native wakeup supplied by an embedding event loop.
#doc(hidden)
pub fn PowerMonitor::set_event_wakeup(
self : PowerMonitor,
wakeup : @ffi.EventWakeup,
) -> Unit {
native_set_event_wakeup(self.handle, wakeup)
}
///|
fn PowerMonitor::classify_status(
self : PowerMonitor,
status : Int32,
operation : String,
) -> PowerMonitorError {
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~)
_ => OperationFailed(operation~, detail~)
}
}
///|
fn power_source_from_native_code(code : Int32) -> PowerSource {
match code {
_ if code == native_source_ac => AC
_ if code == native_source_battery => Battery
_ if code == native_source_unknown => Unknown
_ => Unknown
}
}
///|
fn power_monitor_event_from_native_code(code : Int32) -> PowerMonitorEvent {
match code {
_ if code == native_event_suspend => Suspend
_ if code == native_event_resume => Resume
_ if code == native_event_on_ac => OnAC
_ if code == native_event_on_battery => OnBattery
_ if code == native_event_lock_screen => LockScreen
_ if code == native_event_unlock_screen => UnlockScreen
_ => Suspend
}
}
///|
fn decode_native_detail(bytes : Bytes) -> String {
if bytes.is_empty() {
return ""
}
@utf8.decode_lossy(bytes)
}
///|
/// Destroys an idle monitor. End its watching scope before destroying it.
pub fn PowerMonitor::destroy(
self : PowerMonitor,
) -> Unit raise PowerMonitorError {
if self.watching {
raise OperationFailed(
operation="destroy",
detail="end the watching scope before destroying the monitor",
)
}
native_destroy(self.handle)
}
///|
/// Removes the embedding loop wakeup and waits for any in-flight notification.
#doc(hidden)
pub fn PowerMonitor::clear_event_wakeup(self : PowerMonitor) -> Unit {
native_clear_event_wakeup(self.handle)
}