diff --git a/Cargo.lock b/Cargo.lock index 44059f74..8a80bc32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + [[package]] name = "either" version = "1.6.1" @@ -462,6 +468,7 @@ dependencies = [ "nanoid", "parking_lot", "paste", + "schemars", "serde", "serde_json", "strum", @@ -482,6 +489,7 @@ version = "0.1.8" dependencies = [ "clap", "color-eyre", + "schemars", "serde", "serde_json", "serde_yaml", @@ -1029,6 +1037,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1055,6 +1087,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.78" diff --git a/README.md b/README.md index 1e5eae73..05072397 100644 --- a/README.md +++ b/README.md @@ -544,3 +544,10 @@ in `komorebi-core`. An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Python by [@denBot](https://github.com/denBot) can be found [here](https://gist.github.com/denBot/4136279812f87819f86d99eba77c1ee0). + +### Subscription Event Notification Schema + +A [JSON Schema](https://json-schema.org/) of the event notifications emitted to subscribers can be generated with +the `komorebic notification-schema` command. The output of this command can be redirected to the clipboard or a file, +which can be used with services such as [Quicktype](https://app.quicktype.io/) to generate type definitions in different +programming languages. diff --git a/komorebi-core/Cargo.toml b/komorebi-core/Cargo.toml index 1d4d46d2..3ccf33cb 100644 --- a/komorebi-core/Cargo.toml +++ b/komorebi-core/Cargo.toml @@ -12,6 +12,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml = "0.8" strum = { version = "0.23", features = ["derive"] } +schemars = "0.8" [dependencies.windows] version = "0.30" diff --git a/komorebi-core/src/arrangement.rs b/komorebi-core/src/arrangement.rs index 50c50315..0276ae0d 100644 --- a/komorebi-core/src/arrangement.rs +++ b/komorebi-core/src/arrangement.rs @@ -1,6 +1,7 @@ use std::num::NonZeroUsize; use clap::ArgEnum; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use strum::Display; @@ -341,7 +342,7 @@ impl Arrangement for CustomLayout { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum Axis { Horizontal, diff --git a/komorebi-core/src/custom_layout.rs b/komorebi-core/src/custom_layout.rs index 1d620ca9..0c7eeee4 100644 --- a/komorebi-core/src/custom_layout.rs +++ b/komorebi-core/src/custom_layout.rs @@ -7,12 +7,13 @@ use std::path::PathBuf; use color_eyre::eyre::anyhow; use color_eyre::Result; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use crate::Rect; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct CustomLayout(Vec); impl Deref for CustomLayout { @@ -251,7 +252,7 @@ impl CustomLayout { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] #[serde(tag = "column", content = "configuration")] pub enum Column { Primary(Option), @@ -259,18 +260,18 @@ pub enum Column { Tertiary(ColumnSplit), } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] pub enum ColumnWidth { WidthPercentage(usize), } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] pub enum ColumnSplit { Horizontal, Vertical, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] pub enum ColumnSplitWithCapacity { Horizontal(usize), Vertical(usize), diff --git a/komorebi-core/src/cycle_direction.rs b/komorebi-core/src/cycle_direction.rs index 2882c586..08409292 100644 --- a/komorebi-core/src/cycle_direction.rs +++ b/komorebi-core/src/cycle_direction.rs @@ -1,12 +1,13 @@ use std::num::NonZeroUsize; use clap::ArgEnum; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use strum::Display; use strum::EnumString; -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum CycleDirection { Previous, diff --git a/komorebi-core/src/default_layout.rs b/komorebi-core/src/default_layout.rs index b792b9a5..aff2dc05 100644 --- a/komorebi-core/src/default_layout.rs +++ b/komorebi-core/src/default_layout.rs @@ -1,4 +1,5 @@ use clap::ArgEnum; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use strum::Display; @@ -8,7 +9,7 @@ use crate::OperationDirection; use crate::Rect; use crate::Sizing; -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum DefaultLayout { BSP, diff --git a/komorebi-core/src/layout.rs b/komorebi-core/src/layout.rs index 3764870a..023d41c8 100644 --- a/komorebi-core/src/layout.rs +++ b/komorebi-core/src/layout.rs @@ -1,3 +1,4 @@ +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -6,7 +7,7 @@ use crate::CustomLayout; use crate::DefaultLayout; use crate::Direction; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub enum Layout { Default(DefaultLayout), Custom(CustomLayout), diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 03badd28..dc38fea5 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use clap::ArgEnum; use color_eyre::Result; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use strum::Display; @@ -30,7 +31,7 @@ pub mod layout; pub mod operation_direction; pub mod rect; -#[derive(Clone, Debug, Serialize, Deserialize, Display)] +#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema)] #[serde(tag = "type", content = "content")] pub enum SocketMessage { // Window / Container Commands @@ -103,6 +104,7 @@ pub enum SocketMessage { ToggleMouseFollowsFocus, AddSubscriber(String), RemoveSubscriber(String), + NotificationSchema, } impl SocketMessage { @@ -119,7 +121,7 @@ impl FromStr for SocketMessage { } } -#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum StateQuery { FocusedMonitorIndex, @@ -128,7 +130,7 @@ pub enum StateQuery { FocusedWindowIndex, } -#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum ApplicationIdentifier { Exe, @@ -136,28 +138,28 @@ pub enum ApplicationIdentifier { Title, } -#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum FocusFollowsMouseImplementation { Komorebi, Windows, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum WindowContainerBehaviour { Create, Append, } -#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum HidingBehaviour { Hide, Minimize, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum Sizing { Increase, diff --git a/komorebi-core/src/operation_direction.rs b/komorebi-core/src/operation_direction.rs index 8b7d7391..ce51aa14 100644 --- a/komorebi-core/src/operation_direction.rs +++ b/komorebi-core/src/operation_direction.rs @@ -1,6 +1,7 @@ use std::num::NonZeroUsize; use clap::ArgEnum; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use strum::Display; @@ -9,7 +10,7 @@ use strum::EnumString; use crate::direction::Direction; use crate::Axis; -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum OperationDirection { Left, diff --git a/komorebi-core/src/rect.rs b/komorebi-core/src/rect.rs index efe8a8c9..fc5249a1 100644 --- a/komorebi-core/src/rect.rs +++ b/komorebi-core/src/rect.rs @@ -1,8 +1,9 @@ +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use windows::Win32::Foundation::RECT; -#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)] pub struct Rect { pub left: i32, pub top: i32, diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index 77eb80e4..78cbd310 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -38,6 +38,7 @@ which = "4" winput = "0.2" miow = "0.4" winreg = "0.10" +schemars = "0.8" [dependencies.windows] version = "0.30" diff --git a/komorebi/src/container.rs b/komorebi/src/container.rs index 2a2578cd..448c56da 100644 --- a/komorebi/src/container.rs +++ b/komorebi/src/container.rs @@ -2,12 +2,13 @@ use std::collections::VecDeque; use getset::Getters; use nanoid::nanoid; +use schemars::JsonSchema; use serde::Serialize; use crate::ring::Ring; use crate::window::Window; -#[derive(Debug, Clone, Serialize, Getters)] +#[derive(Debug, Clone, Serialize, Getters, JsonSchema)] pub struct Container { #[serde(skip_serializing)] #[getset(get = "pub")] diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index c4678a03..10d0ab1a 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -24,6 +24,7 @@ use lazy_static::lazy_static; #[cfg(feature = "deadlock_detection")] use parking_lot::deadlock; use parking_lot::Mutex; +use schemars::JsonSchema; use serde::Serialize; use sysinfo::Process; use sysinfo::ProcessExt; @@ -267,14 +268,14 @@ pub fn current_virtual_desktop() -> Option> { current } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, JsonSchema)] #[serde(untagged)] pub enum NotificationEvent { WindowManager(WindowManagerEvent), Socket(SocketMessage), } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, JsonSchema)] pub struct Notification { pub event: NotificationEvent, pub state: State, diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index 298ff8c2..3c0e9f01 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -7,6 +7,7 @@ use getset::CopyGetters; use getset::Getters; use getset::MutGetters; use getset::Setters; +use schemars::JsonSchema; use serde::Serialize; use komorebi_core::Rect; @@ -15,7 +16,7 @@ use crate::container::Container; use crate::ring::Ring; use crate::workspace::Workspace; -#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)] +#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)] pub struct Monitor { #[getset(get_copy = "pub", set = "pub")] id: isize, diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 46e22124..b8f9f743 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -13,6 +13,7 @@ use color_eyre::eyre::anyhow; use color_eyre::Result; use miow::pipe::connect; use parking_lot::Mutex; +use schemars::schema_for; use uds_windows::UnixStream; use komorebi_core::ApplicationIdentifier; @@ -634,6 +635,16 @@ impl WindowManager { let mut hiding_behaviour = HIDING_BEHAVIOUR.lock(); *hiding_behaviour = behaviour; } + SocketMessage::NotificationSchema => { + let notification = schema_for!(Notification); + let schema = serde_json::to_string_pretty(¬ification)?; + let mut socket = HOME_DIR.clone(); + socket.push("komorebic.sock"); + let socket = socket.as_path(); + + let mut stream = UnixStream::connect(&socket)?; + stream.write_all(schema.as_bytes())?; + } }; tracing::info!("processed"); diff --git a/komorebi/src/ring.rs b/komorebi/src/ring.rs index 446759c0..e0c7cb4c 100644 --- a/komorebi/src/ring.rs +++ b/komorebi/src/ring.rs @@ -1,8 +1,9 @@ use std::collections::VecDeque; +use schemars::JsonSchema; use serde::Serialize; -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, JsonSchema)] pub struct Ring { elements: VecDeque, focused: usize, diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index f56bf321..4df86a68 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -4,6 +4,7 @@ use std::fmt::Formatter; use color_eyre::eyre::anyhow; use color_eyre::Result; +use schemars::JsonSchema; use serde::ser::Error; use serde::ser::SerializeStruct; use serde::Serialize; @@ -25,7 +26,7 @@ use crate::LAYERED_EXE_WHITELIST; use crate::MANAGE_IDENTIFIERS; use crate::WSL2_UI_PROCESSES; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, JsonSchema)] pub struct Window { pub(crate) hwnd: isize, } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index d7b8567c..b61a1b2c 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -11,6 +11,7 @@ use crossbeam_channel::Receiver; use hotwatch::notify::DebouncedEvent; use hotwatch::Hotwatch; use parking_lot::Mutex; +use schemars::JsonSchema; use serde::Serialize; use uds_windows::UnixListener; @@ -62,7 +63,7 @@ pub struct WindowManager { pub pending_move_op: Option<(usize, usize, usize)>, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, JsonSchema)] pub struct State { pub monitors: Ring, pub is_paused: bool, diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index 5f70c1bd..c6061361 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -1,13 +1,14 @@ use std::fmt::Display; use std::fmt::Formatter; +use schemars::JsonSchema; use serde::Serialize; use crate::window::Window; use crate::winevent::WinEvent; use crate::OBJECT_NAME_CHANGE_ON_LAUNCH; -#[derive(Debug, Copy, Clone, Serialize)] +#[derive(Debug, Copy, Clone, Serialize, JsonSchema)] #[serde(tag = "type", content = "content")] pub enum WindowManagerEvent { Destroy(WinEvent, Window), diff --git a/komorebi/src/winevent.rs b/komorebi/src/winevent.rs index d7d6ac02..650215ac 100644 --- a/komorebi/src/winevent.rs +++ b/komorebi/src/winevent.rs @@ -1,3 +1,4 @@ +use schemars::JsonSchema; use serde::Serialize; use strum::Display; use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END; @@ -85,7 +86,7 @@ use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START; use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END; use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START; -#[derive(Clone, Copy, PartialEq, Debug, Serialize, Display)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Display, JsonSchema)] #[repr(u32)] #[allow(dead_code)] pub enum WinEvent { diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 7be4aecc..7b674b29 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -7,6 +7,7 @@ use getset::CopyGetters; use getset::Getters; use getset::MutGetters; use getset::Setters; +use schemars::JsonSchema; use serde::Serialize; use komorebi_core::Axis; @@ -21,7 +22,7 @@ use crate::ring::Ring; use crate::window::Window; use crate::windows_api::WindowsApi; -#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)] +#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)] pub struct Workspace { #[getset(set = "pub")] name: Option, diff --git a/komorebic.lib.sample.ahk b/komorebic.lib.sample.ahk index d3620db7..dd2f582b 100644 --- a/komorebic.lib.sample.ahk +++ b/komorebic.lib.sample.ahk @@ -278,4 +278,8 @@ ToggleMouseFollowsFocus() { AhkLibrary() { Run, komorebic.exe ahk-library, , Hide +} + +NotificationSchema() { + Run, komorebic.exe notification-schema, , Hide } \ No newline at end of file diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 06f6ddf5..11e84eff 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -574,6 +574,8 @@ enum SubCommand { ToggleMouseFollowsFocus, /// Generate a library of AutoHotKey helper functions AhkLibrary, + /// Generate a JSON Schema of subscription notifications + NotificationSchema, } pub fn send_message(bytes: &[u8]) -> Result<()> { @@ -1016,6 +1018,40 @@ fn main() -> Result<()> { SubCommand::WindowHidingBehaviour(arg) => { send_message(&*SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour).as_bytes()?)?; } + SubCommand::NotificationSchema => { + let home = HOME_DIR.clone(); + let mut socket = home; + socket.push("komorebic.sock"); + let socket = socket.as_path(); + + match std::fs::remove_file(&socket) { + Ok(_) => {} + Err(error) => match error.kind() { + // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ + ErrorKind::NotFound => {} + _ => { + return Err(error.into()); + } + }, + }; + + send_message(&*SocketMessage::NotificationSchema.as_bytes()?)?; + + let listener = UnixListener::bind(&socket)?; + match listener.accept() { + Ok(incoming) => { + let stream = BufReader::new(incoming.0); + for line in stream.lines() { + println!("{}", line?); + } + + return Ok(()); + } + Err(error) => { + panic!("{}", error); + } + } + } } Ok(())