mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-14 14:23:36 +01:00
Compare commits
1 Commits
v0.1.28
...
feature/ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
506600d689 |
2047
Cargo.lock
generated
2047
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,5 +4,6 @@ members = [
|
||||
"derive-ahk",
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
"komorebi-bar",
|
||||
"komorebic"
|
||||
]
|
||||
|
||||
31
komorebi-bar/Cargo.toml
Normal file
31
komorebi-bar/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "komorebi-bar"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
komorebi = { path = "../komorebi" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
as-any = "0.3"
|
||||
chrono = "0.4"
|
||||
color-eyre = "0.6"
|
||||
eframe = "0.17"
|
||||
egui = "0.17"
|
||||
lazy_static = "1.4"
|
||||
miow = "0.4"
|
||||
schemafy = "0.6"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
parking_lot = "0.12"
|
||||
local-ip-address = "0.4"
|
||||
clipboard-win = "4.4"
|
||||
sysinfo = "0.23"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.35"
|
||||
features = [
|
||||
"Win32_Graphics_Gdi",
|
||||
]
|
||||
125
komorebi-bar/src/bar.rs
Normal file
125
komorebi-bar/src/bar.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::date::Date;
|
||||
use crate::ram::Ram;
|
||||
use crate::time::Time;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widget::Output;
|
||||
use crate::widget::Widget;
|
||||
use crate::IpAddress;
|
||||
use crate::Storage;
|
||||
use crate::Workspaces;
|
||||
use clipboard_win::set_clipboard_string;
|
||||
use color_eyre::owo_colors::OwoColorize;
|
||||
use eframe::epi::App;
|
||||
use eframe::epi::Frame;
|
||||
use egui::style::Margin;
|
||||
use egui::CentralPanel;
|
||||
use egui::Color32;
|
||||
use egui::Context;
|
||||
use egui::Direction;
|
||||
use egui::Layout;
|
||||
use egui::Rounding;
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub struct Bar {
|
||||
pub background_rgb: Color32,
|
||||
pub text_rgb: Color32,
|
||||
pub workspaces: Workspaces,
|
||||
pub time: Time,
|
||||
pub date: Date,
|
||||
pub ip_address: IpAddress,
|
||||
pub memory: Ram,
|
||||
pub storage: Storage,
|
||||
}
|
||||
|
||||
impl App for Bar {
|
||||
fn update(&mut self, ctx: &Context, frame: &Frame) {
|
||||
let custom_frame = egui::Frame {
|
||||
margin: Margin::symmetric(8.0, 8.0),
|
||||
rounding: Rounding::none(),
|
||||
fill: self.background_rgb,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
CentralPanel::default().frame(custom_frame).show(ctx, |ui| {
|
||||
ui.horizontal(|horizontal| {
|
||||
horizontal.style_mut().visuals.override_text_color = Option::from(self.text_rgb);
|
||||
|
||||
horizontal.with_layout(Layout::left_to_right(), |ltr| {
|
||||
for (i, workspace) in self.workspaces.output().iter().enumerate() {
|
||||
if workspace == "komorebi offline" {
|
||||
ltr.label(workspace);
|
||||
} else {
|
||||
ctx.request_repaint();
|
||||
if ltr
|
||||
.selectable_label(*self.workspaces.selected.lock() == i, workspace)
|
||||
.clicked()
|
||||
{
|
||||
let mut selected = self.workspaces.selected.lock();
|
||||
*selected = i;
|
||||
|
||||
if let Err(error) = Workspaces::focus(i) {
|
||||
eprintln!("{}", error)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
horizontal.with_layout(Layout::right_to_left(), |rtl| {
|
||||
for time in self.time.output() {
|
||||
ctx.request_repaint();
|
||||
if rtl.button(format!("🕐 {}", time)).clicked() {
|
||||
self.time.format.toggle()
|
||||
};
|
||||
}
|
||||
|
||||
for date in self.date.output() {
|
||||
if rtl.button(format!("📅 {}", date)).clicked() {
|
||||
self.date.format.next()
|
||||
};
|
||||
}
|
||||
|
||||
for memory in self.memory.output() {
|
||||
if rtl.button(format!("🐏 {}", memory)).clicked() {
|
||||
if let Err(error) =
|
||||
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).output()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for disk in self.storage.output() {
|
||||
if rtl.button(format!("🖴 {}", disk)).clicked() {
|
||||
if let Err(error) = Command::new("cmd.exe")
|
||||
.args([
|
||||
"/C",
|
||||
"explorer.exe",
|
||||
disk.split(' ').collect::<Vec<&str>>()[0],
|
||||
])
|
||||
.output()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for ip in self.ip_address.output() {
|
||||
if rtl.button(format!("🌐 {}", ip)).clicked() {
|
||||
if let Err(error) =
|
||||
Command::new("cmd.exe").args(["/C", "ncpa.cpl"]).output()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"komorebi-bar"
|
||||
}
|
||||
}
|
||||
46
komorebi-bar/src/date.rs
Normal file
46
komorebi-bar/src/date.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::widget::BarWidget;
|
||||
|
||||
pub enum DateFormat {
|
||||
MonthDateYear,
|
||||
YearMonthDate,
|
||||
DateMonthYear,
|
||||
DayDateMonthYear,
|
||||
}
|
||||
|
||||
impl DateFormat {
|
||||
pub fn next(&mut self) {
|
||||
match self {
|
||||
DateFormat::MonthDateYear => *self = Self::YearMonthDate,
|
||||
DateFormat::YearMonthDate => *self = Self::DateMonthYear,
|
||||
DateFormat::DateMonthYear => *self = Self::DayDateMonthYear,
|
||||
DateFormat::DayDateMonthYear => *self = Self::MonthDateYear,
|
||||
};
|
||||
}
|
||||
|
||||
fn fmt_string(&self) -> String {
|
||||
match self {
|
||||
DateFormat::MonthDateYear => String::from("%D"),
|
||||
DateFormat::YearMonthDate => String::from("%F"),
|
||||
DateFormat::DateMonthYear => String::from("%v"),
|
||||
DateFormat::DayDateMonthYear => String::from("%A %e %B %Y"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Date {
|
||||
pub format: DateFormat,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
pub fn init(format: DateFormat) -> Self {
|
||||
Self { format }
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Date {
|
||||
fn output(&mut self) -> Vec<String> {
|
||||
vec![chrono::Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()]
|
||||
}
|
||||
}
|
||||
27
komorebi-bar/src/ip_address.rs
Normal file
27
komorebi-bar/src/ip_address.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::widget::BarWidget;
|
||||
use local_ip_address::find_ifa;
|
||||
use local_ip_address::local_ip;
|
||||
|
||||
pub struct IpAddress {
|
||||
pub interface: String,
|
||||
}
|
||||
|
||||
impl IpAddress {
|
||||
pub fn init(interface: String) -> Self {
|
||||
IpAddress { interface }
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for IpAddress {
|
||||
fn output(&mut self) -> Vec<String> {
|
||||
if let Ok(interfaces) = local_ip_address::list_afinet_netifas() {
|
||||
if let Some((interface, ip_address)) =
|
||||
local_ip_address::find_ifa(interfaces, &self.interface)
|
||||
{
|
||||
return vec![format!("{}: {}", interface, ip_address)];
|
||||
}
|
||||
}
|
||||
|
||||
vec![format!("{}: disconnected", self.interface)]
|
||||
}
|
||||
}
|
||||
80
komorebi-bar/src/main.rs
Normal file
80
komorebi-bar/src/main.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
mod bar;
|
||||
mod date;
|
||||
mod ip_address;
|
||||
mod ram;
|
||||
mod storage;
|
||||
mod time;
|
||||
mod widget;
|
||||
mod workspaces;
|
||||
|
||||
use crate::ip_address::IpAddress;
|
||||
use crate::ram::Ram;
|
||||
use crate::storage::Storage;
|
||||
use bar::Bar;
|
||||
use color_eyre::Result;
|
||||
use date::Date;
|
||||
use date::DateFormat;
|
||||
use eframe::run_native;
|
||||
use eframe::NativeOptions;
|
||||
use egui::Color32;
|
||||
use egui::Pos2;
|
||||
use egui::Vec2;
|
||||
use komorebi::WindowsApi;
|
||||
use time::Time;
|
||||
use time::TimeFormat;
|
||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use workspaces::Workspaces;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let workspaces = Workspaces::init(0)?;
|
||||
let time = Time::init(TimeFormat::TwentyFourHour);
|
||||
let date = Date::init(DateFormat::DayDateMonthYear);
|
||||
let ip_address = IpAddress::init(String::from("Ethernet"));
|
||||
|
||||
let app = Bar {
|
||||
background_rgb: Color32::from_rgb(255, 0, 0),
|
||||
text_rgb: Color32::from_rgb(255, 255, 255),
|
||||
workspaces,
|
||||
time,
|
||||
date,
|
||||
ip_address,
|
||||
memory: Ram,
|
||||
storage: Storage,
|
||||
};
|
||||
|
||||
let mut win_option = NativeOptions {
|
||||
decorated: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// let hmonitors = WindowsApi::valid_hmonitors()?;
|
||||
// for hmonitor in hmonitors {
|
||||
// let info = WindowsApi::monitor_info(hmonitor)?;
|
||||
// }
|
||||
|
||||
let info = WindowsApi::monitor_info_w(HMONITOR(65537))?;
|
||||
|
||||
let offset = Offsets {
|
||||
vertical: 10.0,
|
||||
horizontal: 200.0,
|
||||
};
|
||||
|
||||
win_option.initial_window_pos = Option::from(Pos2::new(
|
||||
info.rcWork.left as f32 + offset.horizontal,
|
||||
info.rcWork.top as f32 + offset.vertical * 2.0,
|
||||
));
|
||||
|
||||
win_option.initial_window_size = Option::from(Vec2::new(
|
||||
info.rcWork.right as f32 - (offset.horizontal * 2.0),
|
||||
info.rcWork.top as f32 - offset.vertical,
|
||||
));
|
||||
|
||||
win_option.always_on_top = true;
|
||||
|
||||
run_native(Box::new(app), win_option);
|
||||
}
|
||||
|
||||
struct Offsets {
|
||||
vertical: f32,
|
||||
horizontal: f32,
|
||||
}
|
||||
15
komorebi-bar/src/ram.rs
Normal file
15
komorebi-bar/src/ram.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use crate::widget::BarWidget;
|
||||
use sysinfo::RefreshKind;
|
||||
use sysinfo::System;
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
pub struct Ram;
|
||||
|
||||
impl BarWidget for Ram {
|
||||
fn output(&mut self) -> Vec<String> {
|
||||
let sys = System::new_with_specifics(RefreshKind::new().with_memory());
|
||||
let used = sys.used_memory();
|
||||
let total = sys.total_memory();
|
||||
vec![format!("RAM: {}%", (used * 100) / total)]
|
||||
}
|
||||
}
|
||||
35
komorebi-bar/src/storage.rs
Normal file
35
komorebi-bar/src/storage.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widget::Output;
|
||||
use crate::widget::Widget;
|
||||
use color_eyre::Result;
|
||||
use sysinfo::DiskExt;
|
||||
use sysinfo::RefreshKind;
|
||||
use sysinfo::System;
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
pub struct Storage;
|
||||
|
||||
impl BarWidget for Storage {
|
||||
fn output(&mut self) -> Vec<String> {
|
||||
let sys = System::new_with_specifics(RefreshKind::new().with_disks_list());
|
||||
|
||||
let mut disks = vec![];
|
||||
|
||||
for disk in sys.disks() {
|
||||
let mount = disk.mount_point();
|
||||
let total = disk.total_space();
|
||||
let available = disk.available_space();
|
||||
let used = total - available;
|
||||
|
||||
disks.push(format!(
|
||||
"{} {}%",
|
||||
mount.to_string_lossy(),
|
||||
(used * 100) / total
|
||||
))
|
||||
}
|
||||
|
||||
disks.reverse();
|
||||
|
||||
disks
|
||||
}
|
||||
}
|
||||
40
komorebi-bar/src/time.rs
Normal file
40
komorebi-bar/src/time.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::widget::BarWidget;
|
||||
|
||||
pub enum TimeFormat {
|
||||
TwelveHour,
|
||||
TwentyFourHour,
|
||||
}
|
||||
|
||||
impl TimeFormat {
|
||||
pub fn toggle(&mut self) {
|
||||
match self {
|
||||
TimeFormat::TwelveHour => *self = TimeFormat::TwentyFourHour,
|
||||
TimeFormat::TwentyFourHour => *self = TimeFormat::TwelveHour,
|
||||
};
|
||||
}
|
||||
|
||||
fn fmt_string(&self) -> String {
|
||||
match self {
|
||||
TimeFormat::TwelveHour => String::from("%l:%M:%S %p"),
|
||||
TimeFormat::TwentyFourHour => String::from("%T"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Time {
|
||||
pub format: TimeFormat,
|
||||
}
|
||||
|
||||
impl Time {
|
||||
pub fn init(format: TimeFormat) -> Self {
|
||||
Self { format }
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Time {
|
||||
fn output(&mut self) -> Vec<String> {
|
||||
vec![chrono::Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()]
|
||||
}
|
||||
}
|
||||
25
komorebi-bar/src/widget.rs
Normal file
25
komorebi-bar/src/widget.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use as_any::AsAny;
|
||||
use color_eyre::Result;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Output {
|
||||
SingleBox(String),
|
||||
MultiBox(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum RepaintStrategy {
|
||||
Default,
|
||||
Constant,
|
||||
}
|
||||
|
||||
pub trait Widget: AsAny {
|
||||
fn output(&mut self) -> Result<Output>;
|
||||
fn repaint_strategy(&self) -> RepaintStrategy {
|
||||
RepaintStrategy::Default
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BarWidget {
|
||||
fn output(&mut self) -> Vec<String>;
|
||||
}
|
||||
179
komorebi-bar/src/workspaces.rs
Normal file
179
komorebi-bar/src/workspaces.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use crate::widget::BarWidget;
|
||||
use color_eyre::Report;
|
||||
use color_eyre::Result;
|
||||
use komorebi::Notification;
|
||||
use komorebi::State;
|
||||
use miow::pipe::NamedPipe;
|
||||
use parking_lot::Mutex;
|
||||
use std::io::Read;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Workspaces {
|
||||
pub enabled: bool,
|
||||
pub monitor_idx: usize,
|
||||
pub connected: Arc<Mutex<bool>>,
|
||||
pub pipe: Arc<Mutex<NamedPipe>>,
|
||||
pub state: Arc<Mutex<State>>,
|
||||
pub selected: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
impl BarWidget for Workspaces {
|
||||
fn output(&mut self) -> Vec<String> {
|
||||
let state = self.state.lock();
|
||||
let mut workspaces = vec![];
|
||||
|
||||
if let Some(primary_monitor) = state.monitors.elements().get(self.monitor_idx) {
|
||||
for (i, workspace) in primary_monitor.workspaces().iter().enumerate() {
|
||||
workspaces.push(if let Some(name) = workspace.name() {
|
||||
name.clone()
|
||||
} else {
|
||||
format!("{}", i + 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if workspaces.is_empty() || !*self.connected.lock() {
|
||||
vec!["komorebi offline".to_string()]
|
||||
} else {
|
||||
workspaces
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PIPE: &str = r#"\\.\pipe\"#;
|
||||
|
||||
impl Workspaces {
|
||||
pub fn focus(index: usize) -> Result<()> {
|
||||
Ok(Command::new("cmd.exe")
|
||||
.args([
|
||||
"/C",
|
||||
"komorebic.exe",
|
||||
"focus-workspace",
|
||||
&format!("{}", index),
|
||||
])
|
||||
.output()
|
||||
.map(|_| ())?)
|
||||
}
|
||||
|
||||
pub fn init(monitor_idx: usize) -> Result<Self> {
|
||||
let name = format!("bar-{}", monitor_idx);
|
||||
let pipe = format!("{}\\{}", PIPE, name);
|
||||
|
||||
let mut named_pipe = NamedPipe::new(pipe)?;
|
||||
|
||||
let mut output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", &name])
|
||||
.output()?;
|
||||
|
||||
while !output.status.success() {
|
||||
println!(
|
||||
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
|
||||
output.status.code()
|
||||
);
|
||||
|
||||
sleep(Duration::from_secs(5));
|
||||
|
||||
output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", &name])
|
||||
.output()?;
|
||||
}
|
||||
|
||||
named_pipe.connect()?;
|
||||
|
||||
let mut buf = vec![0; 4096];
|
||||
|
||||
let mut bytes_read = named_pipe.read(&mut buf)?;
|
||||
let mut data = String::from_utf8(buf[0..bytes_read].to_vec())?;
|
||||
while data == "\n" {
|
||||
bytes_read = named_pipe.read(&mut buf)?;
|
||||
data = String::from_utf8(buf[0..bytes_read].to_vec())?;
|
||||
}
|
||||
|
||||
let notification: Notification = serde_json::from_str(&data)?;
|
||||
|
||||
let mut workspaces = Self {
|
||||
enabled: true,
|
||||
monitor_idx,
|
||||
connected: Arc::new(Mutex::new(true)),
|
||||
pipe: Arc::new(Mutex::new(named_pipe)),
|
||||
state: Arc::new(Mutex::new(notification.state)),
|
||||
selected: Arc::new(Mutex::new(0)),
|
||||
};
|
||||
|
||||
workspaces.listen()?;
|
||||
Ok(workspaces)
|
||||
}
|
||||
|
||||
pub fn listen(&mut self) -> Result<()> {
|
||||
let state = self.state.clone();
|
||||
let pipe = self.pipe.clone();
|
||||
let connected = self.connected.clone();
|
||||
let selected = self.selected.clone();
|
||||
|
||||
thread::spawn(move || -> Result<()> {
|
||||
let mut buf = vec![0; 4096];
|
||||
loop {
|
||||
let mut named_pipe = pipe.lock();
|
||||
match (*named_pipe).read(&mut buf) {
|
||||
Ok(bytes_read) => {
|
||||
let data = String::from_utf8(buf[0..bytes_read].to_vec())?;
|
||||
if data == "\n" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let notification: Notification = serde_json::from_str(&data)?;
|
||||
|
||||
let mut sl = selected.lock();
|
||||
*sl = notification.state.monitors.elements()[0].focused_workspace_idx();
|
||||
|
||||
let mut st = state.lock();
|
||||
*st = notification.state;
|
||||
}
|
||||
Err(error) => {
|
||||
// Broken pipe
|
||||
if error.raw_os_error().unwrap() == 109 {
|
||||
{
|
||||
let mut cn = connected.lock();
|
||||
*cn = false;
|
||||
}
|
||||
|
||||
named_pipe.disconnect()?;
|
||||
|
||||
let mut output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", "bar"])
|
||||
.output()?;
|
||||
|
||||
while !output.status.success() {
|
||||
println!(
|
||||
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
|
||||
output.status.code()
|
||||
);
|
||||
|
||||
sleep(Duration::from_secs(5));
|
||||
|
||||
output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", "bar"])
|
||||
.output()?;
|
||||
}
|
||||
|
||||
named_pipe.connect()?;
|
||||
|
||||
{
|
||||
let mut cn = connected.lock();
|
||||
*cn = true;
|
||||
}
|
||||
} else {
|
||||
return Err(Report::from(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,14 @@ repository = "https://github.com/LGUG2Z/komorebi"
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "komorebi"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "komorebi"
|
||||
path = "src/main.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -3,14 +3,15 @@ use std::collections::VecDeque;
|
||||
use getset::Getters;
|
||||
use nanoid::nanoid;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Getters, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
|
||||
pub struct Container {
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(skip)]
|
||||
#[getset(get = "pub")]
|
||||
id: String,
|
||||
windows: Ring<Window>,
|
||||
|
||||
236
komorebi/src/lib.rs
Normal file
236
komorebi/src/lib.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use which::which;
|
||||
use winreg::enums::HKEY_CURRENT_USER;
|
||||
use winreg::RegKey;
|
||||
|
||||
use komorebi_core::HidingBehaviour;
|
||||
use komorebi_core::SocketMessage;
|
||||
|
||||
#[macro_use]
|
||||
mod ring;
|
||||
|
||||
mod container;
|
||||
mod monitor;
|
||||
mod process_command;
|
||||
mod process_event;
|
||||
mod process_movement;
|
||||
mod set_window_position;
|
||||
mod styles;
|
||||
mod window;
|
||||
mod window_manager;
|
||||
mod window_manager_event;
|
||||
mod windows_api;
|
||||
mod windows_callbacks;
|
||||
mod winevent;
|
||||
mod winevent_listener;
|
||||
mod workspace;
|
||||
|
||||
pub use process_command::listen_for_commands;
|
||||
pub use process_event::listen_for_events;
|
||||
pub use process_movement::listen_for_movements;
|
||||
pub use window_manager::State;
|
||||
pub use window_manager::WindowManager;
|
||||
pub use window_manager_event::WindowManagerEvent;
|
||||
pub use windows_api::WindowsApi;
|
||||
pub use winevent_listener::WinEventListener;
|
||||
|
||||
lazy_static! {
|
||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
|
||||
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec![
|
||||
"explorer.exe".to_string(),
|
||||
"firefox.exe".to_string(),
|
||||
"chrome.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
"ApplicationFrameHost.exe".to_string(),
|
||||
"steam.exe".to_string(),
|
||||
]));
|
||||
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"firefox.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
]));
|
||||
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
// mstsc.exe creates these on Windows 11 when a WSL process is launched
|
||||
// https://github.com/LGUG2Z/komorebi/issues/74
|
||||
"OPContainerClass".to_string(),
|
||||
"IHWindowClass".to_string()
|
||||
]));
|
||||
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"X410.exe".to_string(),
|
||||
"mstsc.exe".to_string(),
|
||||
"vcxsrv.exe".to_string(),
|
||||
]));
|
||||
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
|
||||
Arc::new(Mutex::new(HidingBehaviour::Minimize));
|
||||
pub static ref HOME_DIR: PathBuf = {
|
||||
if let Ok(home_path) = std::env::var("KOMOREBI_CONFIG_HOME") {
|
||||
let home = PathBuf::from(&home_path);
|
||||
|
||||
if home.as_path().is_dir() {
|
||||
home
|
||||
} else {
|
||||
panic!(
|
||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
|
||||
home_path
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dirs::home_dir().expect("there is no home directory")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
|
||||
// This is the path on Windows 10
|
||||
let mut current = hkcu
|
||||
.open_subkey(format!(
|
||||
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
|
||||
SESSION_ID.load(Ordering::SeqCst)
|
||||
))
|
||||
.ok()
|
||||
.and_then(
|
||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||
Ok(current) => Option::from(current.bytes),
|
||||
Err(_) => None,
|
||||
},
|
||||
);
|
||||
|
||||
// This is the path on Windows 11
|
||||
if current.is_none() {
|
||||
current = hkcu
|
||||
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
|
||||
.ok()
|
||||
.and_then(
|
||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||
Ok(current) => Option::from(current.bytes),
|
||||
Err(_) => None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
|
||||
// exist until one has been created in the task view
|
||||
|
||||
// The registry value will also not exist on user login if virtual desktops have been created
|
||||
// but the task view has not been initiated
|
||||
|
||||
// In both of these cases, we return None, and the virtual desktop validation will never run. In
|
||||
// the latter case, if the user desires this validation after initiating the task view, komorebi
|
||||
// should be restarted, and then when this // fn runs again for the first time, it will pick up
|
||||
// the value of CurrentVirtualDesktop and validate against it accordingly
|
||||
current
|
||||
}
|
||||
|
||||
pub fn load_configuration() -> Result<()> {
|
||||
let home = HOME_DIR.clone();
|
||||
|
||||
let mut config_v1 = home.clone();
|
||||
config_v1.push("komorebi.ahk");
|
||||
|
||||
let mut config_v2 = home;
|
||||
config_v2.push("komorebi.ahk2");
|
||||
|
||||
if config_v1.exists() && which("autohotkey.exe").is_ok() {
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_v1
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("autohotkey.exe")
|
||||
.arg(config_v1.as_os_str())
|
||||
.output()?;
|
||||
} else if config_v2.exists() && which("AutoHotkey64.exe").is_ok() {
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_v2
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("AutoHotkey64.exe")
|
||||
.arg(config_v2.as_os_str())
|
||||
.output()?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum NotificationEvent {
|
||||
WindowManager(WindowManagerEvent),
|
||||
Socket(SocketMessage),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Notification {
|
||||
pub event: NotificationEvent,
|
||||
pub state: State,
|
||||
}
|
||||
|
||||
fn notify_subscribers(notification: &str) {
|
||||
let mut stale_subscriptions = vec![];
|
||||
let mut subscriptions = SUBSCRIPTION_PIPES.lock();
|
||||
for (subscriber, pipe) in subscriptions.iter_mut() {
|
||||
match writeln!(pipe, "{}", notification) {
|
||||
Ok(_) => {
|
||||
tracing::debug!("pushed notification to subscriber: {}", subscriber);
|
||||
}
|
||||
Err(error) => {
|
||||
// ERROR_FILE_NOT_FOUND
|
||||
// 2 (0x2)
|
||||
// The system cannot find the file specified.
|
||||
|
||||
// ERROR_NO_DATA
|
||||
// 232 (0xE8)
|
||||
// The pipe is being closed.
|
||||
|
||||
// Remove the subscription; the process will have to subscribe again
|
||||
if let Some(2 | 232) = error.raw_os_error() {
|
||||
let subscriber_cl = subscriber.clone();
|
||||
stale_subscriptions.push(subscriber_cl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for subscriber in stale_subscriptions {
|
||||
tracing::warn!("removing stale subscription: {}", subscriber);
|
||||
subscriptions.remove(&subscriber);
|
||||
}
|
||||
}
|
||||
@@ -1,127 +1,37 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use parking_lot::deadlock;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::thread;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
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;
|
||||
use sysinfo::SystemExt;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use which::which;
|
||||
use winreg::enums::HKEY_CURRENT_USER;
|
||||
use winreg::RegKey;
|
||||
|
||||
use komorebi_core::HidingBehaviour;
|
||||
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;
|
||||
|
||||
#[macro_use]
|
||||
mod ring;
|
||||
|
||||
mod container;
|
||||
mod monitor;
|
||||
mod process_command;
|
||||
mod process_event;
|
||||
mod process_movement;
|
||||
mod set_window_position;
|
||||
mod styles;
|
||||
mod window;
|
||||
mod window_manager;
|
||||
mod window_manager_event;
|
||||
mod windows_api;
|
||||
mod windows_callbacks;
|
||||
mod winevent;
|
||||
mod winevent_listener;
|
||||
mod workspace;
|
||||
|
||||
lazy_static! {
|
||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
|
||||
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec![
|
||||
"explorer.exe".to_string(),
|
||||
"firefox.exe".to_string(),
|
||||
"chrome.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
"ApplicationFrameHost.exe".to_string(),
|
||||
"steam.exe".to_string(),
|
||||
]));
|
||||
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"firefox.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
]));
|
||||
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
// mstsc.exe creates these on Windows 11 when a WSL process is launched
|
||||
// https://github.com/LGUG2Z/komorebi/issues/74
|
||||
"OPContainerClass".to_string(),
|
||||
"IHWindowClass".to_string()
|
||||
]));
|
||||
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"X410.exe".to_string(),
|
||||
"mstsc.exe".to_string(),
|
||||
"vcxsrv.exe".to_string(),
|
||||
]));
|
||||
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
|
||||
Arc::new(Mutex::new(HidingBehaviour::Minimize));
|
||||
static ref HOME_DIR: PathBuf = {
|
||||
if let Ok(home_path) = std::env::var("KOMOREBI_CONFIG_HOME") {
|
||||
let home = PathBuf::from(&home_path);
|
||||
|
||||
if home.as_path().is_dir() {
|
||||
home
|
||||
} else {
|
||||
panic!(
|
||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
|
||||
home_path
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dirs::home_dir().expect("there is no home directory")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||
use komorebi::listen_for_commands;
|
||||
use komorebi::listen_for_events;
|
||||
use komorebi::listen_for_movements;
|
||||
use komorebi::load_configuration;
|
||||
use komorebi::WinEventListener;
|
||||
use komorebi::WindowManager;
|
||||
use komorebi::WindowManagerEvent;
|
||||
use komorebi::WindowsApi;
|
||||
use komorebi::CUSTOM_FFM;
|
||||
use komorebi::HOME_DIR;
|
||||
use komorebi::SESSION_ID;
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
@@ -187,134 +97,6 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
Ok((guard, color_guard))
|
||||
}
|
||||
|
||||
pub fn load_configuration() -> Result<()> {
|
||||
let home = HOME_DIR.clone();
|
||||
|
||||
let mut config_v1 = home.clone();
|
||||
config_v1.push("komorebi.ahk");
|
||||
|
||||
let mut config_v2 = home;
|
||||
config_v2.push("komorebi.ahk2");
|
||||
|
||||
if config_v1.exists() && which("autohotkey.exe").is_ok() {
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_v1
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("autohotkey.exe")
|
||||
.arg(config_v1.as_os_str())
|
||||
.output()?;
|
||||
} else if config_v2.exists() && which("AutoHotkey64.exe").is_ok() {
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_v2
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("AutoHotkey64.exe")
|
||||
.arg(config_v2.as_os_str())
|
||||
.output()?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
|
||||
// This is the path on Windows 10
|
||||
let mut current = hkcu
|
||||
.open_subkey(format!(
|
||||
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
|
||||
SESSION_ID.load(Ordering::SeqCst)
|
||||
))
|
||||
.ok()
|
||||
.and_then(
|
||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||
Ok(current) => Option::from(current.bytes),
|
||||
Err(_) => None,
|
||||
},
|
||||
);
|
||||
|
||||
// This is the path on Windows 11
|
||||
if current.is_none() {
|
||||
current = hkcu
|
||||
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
|
||||
.ok()
|
||||
.and_then(
|
||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||
Ok(current) => Option::from(current.bytes),
|
||||
Err(_) => None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
|
||||
// exist until one has been created in the task view
|
||||
|
||||
// The registry value will also not exist on user login if virtual desktops have been created
|
||||
// but the task view has not been initiated
|
||||
|
||||
// In both of these cases, we return None, and the virtual desktop validation will never run. In
|
||||
// the latter case, if the user desires this validation after initiating the task view, komorebi
|
||||
// should be restarted, and then when this // fn runs again for the first time, it will pick up
|
||||
// the value of CurrentVirtualDesktop and validate against it accordingly
|
||||
current
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum NotificationEvent {
|
||||
WindowManager(WindowManagerEvent),
|
||||
Socket(SocketMessage),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
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();
|
||||
for (subscriber, pipe) in subscriptions.iter_mut() {
|
||||
match writeln!(pipe, "{}", notification) {
|
||||
Ok(_) => {
|
||||
tracing::debug!("pushed notification to subscriber: {}", subscriber);
|
||||
}
|
||||
Err(error) => {
|
||||
// ERROR_FILE_NOT_FOUND
|
||||
// 2 (0x2)
|
||||
// The system cannot find the file specified.
|
||||
|
||||
// ERROR_NO_DATA
|
||||
// 232 (0xE8)
|
||||
// The pipe is being closed.
|
||||
|
||||
// Remove the subscription; the process will have to subscribe again
|
||||
if let Some(2 | 232) = error.raw_os_error() {
|
||||
let subscriber_cl = subscriber.clone();
|
||||
stale_subscriptions.push(subscriber_cl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for subscriber in stale_subscriptions {
|
||||
tracing::warn!("removing stale subscription: {}", subscriber);
|
||||
subscriptions.remove(&subscriber);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
#[tracing::instrument]
|
||||
fn detect_deadlocks() {
|
||||
@@ -389,7 +171,7 @@ fn main() -> Result<()> {
|
||||
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||
crossbeam_channel::unbounded();
|
||||
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
let winevent_listener = WinEventListener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||
|
||||
@@ -8,6 +8,7 @@ use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Rect;
|
||||
@@ -16,7 +17,9 @@ use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||
#[derive(
|
||||
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
|
||||
)]
|
||||
pub struct Monitor {
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
id: isize,
|
||||
@@ -25,7 +28,7 @@ pub struct Monitor {
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
work_area_size: Rect,
|
||||
workspaces: Ring<Workspace>,
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(skip)]
|
||||
#[getset(get_mut = "pub")]
|
||||
workspace_names: HashMap<usize, String>,
|
||||
}
|
||||
|
||||
@@ -722,7 +722,7 @@ impl WindowManager {
|
||||
notify_subscribers(&serde_json::to_string(&Notification {
|
||||
event: NotificationEvent::Socket(message.clone()),
|
||||
state: self.as_ref().into(),
|
||||
})?)?;
|
||||
})?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -495,7 +495,7 @@ impl WindowManager {
|
||||
notify_subscribers(&serde_json::to_string(&Notification {
|
||||
event: NotificationEvent::WindowManager(*event),
|
||||
state: self.as_ref().into(),
|
||||
})?)?;
|
||||
})?);
|
||||
|
||||
tracing::info!("processed: {}", event.window().to_string());
|
||||
Ok(())
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Ring<T> {
|
||||
elements: VecDeque<T>,
|
||||
focused: usize,
|
||||
|
||||
@@ -7,6 +7,7 @@ use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::ser::Error;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
@@ -26,7 +27,7 @@ use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::WSL2_UI_PROCESSES;
|
||||
|
||||
#[derive(Debug, Clone, Copy, JsonSchema)]
|
||||
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
|
||||
pub struct Window {
|
||||
pub(crate) hwnd: isize,
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use hotwatch::notify::DebouncedEvent;
|
||||
use hotwatch::Hotwatch;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use uds_windows::UnixListener;
|
||||
|
||||
@@ -66,7 +67,7 @@ pub struct WindowManager {
|
||||
pub pending_move_op: Option<(usize, usize, usize)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct State {
|
||||
pub monitors: Ring<Monitor>,
|
||||
pub is_paused: bool,
|
||||
|
||||
@@ -2,13 +2,14 @@ use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::window::Window;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, JsonSchema)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum WindowManagerEvent {
|
||||
Destroy(WinEvent, Window),
|
||||
@@ -89,6 +90,7 @@ impl Display for WindowManagerEvent {
|
||||
}
|
||||
|
||||
impl WindowManagerEvent {
|
||||
#[must_use]
|
||||
pub const fn window(self) -> Window {
|
||||
match self {
|
||||
WindowManagerEvent::Destroy(_, window)
|
||||
@@ -106,6 +108,7 @@ impl WindowManagerEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
|
||||
match winevent {
|
||||
WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),
|
||||
|
||||
@@ -239,12 +239,14 @@ impl WindowsApi {
|
||||
.process()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn monitor_from_window(hwnd: HWND) -> isize {
|
||||
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn monitor_from_point(point: POINT) -> isize {
|
||||
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
@@ -364,6 +366,7 @@ impl WindowsApi {
|
||||
Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn window_thread_process_id(hwnd: HWND) -> (u32, u32) {
|
||||
let mut process_id: u32 = 0;
|
||||
|
||||
@@ -374,10 +377,12 @@ impl WindowsApi {
|
||||
(process_id, thread_id)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn current_thread_id() -> u32 {
|
||||
unsafe { GetCurrentThreadId() }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn current_process_id() -> u32 {
|
||||
unsafe { GetCurrentProcessId() }
|
||||
}
|
||||
@@ -532,18 +537,25 @@ impl WindowsApi {
|
||||
))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_window(hwnd: HWND) -> bool {
|
||||
unsafe { IsWindow(hwnd) }.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_window_visible(hwnd: HWND) -> bool {
|
||||
unsafe { IsWindowVisible(hwnd) }.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_iconic(hwnd: HWND) -> bool {
|
||||
unsafe { IsIconic(hwnd) }.into()
|
||||
}
|
||||
|
||||
pub fn monitor_info(hmonitor: isize) -> Result<MONITORINFO> {
|
||||
Self::monitor_info_w(HMONITOR(hmonitor))
|
||||
}
|
||||
|
||||
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFO> {
|
||||
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
|
||||
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
|
||||
@@ -566,7 +578,7 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn system_parameters_info_w(
|
||||
fn system_parameters_info_w(
|
||||
action: SYSTEM_PARAMETERS_INFO_ACTION,
|
||||
ui_param: u32,
|
||||
pv_param: *mut c_void,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::use_self)]
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
||||
@@ -88,7 +89,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, JsonSchema)]
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Display, JsonSchema)]
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
pub enum WinEvent {
|
||||
|
||||
@@ -32,14 +32,15 @@ pub struct WinEventListener {
|
||||
outgoing_events: Arc<Mutex<Sender<WindowManagerEvent>>>,
|
||||
}
|
||||
|
||||
pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> WinEventListener {
|
||||
WinEventListener {
|
||||
hook: Arc::new(AtomicIsize::new(0)),
|
||||
outgoing_events: outgoing,
|
||||
}
|
||||
}
|
||||
|
||||
impl WinEventListener {
|
||||
#[must_use]
|
||||
pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> Self {
|
||||
Self {
|
||||
hook: Arc::new(AtomicIsize::new(0)),
|
||||
outgoing_events: outgoing,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self) {
|
||||
let hook = self.hook.clone();
|
||||
let outgoing = self.outgoing_events.lock().clone();
|
||||
|
||||
@@ -8,6 +8,7 @@ use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Axis;
|
||||
@@ -22,19 +23,21 @@ use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::windows_api::WindowsApi;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||
#[derive(
|
||||
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
|
||||
)]
|
||||
pub struct Workspace {
|
||||
#[getset(set = "pub")]
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
name: Option<String>,
|
||||
containers: Ring<Container>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
monocle_container: Option<Container>,
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(skip)]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
monocle_container_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
maximized_window: Option<Window>,
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(skip)]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
maximized_window_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
@@ -49,7 +52,7 @@ pub struct Workspace {
|
||||
workspace_padding: Option<i32>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
container_padding: Option<i32>,
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(skip)]
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
latest_layout: Vec<Rect>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
|
||||
Reference in New Issue
Block a user