mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-16 10:17:41 +01:00
Compare commits
6 Commits
master-aft
...
feature/ko
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a63490e88a | ||
|
|
e5cf042ea9 | ||
|
|
c435f84afc | ||
|
|
de0db4d014 | ||
|
|
0afcf6d86a | ||
|
|
e0e3afa5b9 |
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -841,6 +841,16 @@ dependencies = [
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.22-dev.0"
|
||||
dependencies = [
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
"serde_json",
|
||||
"uds_windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.22-dev.0"
|
||||
|
||||
@@ -4,6 +4,7 @@ resolver = "2"
|
||||
members = [
|
||||
"derive-ahk",
|
||||
"komorebi",
|
||||
"komorebi-client",
|
||||
"komorebi-core",
|
||||
"komorebic",
|
||||
"komorebic-no-console",
|
||||
|
||||
200
README.md
200
README.md
@@ -101,20 +101,76 @@ widget enabled. The original video can be viewed
|
||||
|
||||
https://user-images.githubusercontent.com/13164844/163496414-a9cde3d1-b8a7-4a7a-96fb-a8985380bc70.mp4
|
||||
|
||||
# Development
|
||||
# Contribution Guidelines
|
||||
|
||||
If you would like to contribute code to this repository, there are a few requests that I have to ensure a foundation of
|
||||
code quality, consistency and commit hygiene:
|
||||
If you would like to contribute to `komorebi` please take the time to carefully read the guidelines below.
|
||||
|
||||
## Commit hygiene
|
||||
|
||||
- Flatten all `use` statements
|
||||
- Run `cargo +nightly clippy` and ensure that all lints and suggestions have been addressed before committing
|
||||
- Run `cargo +stable clippy` and ensure that all lints and suggestions have been addressed before committing
|
||||
- Run `cargo +nightly fmt --all` to ensure consistent formatting before committing
|
||||
- Use `git cz` with
|
||||
the [Commitizen CLI](https://github.com/commitizen/cz-cli#conventional-commit-messages-as-a-global-utility) to prepare
|
||||
commit messages
|
||||
- Provide at least one short sentence or paragraph in your commit message body to describe your thought process for the
|
||||
- Provide **at least** one short sentence or paragraph in your commit message body to describe your thought process for the
|
||||
changes being committed
|
||||
|
||||
## PRs should contain only a single feature or bug fix
|
||||
|
||||
It is very difficult to review pull requests which touch multiple unrelated features and parts of the codebase.
|
||||
|
||||
Please do not submit pull requests like this; you will be asked to separate them into smaller PRs that deal only with
|
||||
one feature or bug fix at a time.
|
||||
|
||||
If you are working on multiple features and bug fixes, I suggest that you cut a branch called `local-trunk`
|
||||
from `master` which you keep up to date, and rebase the various independent branches you are working on onto that branch
|
||||
if you want to test them together or create a build with everything integrated.
|
||||
|
||||
## Refactors to the codebase must have prior approval
|
||||
|
||||
`komorebi` is a mature codebase with an internal consistency and structure that has developed organically over close to
|
||||
half a decade.
|
||||
|
||||
There are [countless hours of live coding videos](https://youtube.com/@LGUG2Z) demonstrating work on this project and
|
||||
showing new contributors how to do everything from basic tasks like implementing new `komorebic` commands to
|
||||
distinguishing monitors by manufacturer hardware identifiers and video card ports.
|
||||
|
||||
Refactors to the structure of the codebase are not taken lightly and require prior discussion and approval.
|
||||
|
||||
Please do not start refactoring the codebase with the expectation of having your changes integrated until you receive an
|
||||
explicit approval or a request to do so.
|
||||
|
||||
Similarly, when implementing features and bug fixes, please stick to the structure of the codebase as much as possible
|
||||
and do not take this as an opportunity to do some "refactoring along the way".
|
||||
|
||||
It is extremely difficult to review PRs for features and bug fixes if they are lost in sweeping changes to the structure
|
||||
of the codebase.
|
||||
|
||||
## Breaking changes to user-facing interfaces are unacceptable
|
||||
|
||||
This includes but is not limited to:
|
||||
|
||||
- All `komorebic` commands
|
||||
- The `komorebi.json` schema
|
||||
- The [`komorebi-application-specific-configuration`](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
|
||||
schema
|
||||
|
||||
No user should ever find that their configuration file has stopped working after upgrading to a new version
|
||||
of `komorebi`.
|
||||
|
||||
More often than not there are ways to reformulate changes that may initially seem like they require breaking user-facing
|
||||
interfaces into additive changes.
|
||||
|
||||
For some inspiration please take a look
|
||||
at [this commit](https://github.com/LGUG2Z/komorebi/commit/e7d928a065eb63bb4ea1fb864c69c1cae8cc763b) which added the
|
||||
ability for users to specify colours in `komorebi.json` in Hex format alongside RGB.
|
||||
|
||||
There is also a process in place for graceful, non-breaking, deprecation of configuration options that are no longer
|
||||
required.
|
||||
|
||||
# Development
|
||||
|
||||
If you use IntelliJ, you should enable the following settings to ensure that code generated by macros is recognised by
|
||||
the IDE for completions and navigation:
|
||||
|
||||
@@ -153,20 +209,21 @@ found, information about it will appear in the log which can be shared when open
|
||||
# Window Manager State and Integrations
|
||||
|
||||
The current state of the window manager can be queried using the `komorebic state` command, which returns a JSON
|
||||
representation of the `State` struct, which includes the current state of `WindowManager`.
|
||||
representation of the `State` struct.
|
||||
|
||||
This may also be polled to build further integrations and widgets on top of (if you ever wanted to build something
|
||||
like [Stackline](https://github.com/AdamWagner/stackline) for Windows, you could do it by polling this command).
|
||||
This may also be polled to build further integrations and widgets on top of.
|
||||
|
||||
# Window Manager Event Subscriptions
|
||||
|
||||
It is also possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled
|
||||
## Named Pipes
|
||||
|
||||
It is possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled
|
||||
by `komorebi` using [Named Pipes](https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes).
|
||||
|
||||
First, your application must create a named pipe. Once the named pipe has been created, run the following command:
|
||||
|
||||
```powershell
|
||||
komorebic.exe subscribe <your pipe name>
|
||||
komorebic.exe subscribe-pipe <your pipe name>
|
||||
```
|
||||
|
||||
Note that you do not have to include the full path of the named pipe, just the name.
|
||||
@@ -190,12 +247,125 @@ You may then filter on the `type` key to listen to the events that you are inter
|
||||
notification types, refer to the enum variants of `WindowManagerEvent` in `komorebi` and `SocketMessage`
|
||||
in `komorebi-core`.
|
||||
|
||||
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Python
|
||||
by [@denBot](https://github.com/denBot) can be
|
||||
found [here](https://gist.github.com/denBot/4136279812f87819f86d99eba77c1ee0).
|
||||
Below is an example of how you can subscribe to and filter on events using a named pipe in `nodejs`.
|
||||
|
||||
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Rust can also be found
|
||||
in the [`komokana`](https://github.com/LGUG2Z/komokana) repository.
|
||||
```javascript
|
||||
const { exec } = require("child_process");
|
||||
const net = require("net");
|
||||
|
||||
const pipeName = "\\\\.\\pipe\\komorebi-js";
|
||||
const server = net.createServer((stream) => {
|
||||
console.log("Client connected");
|
||||
|
||||
// Every time there is a workspace-related event, let's log the names of all
|
||||
// workspaces on the currently focused monitor, and then log the name of the
|
||||
// currently focused workspace on that monitor
|
||||
|
||||
stream.on("data", (data) => {
|
||||
let json = JSON.parse(data.toString());
|
||||
let event = json.event;
|
||||
|
||||
if (event.type.includes("Workspace")) {
|
||||
let monitors = json.state.monitors;
|
||||
let current_monitor = monitors.elements[monitors.focused];
|
||||
let workspaces = monitors.elements[monitors.focused].workspaces;
|
||||
let current_workspace = workspaces.elements[workspaces.focused];
|
||||
|
||||
console.log(
|
||||
workspaces.elements
|
||||
.map((workspace) => workspace.name)
|
||||
.filter((name) => name !== null)
|
||||
);
|
||||
console.log(current_workspace.name);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on("end", () => {
|
||||
console.log("Client disconnected");
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(pipeName, () => {
|
||||
console.log("Named pipe server listening");
|
||||
});
|
||||
|
||||
const command = "komorebic subscribe-pipe komorebi-js";
|
||||
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Error executing command: ${error}`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Unix Domain Sockets
|
||||
|
||||
It is possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled
|
||||
by `komorebi` using [Unix Domain Sockets](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/).
|
||||
|
||||
UDS are also the only mode of communication between `komorebi` and `komorebic`.
|
||||
|
||||
First, your application must create a socket in `$ENV:LocalAppData\komorebi`. Once the socket has been created, run the
|
||||
following command:
|
||||
|
||||
```powershell
|
||||
komorebic.exe subscribe-socket <your socket name>
|
||||
```
|
||||
|
||||
If the socket exists, komorebi will start pushing JSON data of successfully handled events and messages as in the
|
||||
example above in the Named Pipes section.
|
||||
|
||||
## Rust Client
|
||||
|
||||
As of `v0.1.22` it is possible to use the `komorebi-client` crate to subscribe to notifications of
|
||||
every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust codebase.
|
||||
|
||||
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
|
||||
|
||||
```rust
|
||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.22"}
|
||||
|
||||
use anyhow::Result;
|
||||
use komorebi_client::Notification;
|
||||
use komorebi_client::NotificationEvent;
|
||||
use komorebi_client::UnixListener;
|
||||
use komorebi_client::WindowManagerEvent;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let socket = komorebi_client::subscribe(NAME)?;
|
||||
|
||||
for incoming in socket.incoming() {
|
||||
match incoming {
|
||||
Ok(data) => {
|
||||
let reader = BufReader::new(data.try_clone()?);
|
||||
|
||||
for line in reader.lines().flatten() {
|
||||
let notification: Notification = match serde_json::from_str(&line) {
|
||||
Ok(notification) => notification,
|
||||
Err(error) => {
|
||||
log::debug!("discarding malformed komorebi notification: {error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// match and filter on desired notifications
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
log::debug!("{error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
A read-world example can be found
|
||||
in [komokana](https://github.com/LGUG2Z/komokana/blob/feature/komorebi-uds/src/main.rs).
|
||||
|
||||
## Subscription Event Notification Schema
|
||||
|
||||
|
||||
12
komorebi-client/Cargo.toml
Normal file
12
komorebi-client/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.22-dev.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" }
|
||||
uds_windows = "1"
|
||||
serde_json = "1"
|
||||
79
komorebi-client/src/lib.rs
Normal file
79
komorebi-client/src/lib.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
pub use komorebi::container::Container;
|
||||
pub use komorebi::monitor::Monitor;
|
||||
pub use komorebi::ring::Ring;
|
||||
pub use komorebi::window::Window;
|
||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||
pub use komorebi::workspace::Workspace;
|
||||
pub use komorebi::Notification;
|
||||
pub use komorebi::NotificationEvent;
|
||||
pub use komorebi::State;
|
||||
pub use komorebi_core::Arrangement;
|
||||
pub use komorebi_core::Axis;
|
||||
pub use komorebi_core::CustomLayout;
|
||||
pub use komorebi_core::CycleDirection;
|
||||
pub use komorebi_core::DefaultLayout;
|
||||
pub use komorebi_core::Direction;
|
||||
pub use komorebi_core::Layout;
|
||||
pub use komorebi_core::OperationDirection;
|
||||
pub use komorebi_core::Rect;
|
||||
pub use komorebi_core::SocketMessage;
|
||||
|
||||
use komorebi::DATA_DIR;
|
||||
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::net::Shutdown;
|
||||
pub use uds_windows::UnixListener;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
const KOMOREBI: &str = "komorebi.sock";
|
||||
|
||||
pub fn send_message(message: &SocketMessage) -> std::io::Result<()> {
|
||||
let socket = DATA_DIR.join(KOMOREBI);
|
||||
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(())
|
||||
}
|
||||
pub fn send_query(message: &SocketMessage) -> std::io::Result<String> {
|
||||
let socket = DATA_DIR.join(KOMOREBI);
|
||||
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.write_all(serde_json::to_string(message)?.as_bytes())?;
|
||||
stream.shutdown(Shutdown::Write)?;
|
||||
|
||||
let mut reader = BufReader::new(stream);
|
||||
let mut response = String::new();
|
||||
reader.read_to_string(&mut response)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn subscribe(name: &str) -> std::io::Result<UnixListener> {
|
||||
let socket = DATA_DIR.join(name);
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(()) => {}
|
||||
Err(error) => match error.kind() {
|
||||
std::io::ErrorKind::NotFound => {}
|
||||
_ => {
|
||||
return Err(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
|
||||
send_message(&SocketMessage::AddSubscriberSocket(name.to_string()))?;
|
||||
|
||||
Ok(listener)
|
||||
}
|
||||
@@ -86,11 +86,7 @@ impl Border {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_position(
|
||||
self,
|
||||
window: Window,
|
||||
activate: bool,
|
||||
) -> Result<()> {
|
||||
pub fn set_position(self, window: Window, activate: bool) -> Result<()> {
|
||||
if self.hwnd == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
|
||||
@@ -170,7 +170,7 @@ lazy_static! {
|
||||
}
|
||||
})
|
||||
};
|
||||
static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
|
||||
pub static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
|
||||
pub static ref AHK_EXE: String = {
|
||||
let mut ahk: String = String::from("autohotkey.exe");
|
||||
|
||||
@@ -205,7 +205,6 @@ pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
|
||||
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||
pub static ALT_FOCUS_HACK: AtomicBool = AtomicBool::new(false);
|
||||
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
|
||||
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
@@ -192,10 +192,7 @@ impl Monitor {
|
||||
self.workspaces().len()
|
||||
}
|
||||
|
||||
pub fn update_focused_workspace(
|
||||
&mut self,
|
||||
offset: Option<Rect>,
|
||||
) -> Result<()> {
|
||||
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
|
||||
let work_area = *self.work_area_size();
|
||||
let offset = if self.work_area_offset().is_some() {
|
||||
self.work_area_offset()
|
||||
|
||||
@@ -48,7 +48,6 @@ use crate::window_manager::WindowManager;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::ALT_FOCUS_HACK;
|
||||
use crate::BORDER_COLOUR_CURRENT;
|
||||
use crate::BORDER_COLOUR_MONOCLE;
|
||||
use crate::BORDER_COLOUR_SINGLE;
|
||||
@@ -1252,8 +1251,8 @@ impl WindowManager {
|
||||
BORDER_OFFSET.store(offset, Ordering::SeqCst);
|
||||
WindowsApi::invalidate_border_rect()?;
|
||||
}
|
||||
SocketMessage::AltFocusHack(enable) => {
|
||||
ALT_FOCUS_HACK.store(enable, Ordering::SeqCst);
|
||||
SocketMessage::AltFocusHack(_) => {
|
||||
tracing::info!("this action is deprecated");
|
||||
}
|
||||
SocketMessage::ApplicationSpecificConfigurationSchema => {
|
||||
let asc = schema_for!(Vec<ApplicationConfiguration>);
|
||||
|
||||
@@ -376,8 +376,7 @@ impl WindowManager {
|
||||
|
||||
// If we have moved across the monitors, use that override, otherwise determine
|
||||
// if a move has taken place by ruling out a resize
|
||||
let is_move = moved_across_monitors
|
||||
|| resize.right == 0 && resize.bottom == 0;
|
||||
let is_move = moved_across_monitors || resize.right == 0 && resize.bottom == 0;
|
||||
|
||||
if is_move {
|
||||
tracing::info!("moving with mouse");
|
||||
|
||||
@@ -214,7 +214,7 @@ impl From<&Monitor> for MonitorConfig {
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
/// The `komorebi.json` static configuration file reference for `v0.1.20`
|
||||
pub struct StaticConfig {
|
||||
/// Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to
|
||||
/// DEPRECATED from v0.1.22: no longer required
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub invisible_borders: Option<Rect>,
|
||||
/// Delta to resize windows by (default 50)
|
||||
@@ -238,12 +238,14 @@ pub struct StaticConfig {
|
||||
/// Path to applications.yaml from komorebi-application-specific-configurations (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub app_specific_configuration_path: Option<PathBuf>,
|
||||
/// Width of the active window border (default: 20)
|
||||
/// Width of the window border (default: 8)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_window_border_width: Option<i32>,
|
||||
/// Offset of the active window border (default: None)
|
||||
#[serde(alias = "active_window_border_width")]
|
||||
pub border_width: Option<i32>,
|
||||
/// Offset of the window border (default: -1)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_window_border_offset: Option<i32>,
|
||||
#[serde(alias = "active_window_border_offset")]
|
||||
pub border_offset: Option<i32>,
|
||||
/// Display an active window border (default: false)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_window_border: Option<bool>,
|
||||
@@ -259,10 +261,6 @@ pub struct StaticConfig {
|
||||
/// Monitor and workspace configurations
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub monitors: Option<Vec<MonitorConfig>>,
|
||||
/// DEPRECATED from v0.1.20: no longer required
|
||||
#[schemars(skip)]
|
||||
#[serde(skip_serializing)]
|
||||
pub alt_focus_hack: Option<bool>,
|
||||
/// Which Windows signal to use when hiding windows (default: minimize)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub window_hiding_behaviour: Option<HidingBehaviour>,
|
||||
@@ -377,8 +375,8 @@ impl From<&WindowManager> for StaticConfig {
|
||||
focus_follows_mouse: value.focus_follows_mouse,
|
||||
mouse_follows_focus: Option::from(value.mouse_follows_focus),
|
||||
app_specific_configuration_path: None,
|
||||
active_window_border_width: Option::from(BORDER_WIDTH.load(Ordering::SeqCst)),
|
||||
active_window_border_offset: Option::from(BORDER_OFFSET.load(Ordering::SeqCst)),
|
||||
border_width: Option::from(BORDER_WIDTH.load(Ordering::SeqCst)),
|
||||
border_offset: Option::from(BORDER_OFFSET.load(Ordering::SeqCst)),
|
||||
active_window_border: Option::from(BORDER_ENABLED.load(Ordering::SeqCst)),
|
||||
active_window_border_colours: border_colours,
|
||||
default_workspace_padding: Option::from(
|
||||
@@ -388,7 +386,6 @@ impl From<&WindowManager> for StaticConfig {
|
||||
DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),
|
||||
),
|
||||
monitors: Option::from(monitors),
|
||||
alt_focus_hack: None,
|
||||
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
|
||||
global_work_area_offset: value.work_area_offset,
|
||||
float_rules: None,
|
||||
@@ -429,7 +426,7 @@ impl StaticConfig {
|
||||
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
self.active_window_border_width.map_or_else(
|
||||
self.border_width.map_or_else(
|
||||
|| {
|
||||
BORDER_WIDTH.store(8, Ordering::SeqCst);
|
||||
},
|
||||
@@ -438,7 +435,7 @@ impl StaticConfig {
|
||||
},
|
||||
);
|
||||
|
||||
BORDER_OFFSET.store(self.active_window_border_offset.unwrap_or(-1), Ordering::SeqCst);
|
||||
BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
||||
|
||||
if let Some(colours) = &self.active_window_border_colours {
|
||||
BORDER_COLOUR_SINGLE.store(u32::from(colours.single), Ordering::SeqCst);
|
||||
@@ -895,6 +892,12 @@ impl StaticConfig {
|
||||
|
||||
wm.focus_follows_mouse = value.focus_follows_mouse;
|
||||
|
||||
let monitor_count = wm.monitors().len();
|
||||
|
||||
for i in 0..monitor_count {
|
||||
wm.update_focused_workspace_by_monitor_idx(i)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::fmt::Write as _;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::anyhow;
|
||||
@@ -19,9 +18,6 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use winput::press;
|
||||
use winput::release;
|
||||
use winput::Vk;
|
||||
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::HidingBehaviour;
|
||||
@@ -31,7 +27,6 @@ use crate::styles::ExtendedWindowStyle;
|
||||
use crate::styles::WindowStyle;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::ALT_FOCUS_HACK;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
@@ -143,11 +138,7 @@ impl Window {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_position(
|
||||
&mut self,
|
||||
layout: &Rect,
|
||||
top: bool,
|
||||
) -> Result<()> {
|
||||
pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> {
|
||||
let rect = *layout;
|
||||
WindowsApi::position_window(self.hwnd(), &rect, top)
|
||||
}
|
||||
@@ -298,12 +289,7 @@ impl Window {
|
||||
let mut tried_resetting_foreground_access = false;
|
||||
let mut max_attempts = 10;
|
||||
|
||||
let hotkey_uses_alt = WindowsApi::alt_is_pressed();
|
||||
while !foregrounded && max_attempts > 0 {
|
||||
if ALT_FOCUS_HACK.load(Ordering::SeqCst) {
|
||||
press(Vk::Alt);
|
||||
}
|
||||
|
||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||
Ok(()) => {
|
||||
foregrounded = true;
|
||||
@@ -324,10 +310,6 @@ impl Window {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ALT_FOCUS_HACK.load(Ordering::SeqCst) && !hotkey_uses_alt {
|
||||
release(Vk::Alt);
|
||||
}
|
||||
}
|
||||
|
||||
// Center cursor in Window
|
||||
|
||||
@@ -54,8 +54,6 @@ use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
|
||||
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
|
||||
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
|
||||
use windows::Win32::UI::HiDpi::GetDpiForMonitor;
|
||||
use windows::Win32::UI::HiDpi::GetDpiForSystem;
|
||||
use windows::Win32::UI::HiDpi::GetDpiForWindow;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
||||
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
|
||||
@@ -352,7 +350,7 @@ impl WindowsApi {
|
||||
| SetWindowPosition::FRAME_CHANGED;
|
||||
|
||||
let shadow_rect = Self::shadow_rect(hwnd)?;
|
||||
let rect = Rect{
|
||||
let rect = Rect {
|
||||
left: layout.left + shadow_rect.left,
|
||||
top: layout.top + shadow_rect.top,
|
||||
right: layout.right + shadow_rect.right,
|
||||
@@ -499,9 +497,11 @@ impl WindowsApi {
|
||||
let mut rect = unsafe { std::mem::zeroed() };
|
||||
|
||||
if Self::dwm_get_window_attribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &mut rect).is_ok() {
|
||||
let window_scale = unsafe { GetDpiForWindow(hwnd) };
|
||||
let system_scale = unsafe { GetDpiForSystem() };
|
||||
Ok(Rect::from(rect).scale(system_scale.try_into()?, window_scale.try_into()?))
|
||||
// TODO(raggi): once we declare DPI awareness, we will need to scale the rect.
|
||||
// let window_scale = unsafe { GetDpiForWindow(hwnd) };
|
||||
// let system_scale = unsafe { GetDpiForSystem() };
|
||||
// Ok(Rect::from(rect).scale(system_scale.try_into()?, window_scale.try_into()?))
|
||||
Ok(Rect::from(rect))
|
||||
} else {
|
||||
unsafe { GetWindowRect(hwnd, &mut rect) }.process()?;
|
||||
Ok(Rect::from(rect))
|
||||
|
||||
@@ -39,6 +39,7 @@ use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::winevent_listener;
|
||||
use crate::BORDER_COLOUR_CURRENT;
|
||||
use crate::BORDER_RECT;
|
||||
@@ -186,7 +187,10 @@ pub extern "system" fn win_event_hook(
|
||||
|
||||
let window = Window { hwnd: hwnd.0 };
|
||||
|
||||
let winevent = unsafe { ::std::mem::transmute(event) };
|
||||
let winevent = match WinEvent::try_from(event) {
|
||||
Ok(event) => event,
|
||||
Err(_) => return,
|
||||
};
|
||||
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
|
||||
None => return,
|
||||
Some(event) => event,
|
||||
|
||||
@@ -178,3 +178,100 @@ pub enum WinEvent {
|
||||
UiaPropIdSEnd = EVENT_UIA_PROPID_END,
|
||||
UiaPropIdStart = EVENT_UIA_PROPID_START,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for WinEvent {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
EVENT_AIA_END => Ok(Self::AiaEnd),
|
||||
EVENT_AIA_START => Ok(Self::AiaStart),
|
||||
EVENT_CONSOLE_CARET => Ok(Self::ConsoleCaret),
|
||||
EVENT_CONSOLE_END => Ok(Self::ConsoleEnd),
|
||||
EVENT_CONSOLE_END_APPLICATION => Ok(Self::ConsoleEndApplication),
|
||||
EVENT_CONSOLE_LAYOUT => Ok(Self::ConsoleLayout),
|
||||
EVENT_CONSOLE_START_APPLICATION => Ok(Self::ConsoleStartApplication),
|
||||
EVENT_CONSOLE_UPDATE_REGION => Ok(Self::ConsoleUpdateRegion),
|
||||
EVENT_CONSOLE_UPDATE_SCROLL => Ok(Self::ConsoleUpdateScroll),
|
||||
EVENT_CONSOLE_UPDATE_SIMPLE => Ok(Self::ConsoleUpdateSimple),
|
||||
EVENT_OBJECT_ACCELERATORCHANGE => Ok(Self::ObjectAcceleratorChange),
|
||||
EVENT_OBJECT_CLOAKED => Ok(Self::ObjectCloaked),
|
||||
EVENT_OBJECT_CONTENTSCROLLED => Ok(Self::ObjectContentScrolled),
|
||||
EVENT_OBJECT_CREATE => Ok(Self::ObjectCreate),
|
||||
EVENT_OBJECT_DEFACTIONCHANGE => Ok(Self::ObjectDefActionChange),
|
||||
EVENT_OBJECT_DESCRIPTIONCHANGE => Ok(Self::ObjectDescriptionChange),
|
||||
EVENT_OBJECT_DESTROY => Ok(Self::ObjectDestroy),
|
||||
EVENT_OBJECT_DRAGCANCEL => Ok(Self::ObjectDragCancel),
|
||||
EVENT_OBJECT_DRAGCOMPLETE => Ok(Self::ObjectDragComplete),
|
||||
EVENT_OBJECT_DRAGDROPPED => Ok(Self::ObjectDragDropped),
|
||||
EVENT_OBJECT_DRAGENTER => Ok(Self::ObjectDragEnter),
|
||||
EVENT_OBJECT_DRAGLEAVE => Ok(Self::ObjectDragLeave),
|
||||
EVENT_OBJECT_DRAGSTART => Ok(Self::ObjectDragStart),
|
||||
EVENT_OBJECT_END => Ok(Self::ObjectEnd),
|
||||
EVENT_OBJECT_FOCUS => Ok(Self::ObjectFocus),
|
||||
EVENT_OBJECT_HELPCHANGE => Ok(Self::ObjectHelpChange),
|
||||
EVENT_OBJECT_HIDE => Ok(Self::ObjectHide),
|
||||
EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED => Ok(Self::ObjectHostedObjectsInvalidated),
|
||||
EVENT_OBJECT_IME_CHANGE => Ok(Self::ObjectImeChange),
|
||||
EVENT_OBJECT_IME_HIDE => Ok(Self::ObjectImeHide),
|
||||
EVENT_OBJECT_IME_SHOW => Ok(Self::ObjectImeShow),
|
||||
EVENT_OBJECT_INVOKED => Ok(Self::ObjectInvoked),
|
||||
EVENT_OBJECT_LIVEREGIONCHANGED => Ok(Self::ObjectLiveRegionChanged),
|
||||
EVENT_OBJECT_LOCATIONCHANGE => Ok(Self::ObjectLocationChange),
|
||||
EVENT_OBJECT_NAMECHANGE => Ok(Self::ObjectNameChange),
|
||||
EVENT_OBJECT_PARENTCHANGE => Ok(Self::ObjectParentChange),
|
||||
EVENT_OBJECT_REORDER => Ok(Self::ObjectReorder),
|
||||
EVENT_OBJECT_SELECTION => Ok(Self::ObjectSelection),
|
||||
EVENT_OBJECT_SELECTIONADD => Ok(Self::ObjectSelectionAdd),
|
||||
EVENT_OBJECT_SELECTIONREMOVE => Ok(Self::ObjectSelectionRemove),
|
||||
EVENT_OBJECT_SELECTIONWITHIN => Ok(Self::ObjectSelectionWithin),
|
||||
EVENT_OBJECT_SHOW => Ok(Self::ObjectShow),
|
||||
EVENT_OBJECT_STATECHANGE => Ok(Self::ObjectStateChange),
|
||||
EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED => {
|
||||
Ok(Self::ObjectTextEditConversionTargetChanged)
|
||||
}
|
||||
EVENT_OBJECT_TEXTSELECTIONCHANGED => Ok(Self::ObjectTextSelectionChanged),
|
||||
EVENT_OBJECT_UNCLOAKED => Ok(Self::ObjectUncloaked),
|
||||
EVENT_OBJECT_VALUECHANGE => Ok(Self::ObjectValueChange),
|
||||
EVENT_OEM_DEFINED_END => Ok(Self::OemDefinedEnd),
|
||||
EVENT_OEM_DEFINED_START => Ok(Self::OemDefinedStart),
|
||||
EVENT_SYSTEM_ALERT => Ok(Self::SystemAlert),
|
||||
EVENT_SYSTEM_ARRANGMENTPREVIEW => Ok(Self::SystemArrangementPreview),
|
||||
EVENT_SYSTEM_CAPTUREEND => Ok(Self::SystemCaptureEnd),
|
||||
EVENT_SYSTEM_CAPTURESTART => Ok(Self::SystemCaptureStart),
|
||||
EVENT_SYSTEM_CONTEXTHELPEND => Ok(Self::SystemContextHelpEnd),
|
||||
EVENT_SYSTEM_CONTEXTHELPSTART => Ok(Self::SystemContextHelpStart),
|
||||
EVENT_SYSTEM_DESKTOPSWITCH => Ok(Self::SystemDesktopSwitch),
|
||||
EVENT_SYSTEM_DIALOGEND => Ok(Self::SystemDialogEnd),
|
||||
EVENT_SYSTEM_DIALOGSTART => Ok(Self::SystemDialogStart),
|
||||
EVENT_SYSTEM_DRAGDROPEND => Ok(Self::SystemDragDropEnd),
|
||||
EVENT_SYSTEM_DRAGDROPSTART => Ok(Self::SystemDragDropStart),
|
||||
EVENT_SYSTEM_END => Ok(Self::SystemEnd),
|
||||
EVENT_SYSTEM_FOREGROUND => Ok(Self::SystemForeground),
|
||||
EVENT_SYSTEM_IME_KEY_NOTIFICATION => Ok(Self::SystemImeKeyNotification),
|
||||
EVENT_SYSTEM_MENUEND => Ok(Self::SystemMenuEnd),
|
||||
EVENT_SYSTEM_MENUPOPUPEND => Ok(Self::SystemMenuPopupEnd),
|
||||
EVENT_SYSTEM_MENUPOPUPSTART => Ok(Self::SystemMenuPopupStart),
|
||||
EVENT_SYSTEM_MENUSTART => Ok(Self::SystemMenuStart),
|
||||
EVENT_SYSTEM_MINIMIZEEND => Ok(Self::SystemMinimizeEnd),
|
||||
EVENT_SYSTEM_MINIMIZESTART => Ok(Self::SystemMinimizeStart),
|
||||
EVENT_SYSTEM_MOVESIZEEND => Ok(Self::SystemMoveSizeEnd),
|
||||
EVENT_SYSTEM_MOVESIZESTART => Ok(Self::SystemMoveSizeStart),
|
||||
EVENT_SYSTEM_SCROLLINGEND => Ok(Self::SystemScrollingEnd),
|
||||
EVENT_SYSTEM_SCROLLINGSTART => Ok(Self::SystemScrollingStart),
|
||||
EVENT_SYSTEM_SOUND => Ok(Self::SystemSound),
|
||||
EVENT_SYSTEM_SWITCHEND => Ok(Self::SystemSwitchEnd),
|
||||
EVENT_SYSTEM_SWITCHER_APPDROPPED => Ok(Self::SystemSwitcherAppDropped),
|
||||
EVENT_SYSTEM_SWITCHER_APPGRABBED => Ok(Self::SystemSwitcherAppGrabbed),
|
||||
EVENT_SYSTEM_SWITCHER_APPOVERTARGET => Ok(Self::SystemSwitcherAppOverTarget),
|
||||
EVENT_SYSTEM_SWITCHER_CANCELLED => Ok(Self::SystemSwitcherCancelled),
|
||||
EVENT_SYSTEM_SWITCHSTART => Ok(Self::SystemSwitchStart),
|
||||
EVENT_UIA_EVENTID_END => Ok(Self::UiaEventIdSEnd),
|
||||
EVENT_UIA_EVENTID_START => Ok(Self::UiaEventIdStart),
|
||||
EVENT_UIA_PROPID_END => Ok(Self::UiaPropIdSEnd),
|
||||
EVENT_UIA_PROPID_START => Ok(Self::UiaPropIdStart),
|
||||
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,11 +203,7 @@ impl Workspace {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
work_area: &Rect,
|
||||
offset: Option<Rect>,
|
||||
) -> Result<()> {
|
||||
pub fn update(&mut self, work_area: &Rect, offset: Option<Rect>) -> Result<()> {
|
||||
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1077,8 +1077,9 @@ enum SubCommand {
|
||||
WatchConfiguration(WatchConfiguration),
|
||||
/// Signal that the final configuration option has been sent
|
||||
CompleteConfiguration,
|
||||
/// Enable or disable a hack simulating ALT key presses to ensure focus changes succeed
|
||||
/// DEPRECATED since v0.1.22
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(hide = true)]
|
||||
AltFocusHack(AltFocusHack),
|
||||
/// Set the window behaviour when switching workspaces / cycling stacks
|
||||
#[clap(arg_required_else_help = true)]
|
||||
|
||||
Reference in New Issue
Block a user