mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-15 01:37:42 +01:00
Compare commits
16 Commits
v0.1.25
...
feature/ko
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f36926cdb1 | ||
|
|
c47cf4718b | ||
|
|
1accbf65ca | ||
|
|
7ee3c928d8 | ||
|
|
6d1903099a | ||
|
|
d5c6f090cc | ||
|
|
598f9ec0aa | ||
|
|
11acff5236 | ||
|
|
4802b55452 | ||
|
|
482a7b1d7f | ||
|
|
d00ee82a9d | ||
|
|
9f01d8fa0f | ||
|
|
627088c9b9 | ||
|
|
22cf7b5017 | ||
|
|
3e984d886c | ||
|
|
185cb4d4a8 |
58
Cargo.lock
generated
58
Cargo.lock
generated
@@ -796,9 +796,36 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komoborders"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"color-eyre",
|
||||
"dirs",
|
||||
"komoborders-client",
|
||||
"komorebi",
|
||||
"komorebi-client",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uds_windows",
|
||||
"windows 0.54.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komoborders-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"serde",
|
||||
"serde_json_lenient",
|
||||
"uds_windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komorebi"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"clap",
|
||||
@@ -810,6 +837,7 @@ dependencies = [
|
||||
"getset",
|
||||
"hex_color",
|
||||
"hotwatch",
|
||||
"komoborders-client",
|
||||
"komorebi-core",
|
||||
"lazy_static",
|
||||
"miow",
|
||||
@@ -839,7 +867,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
dependencies = [
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
@@ -849,7 +877,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
@@ -865,7 +893,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebic"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
@@ -893,7 +921,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebic-no-console"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
@@ -1640,9 +1668,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.17"
|
||||
version = "0.8.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309"
|
||||
checksum = "0afe01b987fac84253ce8acd5c05af9941975e4dee5b4f2d826b6947be8ec2c7"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -1652,9 +1680,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.17"
|
||||
version = "0.8.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83263746fe5e32097f06356968a077f96089739c927a61450efa069905eec108"
|
||||
checksum = "d253e72f060451e9e5615a1686f3cb4ff87c4e70504c79bdab8fb3b010cd4e97"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1693,18 +1721,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.199"
|
||||
version = "1.0.200"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
|
||||
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.199"
|
||||
version = "1.0.200"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
|
||||
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1888,9 +1916,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.11"
|
||||
version = "0.30.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87341a165d73787554941cd5ef55ad728011566fe714e987d1b976c15dbc3a83"
|
||||
checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"core-foundation-sys",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"derive-ahk",
|
||||
"derive-ahk", "komoborders", "komoborders-client",
|
||||
"komorebi",
|
||||
"komorebi-client",
|
||||
"komorebi-core",
|
||||
@@ -18,6 +18,8 @@ dirs = "5"
|
||||
color-eyre = "0.6"
|
||||
serde_json = { package = "serde_json_lenient", version = "0.1" }
|
||||
sysinfo = "0.30"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
uds_windows = "1"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
version = "0.54"
|
||||
|
||||
13
README.md
13
README.md
@@ -84,6 +84,19 @@ using `scoop`, `winget` or building from source.
|
||||
|
||||
[](https://www.youtube.com/watch?v=H9-_c1egQ4g)
|
||||
|
||||
# Comparison With Fancy Zones
|
||||
|
||||
Community member [Olge](https://www.youtube.com/@polle5555) has created an
|
||||
excellent video which compares the default window management features of
|
||||
Windows 11, Fancy Zones and komorebi.
|
||||
|
||||
If you are not familiar with tiling window managers or if you are looking at
|
||||
komorebi and wondering "how is this different from Fancy Zones? 🤔", this short
|
||||
video will answer the majority of your questions.
|
||||
|
||||
[](https://www.youtube.com/watch?v=0LCbS_gm0RA)
|
||||
|
||||
|
||||
# Demonstrations
|
||||
|
||||
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
|
||||
|
||||
@@ -101,7 +101,7 @@ monocle.
|
||||
+-------+-----+
|
||||
```
|
||||
|
||||
### RightMainVerticalStack
|
||||
#### RightMainVerticalStack
|
||||
|
||||
```
|
||||
+-----+-------+
|
||||
|
||||
124
docs/troubleshooting.md
Normal file
124
docs/troubleshooting.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Troubleshooting
|
||||
|
||||
## AutoHotKey executable not found
|
||||
|
||||
If you try to start komorebi with AHK using `komorebic start --ahk`, and you have
|
||||
not installed AHK using `scoop`, you'll probably receive an error:
|
||||
|
||||
```text
|
||||
Error: could not find autohotkey, please make sure it is installed before using the --ahk flag
|
||||
```
|
||||
|
||||
Depending on how AHK is installed the executable on your system may have a
|
||||
different name. In order to account for this, you may set the `KOMOREBI_AHK_EXE`
|
||||
environment variable in your
|
||||
[PowerShell profile](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.4)
|
||||
to match the name of the executable as it is found on your system.
|
||||
|
||||
After setting `KOMOREBI_AHK_EXE` make sure to either reload your PowerShell
|
||||
profile or open a new terminal tab.
|
||||
|
||||
## Komorebi is unresponsive when the display wakes from sleep
|
||||
|
||||
This can happen in rare cases when your monitor state is not preserved after it
|
||||
wakes from sleep.
|
||||
|
||||
### Problem
|
||||
|
||||
Your hotkeys in _whkd_ work, but it feels as if _komorebi_ knows nothing about
|
||||
the previous state (you can't control previous windows, although newly launched ones
|
||||
can be manipulated as normal).
|
||||
|
||||
### Solution
|
||||
|
||||
Some monitors, such as the Samsung G8/G9 (LED, Neo, OLED) have an _adaptive
|
||||
sync_ or _variable refresh rate_ setting within the actual monitor OSD that can
|
||||
disrupt how the device is persisted in the _komorebi_ state following suspension.
|
||||
|
||||
To fix this, please try to disable _Adaptive Sync_ or any other _VRR_ branded
|
||||
alias by referring to the manufacturer's documentation.
|
||||
|
||||
!!! warning
|
||||
|
||||
Disabling VRR within Windows (e.g. _Nvidia Control Panel_) may work and can indeed
|
||||
change the configuration you see within your monitor's OSD, but some monitors
|
||||
will re-enable the setting regardless following suspension.
|
||||
|
||||
### Reproducing
|
||||
|
||||
Ensure _komorebi_ is in an operational state by executing `komorebic start` as
|
||||
normal.
|
||||
|
||||
If _komorebi_ is already unresponsive, then please restart _komorebi_ first by
|
||||
running `komorebic stop` and `komorebic start`.
|
||||
|
||||
1. **`komorebic state`**
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": {
|
||||
"elements": [
|
||||
{
|
||||
"id": 65537,
|
||||
"name": "DISPLAY1",
|
||||
"device": "SAM71AA",
|
||||
"device_id": "SAM71AA-5&a1a3e88&0&UID24834",
|
||||
"size": {
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"right": 5120,
|
||||
"bottom": 1440
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This appears to be fine -- _komorebi_ is aware of the device and associated
|
||||
window handles.
|
||||
|
||||
2. **Let your display go to sleep.**
|
||||
|
||||
Simply turning the monitor off is not enough to reproduce the problem; you must
|
||||
let Windows turn off the display itself.
|
||||
|
||||
To avoid waiting an eternity:
|
||||
|
||||
- _Control Panel_ -> _Hardware and Sound_ -> _Power Options_ -> _Edit Plan
|
||||
Settings_
|
||||
|
||||
_Turn off the display: 1 minute_
|
||||
|
||||
Allow a minute for the display to reset, then once it actually shuts off
|
||||
allow for any additional time as prompted by your monitor for the cycle to
|
||||
complete.
|
||||
|
||||
3. **Wake your display again** by pressing any key.
|
||||
|
||||
_komorebi_ should now be unresponsive.
|
||||
|
||||
4. **`komorebic state`**
|
||||
|
||||
Don't stop _komorebi_ just yet.
|
||||
|
||||
Since it's unresponsive, you can open another shell instead to execute the above command.
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": {
|
||||
"elements": [
|
||||
{
|
||||
"id": 65537,
|
||||
"name": "DISPLAY1",
|
||||
"device": null,
|
||||
"device_id": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can see the _komorebi_ state is no longer associated with the previous
|
||||
device: `null`, suggesting an issue when the display resumes from a suspended
|
||||
state.
|
||||
12
komoborders-client/Cargo.toml
Normal file
12
komoborders-client/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "komoborders-client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
uds_windows = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
74
komoborders-client/src/lib.rs
Normal file
74
komoborders-client/src/lib.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
const KOMOBORDERS: &str = "komoborders.sock";
|
||||
|
||||
pub fn send_message(message: &SocketMessage) -> std::io::Result<()> {
|
||||
let socket = dirs::data_local_dir()
|
||||
.expect("there is no local data directory")
|
||||
.join("komorebi")
|
||||
.join(KOMOBORDERS);
|
||||
|
||||
let mut connected = false;
|
||||
while !connected {
|
||||
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||
connected = true;
|
||||
stream.write_all(serde_json::to_string(message)?.as_bytes())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum ZOrder {
|
||||
Top,
|
||||
NoTopMost,
|
||||
Bottom,
|
||||
TopMost,
|
||||
}
|
||||
|
||||
// impl From<isize> for ZOrder {
|
||||
// fn from(value: isize) -> Self {
|
||||
// match value {
|
||||
// -2 => Self::NoTopMost,
|
||||
// -1 => Self::TopMost,
|
||||
// 0 => Self::Top,
|
||||
// 1 => Self::Bottom,
|
||||
// _ => unimplemented!(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Into<isize> for ZOrder {
|
||||
fn into(self) -> isize {
|
||||
match self {
|
||||
ZOrder::Top => 0,
|
||||
ZOrder::NoTopMost => -2,
|
||||
ZOrder::Bottom => 1,
|
||||
ZOrder::TopMost => -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum SocketMessage {
|
||||
FocusedColour(u32, u32, u32),
|
||||
UnfocusedColour(u32, u32, u32),
|
||||
MonocleColour(u32, u32, u32),
|
||||
StackColour(u32, u32, u32),
|
||||
Width(i32),
|
||||
Offset(i32),
|
||||
ZOrder(ZOrder),
|
||||
}
|
||||
|
||||
impl FromStr for SocketMessage {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str(s)
|
||||
}
|
||||
}
|
||||
19
komoborders/Cargo.toml
Normal file
19
komoborders/Cargo.toml
Normal file
@@ -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 }
|
||||
199
komoborders/src/border.rs
Normal file
199
komoborders/src/border.rs
Normal file
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
259
komoborders/src/main.rs
Normal file
259
komoborders/src/main.rs
Normal file
@@ -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(())
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "A tiling window manager for Windows"
|
||||
categories = ["tiling-window-manager", "windows"]
|
||||
@@ -12,6 +12,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
komoborders-client = { path = "../komoborders-client" }
|
||||
|
||||
bitflags = { version = "2", features = ["serde"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
@@ -65,7 +65,7 @@ pub struct Rgb {
|
||||
}
|
||||
|
||||
impl Rgb {
|
||||
pub fn new(r: u32, g: u32, b: u32) -> Self {
|
||||
pub const fn new(r: u32, g: u32, b: u32) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1255,12 +1255,21 @@ impl WindowManager {
|
||||
WindowKind::Single => {
|
||||
BORDER_COLOUR_SINGLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
BORDER_COLOUR_CURRENT.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
komoborders_client::send_message(
|
||||
&komoborders_client::SocketMessage::FocusedColour(r, g, b),
|
||||
)?;
|
||||
}
|
||||
WindowKind::Stack => {
|
||||
BORDER_COLOUR_STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
komoborders_client::send_message(
|
||||
&komoborders_client::SocketMessage::StackColour(r, g, b),
|
||||
)?;
|
||||
}
|
||||
WindowKind::Monocle => {
|
||||
BORDER_COLOUR_MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
komoborders_client::send_message(
|
||||
&komoborders_client::SocketMessage::MonocleColour(r, g, b),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,10 +1284,14 @@ impl WindowManager {
|
||||
SocketMessage::BorderWidth(width) => {
|
||||
BORDER_WIDTH.store(width, Ordering::SeqCst);
|
||||
WindowsApi::invalidate_border_rect()?;
|
||||
komoborders_client::send_message(&komoborders_client::SocketMessage::Width(width))?;
|
||||
}
|
||||
SocketMessage::BorderOffset(offset) => {
|
||||
BORDER_OFFSET.store(offset, Ordering::SeqCst);
|
||||
WindowsApi::invalidate_border_rect()?;
|
||||
komoborders_client::send_message(&komoborders_client::SocketMessage::Offset(
|
||||
offset,
|
||||
))?;
|
||||
}
|
||||
SocketMessage::StackbarMode(mode) => {
|
||||
let mut stackbar_mode = STACKBAR_MODE.lock();
|
||||
|
||||
@@ -284,7 +284,9 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::Show(_, window) | WindowManagerEvent::Manage(window) => {
|
||||
WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::Manage(window)
|
||||
| WindowManagerEvent::Uncloak(_, window) => {
|
||||
let mut switch_to = None;
|
||||
for (i, monitors) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
||||
@@ -294,56 +296,66 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((known_monitor_idx, known_workspace_idx)) = switch_to {
|
||||
if self.focused_monitor_idx() != known_monitor_idx
|
||||
|| self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_idx()
|
||||
!= known_workspace_idx
|
||||
{
|
||||
self.focus_monitor(known_monitor_idx)?;
|
||||
self.focus_workspace(known_workspace_idx)?;
|
||||
return Ok(());
|
||||
match switch_to {
|
||||
Some((known_monitor_idx, known_workspace_idx)) => {
|
||||
if !matches!(event, WindowManagerEvent::Uncloak(_, _)) {
|
||||
if self.focused_monitor_idx() != known_monitor_idx
|
||||
|| self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_idx()
|
||||
!= known_workspace_idx
|
||||
{
|
||||
self.focus_monitor(known_monitor_idx)?;
|
||||
self.focus_workspace(known_workspace_idx)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// There are some applications such as Firefox where, if they are focused when a
|
||||
// workspace switch takes place, it will fire an additional Show event, which will
|
||||
// result in them being associated with both the original workspace and the workspace
|
||||
// being switched to. This loop is to try to ensure that we don't end up with
|
||||
// duplicates across multiple workspaces, as it results in ghost layout tiles.
|
||||
let mut proceed = true;
|
||||
|
||||
// There are some applications such as Firefox where, if they are focused when a
|
||||
// workspace switch takes place, it will fire an additional Show event, which will
|
||||
// result in them being associated with both the original workspace and the workspace
|
||||
// being switched to. This loop is to try to ensure that we don't end up with
|
||||
// duplicates across multiple workspaces, as it results in ghost layout tiles.
|
||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
||||
if workspace.container_for_window(window.hwnd).is_some()
|
||||
&& i != self.focused_monitor_idx()
|
||||
&& j != monitor.focused_workspace_idx()
|
||||
{
|
||||
tracing::debug!(
|
||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
||||
if workspace.container_for_window(window.hwnd).is_some()
|
||||
&& i != self.focused_monitor_idx()
|
||||
&& j != monitor.focused_workspace_idx()
|
||||
{
|
||||
tracing::debug!(
|
||||
"ignoring show event for window already associated with another workspace"
|
||||
);
|
||||
|
||||
window.hide();
|
||||
return Ok(());
|
||||
window.hide();
|
||||
proceed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let behaviour = self.window_container_behaviour;
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
if proceed {
|
||||
let behaviour = self.window_container_behaviour;
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
if !workspace.contains_window(window.hwnd) {
|
||||
match behaviour {
|
||||
WindowContainerBehaviour::Create => {
|
||||
workspace.new_container_for_window(window);
|
||||
self.update_focused_workspace(false, false)?;
|
||||
}
|
||||
WindowContainerBehaviour::Append => {
|
||||
workspace
|
||||
.focused_container_mut()
|
||||
.ok_or_else(|| anyhow!("there is no focused container"))?
|
||||
.add_window(window);
|
||||
self.update_focused_workspace(true, false)?;
|
||||
if !workspace.contains_window(window.hwnd) && switch_to.is_none() {
|
||||
match behaviour {
|
||||
WindowContainerBehaviour::Create => {
|
||||
workspace.new_container_for_window(window);
|
||||
self.update_focused_workspace(false, false)?;
|
||||
}
|
||||
WindowContainerBehaviour::Append => {
|
||||
workspace
|
||||
.focused_container_mut()
|
||||
.ok_or_else(|| {
|
||||
anyhow!("there is no focused container")
|
||||
})?
|
||||
.add_window(window);
|
||||
self.update_focused_workspace(true, false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,8 +595,7 @@ impl WindowManager {
|
||||
}
|
||||
WindowManagerEvent::DisplayChange(..)
|
||||
| WindowManagerEvent::MouseCapture(..)
|
||||
| WindowManagerEvent::Cloak(..)
|
||||
| WindowManagerEvent::Uncloak(..) => {}
|
||||
| WindowManagerEvent::Cloak(..) => {}
|
||||
};
|
||||
|
||||
if !self.focused_workspace()?.tile() {
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::Rgb;
|
||||
use crate::ACTIVE_WINDOW_BORDER_STYLE;
|
||||
use crate::BORDER_COLOUR_CURRENT;
|
||||
use crate::BORDER_COLOUR_MONOCLE;
|
||||
@@ -475,13 +476,36 @@ impl StaticConfig {
|
||||
},
|
||||
);
|
||||
|
||||
komoborders_client::send_message(&komoborders_client::SocketMessage::Width(
|
||||
self.border_width.unwrap_or(8),
|
||||
))?;
|
||||
|
||||
BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
||||
|
||||
komoborders_client::send_message(&komoborders_client::SocketMessage::Width(
|
||||
self.border_offset.unwrap_or(-1),
|
||||
))?;
|
||||
|
||||
if let Some(colours) = &self.active_window_border_colours {
|
||||
BORDER_COLOUR_SINGLE.store(u32::from(colours.single), Ordering::SeqCst);
|
||||
BORDER_COLOUR_CURRENT.store(u32::from(colours.single), Ordering::SeqCst);
|
||||
BORDER_COLOUR_STACK.store(u32::from(colours.stack), Ordering::SeqCst);
|
||||
BORDER_COLOUR_MONOCLE.store(u32::from(colours.monocle), Ordering::SeqCst);
|
||||
|
||||
let single: Rgb = u32::from(colours.single).into();
|
||||
komoborders_client::send_message(&komoborders_client::SocketMessage::FocusedColour(
|
||||
single.r, single.g, single.b,
|
||||
))?;
|
||||
|
||||
let stack: Rgb = u32::from(colours.stack).into();
|
||||
komoborders_client::send_message(&komoborders_client::SocketMessage::StackColour(
|
||||
stack.r, stack.g, stack.b,
|
||||
))?;
|
||||
|
||||
let monocle: Rgb = u32::from(colours.monocle).into();
|
||||
komoborders_client::send_message(&komoborders_client::SocketMessage::MonocleColour(
|
||||
monocle.r, monocle.g, monocle.b,
|
||||
))?;
|
||||
}
|
||||
|
||||
let active_window_border_style = self.active_window_border_style.unwrap_or_default();
|
||||
|
||||
@@ -14,7 +14,6 @@ use komorebi_core::config_generation::MatchingRule;
|
||||
use komorebi_core::config_generation::MatchingStrategy;
|
||||
use regex::Regex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::ser::Error;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -97,24 +96,23 @@ impl Serialize for Window {
|
||||
"title",
|
||||
&self
|
||||
.title()
|
||||
.map_err(|_| S::Error::custom("could not get window title"))?,
|
||||
.unwrap_or_else(|_| String::from("could not get window title")),
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"exe",
|
||||
&self
|
||||
.exe()
|
||||
.map_err(|_| S::Error::custom("could not get window exe"))?,
|
||||
.unwrap_or_else(|_| String::from("could not get window exe")),
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"class",
|
||||
&self
|
||||
.class()
|
||||
.map_err(|_| S::Error::custom("could not get window class"))?,
|
||||
.unwrap_or_else(|_| String::from("could not get window class")),
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"rect",
|
||||
&WindowsApi::window_rect(self.hwnd())
|
||||
.map_err(|_| S::Error::custom("could not get window rect"))?,
|
||||
&WindowsApi::window_rect(self.hwnd()).unwrap_or_default(),
|
||||
)?;
|
||||
state.end()
|
||||
}
|
||||
|
||||
@@ -566,7 +566,7 @@ impl WindowManager {
|
||||
target_workspace_idx: usize,
|
||||
to_move: &mut Vec<EnforceWorkspaceRuleOp>,
|
||||
) -> () {
|
||||
tracing::info!(
|
||||
tracing::trace!(
|
||||
"{} should be on monitor {}, workspace {}",
|
||||
window_title,
|
||||
target_monitor_idx,
|
||||
@@ -1205,6 +1205,7 @@ impl WindowManager {
|
||||
let monitor = self
|
||||
.focused_monitor_mut()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let workspace = monitor
|
||||
.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
@@ -1225,6 +1226,11 @@ impl WindowManager {
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
target_monitor.add_container(container, workspace_idx)?;
|
||||
|
||||
if let Some(workspace_idx) = workspace_idx {
|
||||
target_monitor.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
|
||||
target_monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
target_monitor.update_focused_workspace(offset)?;
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
|
||||
@@ -414,7 +415,8 @@ impl WindowsApi {
|
||||
// top of other pop-up dialogs such as a file picker dialog from
|
||||
// Firefox. When adjusting this in the future, it's important to check
|
||||
// those dialog cases.
|
||||
Self::set_window_pos(hwnd, layout, HWND_TOP, flags.bits())
|
||||
// TODO: Make the HWND_X flag configurable
|
||||
Self::set_window_pos(hwnd, layout, HWND_BOTTOM, flags.bits())
|
||||
}
|
||||
|
||||
pub fn hide_border_window(hwnd: HWND) -> Result<()> {
|
||||
@@ -424,6 +426,11 @@ impl WindowsApi {
|
||||
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
||||
}
|
||||
|
||||
pub fn set_border_pos(hwnd: HWND, layout: &Rect, position: HWND) -> Result<()> {
|
||||
let flags = { SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE };
|
||||
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
||||
}
|
||||
|
||||
/// set_window_pos calls SetWindowPos without any accounting for Window decorations.
|
||||
fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> {
|
||||
unsafe {
|
||||
@@ -461,6 +468,13 @@ impl WindowsApi {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_window(hwnd: HWND) -> Result<()> {
|
||||
match Self::post_message(hwnd, WM_DESTROY, WPARAM(0), LPARAM(0)) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => Err(anyhow!("could not close window")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hide_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_HIDE);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic-no-console"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
|
||||
categories = ["cli", "tiling-window-manager", "windows"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26-dev.0"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
|
||||
categories = ["cli", "tiling-window-manager", "windows"]
|
||||
|
||||
@@ -39,6 +39,10 @@ theme:
|
||||
- search.share
|
||||
- search.suggest
|
||||
- toc.follow
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.highlight
|
||||
- pymdownx.superfences
|
||||
plugins:
|
||||
- macros
|
||||
- search
|
||||
@@ -50,6 +54,7 @@ nav:
|
||||
- Getting started:
|
||||
- Installation: installation.md
|
||||
- Example configurations: example-configurations.md
|
||||
- Troubleshooting: troubleshooting.md
|
||||
- Common workflows:
|
||||
- common-workflows/komorebi-config-home.md
|
||||
- common-workflows/active-window-border.md
|
||||
|
||||
Reference in New Issue
Block a user