feat(subscriptions): embed latest state

This commit embeds the latest window manager state (as returned from
'komorebic.exe state') as part of the event notifications sent to
subscribers.

Separately, WindowManager.update_focused_workspace has been refactored
to allow a failure to set the foreground window to the default desktop
window on an empty workspace to log a warning instead of returning an
error, allowing messages previously impacted by this to run to
conclusion and be surfaced in the event notifications stream.

resolve #56
This commit is contained in:
LGUG2Z
2021-10-26 18:49:50 -07:00
parent 5d0806a8c9
commit 29a6c39084
5 changed files with 49 additions and 19 deletions

View File

@@ -465,21 +465,21 @@ First, your application must create a named pipe. Once the named pipe has been c
komorebic.exe subscribe <your pipe name>
```
Note that you do not have to incldue the full path of the named pipe, just the name.
Note that you do not have to include the full path of the named pipe, just the name.
If the named pipe exists, `komorebi` will start pushing JSON data of successfully handled events and messages:
```json lines
{"type":"AddSubscriber","content":"test-pipe"}
{"type":"FocusWindow","content":"Up"}
{"type":"FocusChange","content":["SystemForeground",{"hwnd":1443930,"title":"komorebi README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]}
{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":2624200,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]}
{"type":"FocusWindow","content":"Left"}
{"type":"FocusChange","content":["SystemForeground",{"hwnd":2558668,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]}
{"type":"FocusWindow","content":"Right"}
{"type":"FocusChange","content":["SystemForeground",{"hwnd":1443930,"title":"komorebi README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]}
{"type":"FocusWindow","content":"Down"}
{"type":"FocusChange","content":["SystemForeground",{"hwnd":67344,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":894,"right":757,"bottom":821}}]}
{"event":{"type":"AddSubscriber","content":"yasb"},"state":{...}}
{"event":{"type":"FocusWindow","content":"Left"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{...}}
{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{...}}
{"event":{"type":"FocusWindow","content":"Right"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}...}
{"event":{"type":"FocusWindow","content":"Down"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{...}}
{"event":{"type":"FocusWindow","content":"Up"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{...}}
```
You may then filter on the `type` key to listen to the events that you are interested in. For a full list of possible

View File

@@ -22,15 +22,19 @@ use lazy_static::lazy_static;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use serde::Serialize;
use sysinfo::SystemExt;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use which::which;
use komorebi_core::SocketMessage;
use crate::process_command::listen_for_commands;
use crate::process_event::listen_for_events;
use crate::process_movement::listen_for_movements;
use crate::window_manager::State;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
@@ -186,6 +190,19 @@ pub fn load_configuration() -> Result<()> {
Ok(())
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum NotificationEvent {
WindowManager(WindowManagerEvent),
Socket(SocketMessage),
}
#[derive(Debug, Serialize)]
pub struct Notification {
pub event: NotificationEvent,
pub state: State,
}
pub fn notify_subscribers(notification: &str) -> Result<()> {
let mut stale_subscriptions = vec![];
let mut subscriptions = SUBSCRIPTION_PIPES.lock();

View File

@@ -24,6 +24,8 @@ use crate::notify_subscribers;
use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::CUSTOM_FFM;
use crate::FLOAT_IDENTIFIERS;
@@ -221,7 +223,7 @@ impl WindowManager {
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
}
SocketMessage::State => {
let state = serde_json::to_string_pretty(&window_manager::State::from(self))?;
let state = serde_json::to_string_pretty(&window_manager::State::from(&*self))?;
let mut socket =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
socket.push("komorebic.sock");
@@ -476,7 +478,10 @@ impl WindowManager {
}
self.process_command(message.clone())?;
notify_subscribers(&serde_json::to_string(&message)?)?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: (&*self).into(),
})?)?;
}
Ok(())

View File

@@ -15,6 +15,8 @@ use crate::notify_subscribers;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::HIDDEN_HWNDS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
@@ -317,7 +319,10 @@ impl WindowManager {
.open(hwnd_json)?;
serde_json::to_writer_pretty(&file, &known_hwnds)?;
notify_subscribers(&serde_json::to_string(&event)?)?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::WindowManager(*event),
state: (&*self).into(),
})?)?;
tracing::info!("processed: {}", event.window().to_string());
Ok(())

View File

@@ -70,9 +70,8 @@ pub struct State {
pub border_overflow_identifiers: Vec<String>,
}
#[allow(clippy::fallible_impl_from)]
impl From<&mut WindowManager> for State {
fn from(wm: &mut WindowManager) -> Self {
impl From<&WindowManager> for State {
fn from(wm: &WindowManager) -> Self {
Self {
monitors: wm.monitors.clone(),
is_paused: wm.is_paused,
@@ -567,8 +566,12 @@ impl WindowManager {
// Calling this directly instead of the window.focus() wrapper because trying to
// attach to the thread of the desktop window always seems to result in "Access is
// denied (os error 5)"
WindowsApi::set_foreground_window(desktop_window.hwnd())
.map_err(|error| anyhow!("{} {}:{}", error, file!(), line!()))?;
match WindowsApi::set_foreground_window(desktop_window.hwnd()) {
Ok(_) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
}
}
}
}