mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-25 10:08:33 +02:00
feat(bar): don't think i'll pursue this
This commit is contained in:
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",
|
"derive-ahk",
|
||||||
"komorebi",
|
"komorebi",
|
||||||
"komorebi-core",
|
"komorebi-core",
|
||||||
|
"komorebi-bar",
|
||||||
"komorebic"
|
"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"
|
license = "MIT"
|
||||||
edition = "2021"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ use std::collections::VecDeque;
|
|||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Getters, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip)]
|
||||||
#[getset(get = "pub")]
|
#[getset(get = "pub")]
|
||||||
id: String,
|
id: String,
|
||||||
windows: Ring<Window>,
|
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)]
|
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||||
#![allow(clippy::missing_errors_doc)]
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use clap::Parser;
|
||||||
use std::fs::File;
|
use color_eyre::Result;
|
||||||
use std::io::Write;
|
use crossbeam_channel::Receiver;
|
||||||
use std::path::PathBuf;
|
use crossbeam_channel::Sender;
|
||||||
use std::process::Command;
|
#[cfg(feature = "deadlock_detection")]
|
||||||
use std::sync::atomic::AtomicBool;
|
use parking_lot::deadlock;
|
||||||
use std::sync::atomic::AtomicU32;
|
use parking_lot::Mutex;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
use std::thread;
|
use std::thread;
|
||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
use std::time::Duration;
|
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::Process;
|
||||||
use sysinfo::ProcessExt;
|
use sysinfo::ProcessExt;
|
||||||
use sysinfo::SystemExt;
|
use sysinfo::SystemExt;
|
||||||
use tracing_appender::non_blocking::WorkerGuard;
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use which::which;
|
|
||||||
use winreg::enums::HKEY_CURRENT_USER;
|
|
||||||
use winreg::RegKey;
|
|
||||||
|
|
||||||
use komorebi_core::HidingBehaviour;
|
use komorebi::listen_for_commands;
|
||||||
use komorebi_core::SocketMessage;
|
use komorebi::listen_for_events;
|
||||||
|
use komorebi::listen_for_movements;
|
||||||
use crate::process_command::listen_for_commands;
|
use komorebi::load_configuration;
|
||||||
use crate::process_event::listen_for_events;
|
use komorebi::WinEventListener;
|
||||||
use crate::process_movement::listen_for_movements;
|
use komorebi::WindowManager;
|
||||||
use crate::window_manager::State;
|
use komorebi::WindowManagerEvent;
|
||||||
use crate::window_manager::WindowManager;
|
use komorebi::WindowsApi;
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use komorebi::CUSTOM_FFM;
|
||||||
use crate::windows_api::WindowsApi;
|
use komorebi::HOME_DIR;
|
||||||
|
use komorebi::SESSION_ID;
|
||||||
#[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);
|
|
||||||
|
|
||||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||||
@@ -187,134 +97,6 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
|||||||
Ok((guard, color_guard))
|
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")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
fn detect_deadlocks() {
|
fn detect_deadlocks() {
|
||||||
@@ -389,7 +171,7 @@ fn main() -> Result<()> {
|
|||||||
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||||
crossbeam_channel::unbounded();
|
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();
|
winevent_listener.start();
|
||||||
|
|
||||||
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use getset::Getters;
|
|||||||
use getset::MutGetters;
|
use getset::MutGetters;
|
||||||
use getset::Setters;
|
use getset::Setters;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use komorebi_core::Rect;
|
use komorebi_core::Rect;
|
||||||
@@ -16,7 +17,9 @@ use crate::container::Container;
|
|||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::workspace::Workspace;
|
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 {
|
pub struct Monitor {
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
id: isize,
|
id: isize,
|
||||||
@@ -25,7 +28,7 @@ pub struct Monitor {
|
|||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
work_area_size: Rect,
|
work_area_size: Rect,
|
||||||
workspaces: Ring<Workspace>,
|
workspaces: Ring<Workspace>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip)]
|
||||||
#[getset(get_mut = "pub")]
|
#[getset(get_mut = "pub")]
|
||||||
workspace_names: HashMap<usize, String>,
|
workspace_names: HashMap<usize, String>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -722,7 +722,7 @@ impl WindowManager {
|
|||||||
notify_subscribers(&serde_json::to_string(&Notification {
|
notify_subscribers(&serde_json::to_string(&Notification {
|
||||||
event: NotificationEvent::Socket(message.clone()),
|
event: NotificationEvent::Socket(message.clone()),
|
||||||
state: self.as_ref().into(),
|
state: self.as_ref().into(),
|
||||||
})?)?;
|
})?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ impl WindowManager {
|
|||||||
notify_subscribers(&serde_json::to_string(&Notification {
|
notify_subscribers(&serde_json::to_string(&Notification {
|
||||||
event: NotificationEvent::WindowManager(*event),
|
event: NotificationEvent::WindowManager(*event),
|
||||||
state: self.as_ref().into(),
|
state: self.as_ref().into(),
|
||||||
})?)?;
|
})?);
|
||||||
|
|
||||||
tracing::info!("processed: {}", event.window().to_string());
|
tracing::info!("processed: {}", event.window().to_string());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct Ring<T> {
|
pub struct Ring<T> {
|
||||||
elements: VecDeque<T>,
|
elements: VecDeque<T>,
|
||||||
focused: usize,
|
focused: usize,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use color_eyre::Result;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::ser::Error;
|
use serde::ser::Error;
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::Serializer;
|
use serde::Serializer;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
@@ -26,7 +27,7 @@ use crate::LAYERED_WHITELIST;
|
|||||||
use crate::MANAGE_IDENTIFIERS;
|
use crate::MANAGE_IDENTIFIERS;
|
||||||
use crate::WSL2_UI_PROCESSES;
|
use crate::WSL2_UI_PROCESSES;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, JsonSchema)]
|
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
pub(crate) hwnd: isize,
|
pub(crate) hwnd: isize,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use hotwatch::notify::DebouncedEvent;
|
|||||||
use hotwatch::Hotwatch;
|
use hotwatch::Hotwatch;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use uds_windows::UnixListener;
|
use uds_windows::UnixListener;
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ pub struct WindowManager {
|
|||||||
pub pending_move_op: Option<(usize, usize, usize)>,
|
pub pending_move_op: Option<(usize, usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub monitors: Ring<Monitor>,
|
pub monitors: Ring<Monitor>,
|
||||||
pub is_paused: bool,
|
pub is_paused: bool,
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ use std::fmt::Display;
|
|||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::winevent::WinEvent;
|
use crate::winevent::WinEvent;
|
||||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
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")]
|
#[serde(tag = "type", content = "content")]
|
||||||
pub enum WindowManagerEvent {
|
pub enum WindowManagerEvent {
|
||||||
Destroy(WinEvent, Window),
|
Destroy(WinEvent, Window),
|
||||||
@@ -89,6 +90,7 @@ impl Display for WindowManagerEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WindowManagerEvent {
|
impl WindowManagerEvent {
|
||||||
|
#[must_use]
|
||||||
pub const fn window(self) -> Window {
|
pub const fn window(self) -> Window {
|
||||||
match self {
|
match self {
|
||||||
WindowManagerEvent::Destroy(_, window)
|
WindowManagerEvent::Destroy(_, window)
|
||||||
@@ -106,6 +108,7 @@ impl WindowManagerEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
|
pub fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
|
||||||
match winevent {
|
match winevent {
|
||||||
WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),
|
WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),
|
||||||
|
|||||||
@@ -239,12 +239,14 @@ impl WindowsApi {
|
|||||||
.process()
|
.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn monitor_from_window(hwnd: HWND) -> isize {
|
pub fn monitor_from_window(hwnd: HWND) -> isize {
|
||||||
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||||
unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0
|
unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn monitor_from_point(point: POINT) -> isize {
|
pub fn monitor_from_point(point: POINT) -> isize {
|
||||||
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
// 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))
|
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) {
|
pub fn window_thread_process_id(hwnd: HWND) -> (u32, u32) {
|
||||||
let mut process_id: u32 = 0;
|
let mut process_id: u32 = 0;
|
||||||
|
|
||||||
@@ -374,10 +377,12 @@ impl WindowsApi {
|
|||||||
(process_id, thread_id)
|
(process_id, thread_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn current_thread_id() -> u32 {
|
pub fn current_thread_id() -> u32 {
|
||||||
unsafe { GetCurrentThreadId() }
|
unsafe { GetCurrentThreadId() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn current_process_id() -> u32 {
|
pub fn current_process_id() -> u32 {
|
||||||
unsafe { GetCurrentProcessId() }
|
unsafe { GetCurrentProcessId() }
|
||||||
}
|
}
|
||||||
@@ -532,18 +537,25 @@ impl WindowsApi {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_window(hwnd: HWND) -> bool {
|
pub fn is_window(hwnd: HWND) -> bool {
|
||||||
unsafe { IsWindow(hwnd) }.into()
|
unsafe { IsWindow(hwnd) }.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_window_visible(hwnd: HWND) -> bool {
|
pub fn is_window_visible(hwnd: HWND) -> bool {
|
||||||
unsafe { IsWindowVisible(hwnd) }.into()
|
unsafe { IsWindowVisible(hwnd) }.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_iconic(hwnd: HWND) -> bool {
|
pub fn is_iconic(hwnd: HWND) -> bool {
|
||||||
unsafe { IsIconic(hwnd) }.into()
|
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> {
|
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFO> {
|
||||||
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
|
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
|
||||||
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
|
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
|
||||||
@@ -566,7 +578,7 @@ impl WindowsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn system_parameters_info_w(
|
fn system_parameters_info_w(
|
||||||
action: SYSTEM_PARAMETERS_INFO_ACTION,
|
action: SYSTEM_PARAMETERS_INFO_ACTION,
|
||||||
ui_param: u32,
|
ui_param: u32,
|
||||||
pv_param: *mut c_void,
|
pv_param: *mut c_void,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![allow(clippy::use_self)]
|
#![allow(clippy::use_self)]
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
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_END;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
|
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)]
|
#[repr(u32)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum WinEvent {
|
pub enum WinEvent {
|
||||||
|
|||||||
@@ -32,14 +32,15 @@ pub struct WinEventListener {
|
|||||||
outgoing_events: Arc<Mutex<Sender<WindowManagerEvent>>>,
|
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 {
|
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) {
|
pub fn start(self) {
|
||||||
let hook = self.hook.clone();
|
let hook = self.hook.clone();
|
||||||
let outgoing = self.outgoing_events.lock().clone();
|
let outgoing = self.outgoing_events.lock().clone();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use getset::Getters;
|
|||||||
use getset::MutGetters;
|
use getset::MutGetters;
|
||||||
use getset::Setters;
|
use getset::Setters;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use komorebi_core::Axis;
|
use komorebi_core::Axis;
|
||||||
@@ -22,19 +23,21 @@ use crate::ring::Ring;
|
|||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::windows_api::WindowsApi;
|
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 {
|
pub struct Workspace {
|
||||||
#[getset(set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
containers: Ring<Container>,
|
containers: Ring<Container>,
|
||||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
monocle_container: Option<Container>,
|
monocle_container: Option<Container>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip)]
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
monocle_container_restore_idx: Option<usize>,
|
monocle_container_restore_idx: Option<usize>,
|
||||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
maximized_window: Option<Window>,
|
maximized_window: Option<Window>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip)]
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
maximized_window_restore_idx: Option<usize>,
|
maximized_window_restore_idx: Option<usize>,
|
||||||
#[getset(get = "pub", get_mut = "pub")]
|
#[getset(get = "pub", get_mut = "pub")]
|
||||||
@@ -49,7 +52,7 @@ pub struct Workspace {
|
|||||||
workspace_padding: Option<i32>,
|
workspace_padding: Option<i32>,
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
container_padding: Option<i32>,
|
container_padding: Option<i32>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip)]
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
latest_layout: Vec<Rect>,
|
latest_layout: Vec<Rect>,
|
||||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
|
|||||||
Reference in New Issue
Block a user