use chrono::{DateTime, Utc};
use serde::de::{self, Deserializer};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventKind {
Initial,
TransitionStart,
BusyStart,
BusyEnd,
ValueSample,
Settled,
Manual,
}
impl EventKind {
pub const ALL: [EventKind; 7] = [
EventKind::Initial,
EventKind::TransitionStart,
EventKind::BusyStart,
EventKind::BusyEnd,
EventKind::ValueSample,
EventKind::Settled,
EventKind::Manual,
];
pub fn as_str(self) -> &'static str {
match self {
EventKind::Initial => "initial",
EventKind::TransitionStart => "transition_start",
EventKind::BusyStart => "busy_start",
EventKind::BusyEnd => "busy_end",
EventKind::ValueSample => "value_sample",
EventKind::Settled => "settled",
EventKind::Manual => "manual",
}
}
#[inline]
fn bit(self) -> u8 {
match self {
EventKind::Initial => 1 << 0,
EventKind::TransitionStart => 1 << 1,
EventKind::BusyStart => 1 << 2,
EventKind::BusyEnd => 1 << 3,
EventKind::ValueSample => 1 << 4,
EventKind::Settled => 1 << 5,
EventKind::Manual => 1 << 6,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SaveMask(u8);
impl SaveMask {
pub const NONE: SaveMask = SaveMask(0);
pub fn from_kinds(kinds: &[EventKind]) -> Self {
let mut bits = 0u8;
for k in kinds {
bits |= k.bit();
}
SaveMask(bits)
}
#[inline]
pub fn contains(self, kind: EventKind) -> bool {
self.0 & kind.bit() != 0
}
pub fn with(mut self, kind: EventKind) -> Self {
self.0 |= kind.bit();
self
}
pub fn kinds(self) -> Vec<EventKind> {
EventKind::ALL
.iter()
.copied()
.filter(|k| self.contains(*k))
.collect()
}
}
impl Default for SaveMask {
fn default() -> Self {
SaveMask::from_kinds(&[
EventKind::Initial,
EventKind::Settled,
EventKind::BusyEnd,
EventKind::Manual,
])
}
}
impl Serialize for SaveMask {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.kinds().serialize(s)
}
}
impl<'de> Deserialize<'de> for SaveMask {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let kinds = Vec::<EventKind>::deserialize(d).map_err(de::Error::custom)?;
Ok(SaveMask::from_kinds(&kinds))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ImageFormat {
Png,
Jpeg,
Webp,
}
impl ImageFormat {
pub fn ext(self) -> &'static str {
match self {
ImageFormat::Png => "png",
ImageFormat::Jpeg => "jpg",
ImageFormat::Webp => "webp",
}
}
}
#[derive(Debug, Clone)]
pub struct EncodedImage {
pub bytes: Vec<u8>,
pub format: ImageFormat,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowMeta {
pub title: String,
pub exe: String,
pub class: String,
pub hwnd: isize,
pub rect: [i32; 4],
pub dpi: u32,
pub foreground: bool,
}
pub type BBox = [i32; 4];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangeMeta {
pub changed_tiles: u32,
pub tile_grid: [u16; 2],
pub area_ratio: f32,
pub bboxes: Vec<BBox>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dhash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hamming_to_prev_emit: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegionMeta {
pub label: String,
pub active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BusyMeta {
pub active: bool,
pub regions: Vec<RegionMeta>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimingMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub since_prev_emit_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active_for_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quiescent_for_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureMeta {
pub session_id: String,
pub seq: u64,
pub id: String,
pub kind: EventKind,
pub wall_time: DateTime<Utc>,
pub elapsed_ms: u64,
pub image: Option<String>,
pub window: WindowMeta,
pub change: ChangeMeta,
pub busy: BusyMeta,
pub timing: TimingMeta,
pub coalesced_frames: u32,
pub note: String,
}
#[derive(Debug, Clone)]
pub struct CaptureEvent {
pub meta: CaptureMeta,
pub image: Option<EncodedImage>,
}
impl CaptureEvent {
pub fn kind(&self) -> EventKind {
self.meta.kind
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn event_kind_as_str_round_trips_via_serde() {
for k in EventKind::ALL {
let json = serde_json::to_string(&k).unwrap();
assert_eq!(json, format!("\"{}\"", k.as_str()));
let back: EventKind = serde_json::from_str(&json).unwrap();
assert_eq!(back, k);
}
assert_eq!(EventKind::ALL.len(), 7);
}
#[test]
fn image_format_ext() {
assert_eq!(ImageFormat::Png.ext(), "png");
assert_eq!(ImageFormat::Jpeg.ext(), "jpg");
assert_eq!(ImageFormat::Webp.ext(), "webp");
}
#[test]
fn save_mask_construction_and_membership() {
assert!(!SaveMask::NONE.contains(EventKind::Settled));
let m = SaveMask::from_kinds(&[EventKind::Initial, EventKind::Settled]);
assert!(m.contains(EventKind::Initial));
assert!(m.contains(EventKind::Settled));
assert!(!m.contains(EventKind::ValueSample));
let m2 = SaveMask::NONE.with(EventKind::Manual);
assert!(m2.contains(EventKind::Manual));
assert_eq!(m.kinds(), vec![EventKind::Initial, EventKind::Settled]);
}
#[test]
fn save_mask_default_contains_money_frames() {
let d = SaveMask::default();
for k in [
EventKind::Initial,
EventKind::Settled,
EventKind::BusyEnd,
EventKind::Manual,
] {
assert!(d.contains(k), "default should save {k:?}");
}
assert!(!d.contains(EventKind::ValueSample));
}
#[test]
fn save_mask_serde_is_kind_array() {
let m = SaveMask::from_kinds(&[EventKind::Initial, EventKind::BusyEnd]);
let json = serde_json::to_string(&m).unwrap();
assert_eq!(json, r#"["initial","busy_end"]"#);
let back: SaveMask = serde_json::from_str(&json).unwrap();
assert_eq!(back, m);
}
}