mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-09-01 08:27:16 +02:00
feat(borders): wip
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "komoborders"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
komorebi-client = { path = "../komorebi-client" }
|
||||
komoborders-client = { path = "../komoborders-client" }
|
||||
komorebi = { path = "../komorebi" }
|
||||
serde_json = "1"
|
||||
color-eyre = "0.6"
|
||||
windows = { workspace = true }
|
||||
lazy_static = "1"
|
||||
parking_lot = "0.12"
|
||||
uds_windows = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
@@ -0,0 +1,199 @@
|
||||
use komoborders_client::ZOrder;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Win32::Foundation::COLORREF;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::Foundation::LRESULT;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::Graphics::Gdi::BeginPaint;
|
||||
use windows::Win32::Graphics::Gdi::CreatePen;
|
||||
use windows::Win32::Graphics::Gdi::EndPaint;
|
||||
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||
use windows::Win32::Graphics::Gdi::SelectObject;
|
||||
use windows::Win32::Graphics::Gdi::ValidateRect;
|
||||
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
|
||||
use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME;
|
||||
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||
|
||||
use crate::FocusKind;
|
||||
use crate::FOCUSED_STATE;
|
||||
use crate::RECT_STATE;
|
||||
use komorebi::Rgb;
|
||||
use komorebi::WindowsApi;
|
||||
use komorebi_client::Rect;
|
||||
|
||||
pub static TRANSPARENCY: u32 = 0;
|
||||
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
|
||||
pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref Z_ORDER: Arc<Mutex<ZOrder>> = Arc::new(Mutex::new(ZOrder::Bottom));
|
||||
pub static ref FOCUSED: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb(
|
||||
Rgb::new(66, 165, 245)
|
||||
)));
|
||||
pub static ref UNFOCUSED: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb(
|
||||
Rgb::new(128, 128, 128)
|
||||
)));
|
||||
pub static ref MONOCLE: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb(
|
||||
Rgb::new(255, 51, 153)
|
||||
)));
|
||||
pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb(
|
||||
Rgb::new(0, 165, 66)
|
||||
)));
|
||||
}
|
||||
|
||||
pub struct Border {
|
||||
pub hwnd: isize,
|
||||
}
|
||||
|
||||
impl Border {
|
||||
pub const fn hwnd(&self) -> HWND {
|
||||
HWND(self.hwnd)
|
||||
}
|
||||
|
||||
pub fn create(id: &str) -> color_eyre::Result<Self> {
|
||||
let name: Vec<u16> = format!("komoborder-{id}\0").encode_utf16().collect();
|
||||
let class_name = PCWSTR(name.as_ptr());
|
||||
|
||||
let h_module = WindowsApi::module_handle_w()?;
|
||||
|
||||
let window_class = WNDCLASSW {
|
||||
hInstance: h_module.into(),
|
||||
lpszClassName: class_name,
|
||||
style: CS_HREDRAW | CS_VREDRAW,
|
||||
lpfnWndProc: Some(Self::callback),
|
||||
hbrBackground: WindowsApi::create_solid_brush(TRANSPARENCY),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let _ = WindowsApi::register_class_w(&window_class);
|
||||
|
||||
let (hwnd_sender, hwnd_receiver) = mpsc::channel();
|
||||
|
||||
std::thread::spawn(move || -> color_eyre::Result<()> {
|
||||
let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), h_module)?;
|
||||
hwnd_sender.send(hwnd)?;
|
||||
|
||||
let mut message = MSG::default();
|
||||
unsafe {
|
||||
while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() {
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageW(&message);
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
hwnd: hwnd_receiver.recv()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn destroy(&self) -> color_eyre::Result<()> {
|
||||
WindowsApi::destroy_window(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn update(&self, rect: &Rect) -> color_eyre::Result<()> {
|
||||
// Make adjustments to the border
|
||||
let mut rect = *rect;
|
||||
rect.add_margin(BORDER_WIDTH.load(Ordering::SeqCst));
|
||||
rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst));
|
||||
|
||||
// Store the border rect so that it can be used by the callback
|
||||
{
|
||||
let mut rects = RECT_STATE.lock();
|
||||
rects.insert(self.hwnd, rect);
|
||||
}
|
||||
|
||||
// Update the position of the border
|
||||
WindowsApi::set_border_pos(self.hwnd(), &rect, HWND((*Z_ORDER.lock()).into()))?;
|
||||
|
||||
// Invalidate the rect to trigger the callback to update colours etc.
|
||||
self.invalidate();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn invalidate(&self) {
|
||||
let _ = unsafe { InvalidateRect(self.hwnd(), None, false) };
|
||||
}
|
||||
|
||||
pub extern "system" fn callback(
|
||||
window: HWND,
|
||||
message: u32,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
) -> LRESULT {
|
||||
unsafe {
|
||||
match message {
|
||||
WM_PAINT => {
|
||||
let rects = RECT_STATE.lock();
|
||||
|
||||
// With the rect that we stored in Self::update
|
||||
if let Some(rect) = rects.get(&window.0).copied() {
|
||||
// Grab the focus kind for this border
|
||||
let focus_kind = {
|
||||
FOCUSED_STATE
|
||||
.lock()
|
||||
.get(&window.0)
|
||||
.copied()
|
||||
.unwrap_or(FocusKind::Unfocused)
|
||||
};
|
||||
|
||||
// Set up the brush to draw the border
|
||||
let mut ps = PAINTSTRUCT::default();
|
||||
let hdc = BeginPaint(window, &mut ps);
|
||||
let hpen = CreatePen(
|
||||
PS_SOLID | PS_INSIDEFRAME,
|
||||
BORDER_WIDTH.load(Ordering::SeqCst),
|
||||
COLORREF(match focus_kind {
|
||||
FocusKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst),
|
||||
FocusKind::Single => FOCUSED.load(Ordering::SeqCst),
|
||||
FocusKind::Stack => STACK.load(Ordering::SeqCst),
|
||||
FocusKind::Monocle => MONOCLE.load(Ordering::SeqCst),
|
||||
}),
|
||||
);
|
||||
|
||||
let hbrush = WindowsApi::create_solid_brush(TRANSPARENCY);
|
||||
|
||||
// Draw the border
|
||||
SelectObject(hdc, hpen);
|
||||
SelectObject(hdc, hbrush);
|
||||
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
|
||||
EndPaint(window, &ps);
|
||||
ValidateRect(window, None);
|
||||
}
|
||||
|
||||
LRESULT(0)
|
||||
}
|
||||
WM_DESTROY => {
|
||||
PostQuitMessage(0);
|
||||
LRESULT(0)
|
||||
}
|
||||
_ => DefWindowProcW(window, message, wparam, lparam),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(
|
||||
clippy::missing_errors_doc,
|
||||
clippy::redundant_pub_crate,
|
||||
clippy::significant_drop_tightening,
|
||||
clippy::significant_drop_in_scrutinee
|
||||
)]
|
||||
|
||||
mod border;
|
||||
|
||||
use komorebi_client::Rect;
|
||||
use komorebi_client::UnixListener;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::io::ErrorKind;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use uds_windows::UnixStream;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
|
||||
use crate::border::Border;
|
||||
use crate::border::BORDER_WIDTH;
|
||||
use crate::border::FOCUSED;
|
||||
use crate::border::MONOCLE;
|
||||
use crate::border::STACK;
|
||||
use crate::border::UNFOCUSED;
|
||||
use crate::border::Z_ORDER;
|
||||
use komorebi::WindowsApi;
|
||||
use komorebi_client::Rgb;
|
||||
|
||||
lazy_static! {
|
||||
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
|
||||
static ref RECT_STATE: Mutex<HashMap<isize, Rect>> = Mutex::new(HashMap::new());
|
||||
static ref FOCUSED_STATE: Mutex<HashMap<isize, FocusKind>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum FocusKind {
|
||||
Unfocused,
|
||||
Single,
|
||||
Stack,
|
||||
Monocle,
|
||||
}
|
||||
|
||||
pub fn read_commands_uds(stream: UnixStream) -> color_eyre::Result<()> {
|
||||
let reader = BufReader::new(stream.try_clone()?);
|
||||
for line in reader.lines() {
|
||||
let message = komoborders_client::SocketMessage::from_str(&line?)?;
|
||||
|
||||
match message {
|
||||
komoborders_client::SocketMessage::FocusedColour(r, g, b) => FOCUSED.store(
|
||||
komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(),
|
||||
Ordering::SeqCst,
|
||||
),
|
||||
komoborders_client::SocketMessage::UnfocusedColour(r, g, b) => UNFOCUSED.store(
|
||||
komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(),
|
||||
Ordering::SeqCst,
|
||||
),
|
||||
komoborders_client::SocketMessage::MonocleColour(r, g, b) => MONOCLE.store(
|
||||
komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(),
|
||||
Ordering::SeqCst,
|
||||
),
|
||||
komoborders_client::SocketMessage::StackColour(r, g, b) => STACK.store(
|
||||
komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(),
|
||||
Ordering::SeqCst,
|
||||
),
|
||||
komoborders_client::SocketMessage::Width(width) => {
|
||||
BORDER_WIDTH.store(width, Ordering::SeqCst)
|
||||
}
|
||||
komoborders_client::SocketMessage::Offset(offset) => {
|
||||
BORDER_WIDTH.store(offset, Ordering::SeqCst)
|
||||
}
|
||||
komoborders_client::SocketMessage::ZOrder(z_order) => {
|
||||
let mut z = Z_ORDER.lock();
|
||||
*z = z_order;
|
||||
}
|
||||
}
|
||||
|
||||
let borders = BORDER_STATE.lock();
|
||||
for (_, border) in borders.iter() {
|
||||
border.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
WindowsApi::set_process_dpi_awareness_context()?;
|
||||
let socket = dirs::data_local_dir()
|
||||
.expect("there is no local data directory")
|
||||
.join("komorebi")
|
||||
.join("komoborders.sock");
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(()) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
_ => {
|
||||
return Err(error.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
std::thread::spawn(move || {
|
||||
for client in listener.incoming() {
|
||||
match client {
|
||||
Ok(stream) => match read_commands_uds(stream) {
|
||||
Ok(()) => {
|
||||
println!("processed message");
|
||||
}
|
||||
Err(error) => {
|
||||
println!("{error}");
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
println!("{error}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let komorebi = komorebi_client::subscribe("komoborders")?;
|
||||
|
||||
for client in komorebi.incoming() {
|
||||
match client {
|
||||
Ok(subscription) => {
|
||||
let reader = BufReader::new(subscription);
|
||||
|
||||
#[allow(clippy::lines_filter_map_ok)]
|
||||
for line in reader.lines().flatten() {
|
||||
if let Ok(notification) =
|
||||
serde_json::from_str::<komorebi_client::Notification>(&line)
|
||||
{
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
// Check the state every time we receive a notification
|
||||
let state = notification.state;
|
||||
|
||||
for m in state.monitors.elements() {
|
||||
// Only operate on the focused workspace of each monitor
|
||||
if let Some(ws) = m.focused_workspace() {
|
||||
let mut should_proceed = true;
|
||||
|
||||
// Handle the monocle container separately
|
||||
if let Some(monocle) = ws.monocle_container() {
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
let border = borders
|
||||
.entry(monocle.id().clone())
|
||||
.or_insert_with(|| Border::create(monocle.id()).unwrap());
|
||||
|
||||
{
|
||||
let mut focused = FOCUSED_STATE.lock();
|
||||
focused.insert(border.hwnd, FocusKind::Monocle);
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
monocle.focused_window().unwrap().hwnd(),
|
||||
)?;
|
||||
|
||||
border.update(&rect)?;
|
||||
should_proceed = false;
|
||||
}
|
||||
|
||||
if should_proceed {
|
||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
||||
WindowsApi::foreground_window().unwrap_or_default(),
|
||||
));
|
||||
|
||||
if is_maximized {
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
should_proceed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if should_proceed {
|
||||
// Destroy any borders not associated with the focused workspace
|
||||
let container_ids = ws
|
||||
.containers()
|
||||
.iter()
|
||||
.map(|c| c.id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (id, border) in borders.iter() {
|
||||
if !container_ids.contains(id) {
|
||||
border.destroy()?;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove them from the border map
|
||||
borders.retain(|k, _| container_ids.contains(k));
|
||||
|
||||
for (idx, c) in ws.containers().iter().enumerate() {
|
||||
// Get the border entry for this container from the map or create one
|
||||
let border = borders
|
||||
.entry(c.id().clone())
|
||||
.or_insert_with(|| Border::create(c.id()).unwrap());
|
||||
|
||||
// Update the focused state for all containers on this workspace
|
||||
{
|
||||
let mut focused = FOCUSED_STATE.lock();
|
||||
focused.insert(
|
||||
border.hwnd,
|
||||
if idx != ws.focused_container_idx() {
|
||||
FocusKind::Unfocused
|
||||
} else {
|
||||
if c.windows().len() > 1 {
|
||||
FocusKind::Stack
|
||||
} else {
|
||||
FocusKind::Single
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
c.focused_window().unwrap().hwnd(),
|
||||
)?;
|
||||
|
||||
border.update(&rect)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
if error.raw_os_error().expect("could not get raw os error") == 109 {
|
||||
while komorebi_client::send_message(
|
||||
&komorebi_client::SocketMessage::AddSubscriberSocket(String::from(
|
||||
"komoborders",
|
||||
)),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user