Compare commits

..

1 Commits

Author SHA1 Message Date
LGUG2Z
58295a15bf test(wm): don't rely on datadir for socket location
This commit avoids relying on DATA_DIR for the test socket location as
this path is not available to us in GitHub Actions runners.
2025-03-07 16:01:47 -08:00
48 changed files with 469 additions and 3568 deletions

580
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,6 @@ members = [
[workspace.dependencies]
clap = { version = "4", features = ["derive", "wrap_help"] }
chrono-tz = "0.10"
chrono = "0.4"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
@@ -35,16 +34,16 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
paste = "1"
sysinfo = "0.33"
uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "93949750b1f123fb79827ba4d66ffcab68055654" }
windows-numerics = { version = "0.2" }
windows-implement = { version = "0.60" }
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "55cebdebfbd68dbd14945a1ba90f6b05b7be2893" }
windows-numerics = { version = "0.1" }
windows-implement = { version = "0.59" }
windows-interface = { version = "0.59" }
windows-core = { version = "0.61" }
windows-core = { version = "0.60" }
shadow-rs = "1"
which = "7"
[workspace.dependencies.windows]
version = "0.61"
version = "0.60"
features = [
"Foundation_Numerics",
"Win32_Devices",

View File

@@ -71,9 +71,6 @@ showcases the many awesome projects that exist in the _komorebi_ ecosystem.
## Licensing for Personal Use
`komorebi` is [educational source
software](https://lgug2z.com/articles/educational-source-software/).
`komorebi` is licensed under the [Komorebi 1.0.0
license](https://github.com/LGUG2Z/komorebi-license), which is a fork of the
[PolyForm Strict 1.0.0
@@ -102,8 +99,7 @@ me on GitHub.
[GitHub Sponsors is enabled for this
project](https://github.com/sponsors/LGUG2Z). Sponsors can claim custom roles on
the Discord server, get shout outs at the end of _komorebi_-related videos on
YouTube, gain the ability to submit feature requests on the issue tracker, and
receive releases of komorebi with "easter eggs" on physical media.
YouTube, and gain the ability to submit feature requests on the issue tracker.
If you would like to tip or sponsor the project but are unable to use GitHub
Sponsors, you may also sponsor through [Ko-fi](https://ko-fi.com/lgug2z), or
@@ -393,7 +389,7 @@ every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust c
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.35"}
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.34"}
use anyhow::Result;
use komorebi_client::Notification;

View File

@@ -1,412 +0,0 @@
# Multi-Monitor Setup
You can set up komorebi to work with multiple monitors. To do so, first you start by setting up multiple monitor
configurations on your `komorebi.json` config file.
If you've used the [`komorebic quickstart`](../cli/quickstart.md) command you'll already have a `komorebi.json` config
file with one monitor config setup. Open this file and look for the `"monitors":` line, you should find something like
this:
```json
{
"monitors": [
{
"workspaces": [
{
"name": "I",
"layout": "BSP"
},
{
"name": "II",
"layout": "VerticalStack"
},
{
"name": "III",
"layout": "HorizontalStack"
},
{
"name": "IV",
"layout": "UltrawideVerticalStack"
},
{
"name": "V",
"layout": "Rows"
},
{
"name": "VI",
"layout": "Grid"
},
{
"name": "VII",
"layout": "RightMainVerticalStack"
}
]
}
]
}
```
For this example we will remove some workspaces to simplify the config so it is easier to look at, but feel free to
set up as many workspaces per monitor as you'd like. Here is the same configuration with only 3 workspaces.
```json
{
"monitors": [
{
"workspaces": [
{
"name": "I",
"layout": "BSP"
},
{
"name": "II",
"layout": "VerticalStack"
},
{
"name": "III",
"layout": "HorizontalStack"
}
]
}
]
}
```
Let's add another monitor:
```json
{
"monitors": [
// monitor 1, index 0
{
"workspaces": [
{
"name": "I",
"layout": "BSP"
},
{
"name": "II",
"layout": "VerticalStack"
},
{
"name": "III",
"layout": "HorizontalStack"
}
]
},
// monitor 2, index 1
{
"workspaces": [
{
"name": "1",
"layout": "BSP"
},
{
"name": "2",
"layout": "VerticalStack"
},
{
"name": "3",
"layout": "HorizontalStack"
}
]
}
]
}
```
Now have two monitor configurations. We have the first monitor configuration, which is index 0 (*usually
on programming languages the first item of a list starts with index 0*), this configuration has 3 workspaces with names
"I", "II" and "III". Then the 2nd monitor configuration, which is index 1, also has 3 workspaces with names "1", "2",
and "3" (you should always give unique names to your workspaces).
Now if you start komorebi with two monitors connected, the main monitor will use the configuration with index 0 and the
secondary monitor will use the configuration with index 1.
---
Let's say you have more monitors, or you want to make sure that a certain configuration is always applied to a certain
monitor. For this you will want to use the `display_index_preferences`.
Open up a terminal and type the following command: [ `komorebic monitor-info`](../cli/monitor-information.md). This
command will give you the information about your connected monitors, you want to look up the `serial_number_id`. You
should get something like this:
```
komorebic monitor-info
[
{
"id": 6620935,
"name": "DISPLAY1",
"device": "BOE0A1C",
"device_id": "BOE0A1C-5&a2bea0b&0&UID512",
"serial_number_id": "0",
"size": {
"left": 0,
"top": 0,
"right": 1920,
"bottom": 1080
}
},
{
"id": 181932057,
"name": "DISPLAY2",
"device": "VSC8C31",
"device_id": "VSC8C31-5&18560b1f&0&UID4356",
"serial_number_id": "UEP174021562",
"size": {
"left": 0,
"top": -1080,
"right": 1920,
"bottom": 1080
}
}
]
```
In this case the setup is a laptop with a secondary monitor connected. You'll need to figure out which monitor is which,
usually the display name's number should be similar to the numbers you can find on
`Windows Settings -> System -> Display`.
If you have trouble with this step you can always jump on Discord and ask for help (create a `Support` thread).
Once you know which monitor is which, you want to look up their `serial_number_id` to use that on
`display_index_preferences`, you can also use the `device_id`, it accepts both however there have been reported cases
where the `device_id` changes after a restart while the `serial_number_id` doesn't.
So with the example above, we want the laptop to always use the configuration index 0 and the other monitor to use
configuration index 1, so we map the configuration index number to the monitor `serial_number_id`/`device_id` like this:
```json
{
"display_index_preferences": {
"0": "0",
"1": "UEP174021562"
}
}
```
Again you could also have used the `device_id` like this:
```json
{
"display_index_preferences": {
"0": "BOE0A1C-5&a2bea0b&0&UID512",
"1": "VSC8C31-5&18560b1f&0&UID4356"
}
}
```
You should add this `display_index_preferences` option to your `komorebi.json` file. If you find that something is
not working as expected you can try to use the command `komorebic check`.
> [!IMPORTANT]
>
> **When using multiple monitors it is recommended to always set the `display_index_preferences`. If you don't you might
get some undefined behaviour.**
---
If you would like to run multiple instances of `komorebi-bar` to target different monitors, it is possible to do so
using the `bar_configurations` array in your `komorebi.json` configuration file. You can refer to the
[multiple-bar-instances](multiple-bar-instances.md) documentation.
In this case it is important to use `display_index_preferences`, because if you don't, and you have 3 or more monitors,
disconnecting and reconnecting monitors may result in the bars for the monitors getting shifted around.
Consider this setup with 3 monitors (A, B and C):
```json
// HOME_MONITOR_1_BAR.json
{
"monitor_index": 0
//...
}
```
```json
// HOME_MONITOR_2_BAR.json
{
"monitor_index": 1
//...
}
```
```json
// WORK_MONITOR_1_BAR.json
{
"monitor_index": 2
//...
}
```
```json
{
"display_index_preferences": {
"0": "MONITOR_1_ID",
"1": "MONITOR_2_ID",
"2": "MONITOR_3_ID"
},
"bar_configurations": [
// this bar uses "monitor_index": 0,
"path/to/bar_config_1.json",
// this bar uses "monitor_index": 1,
"path/to/bar_config_2.json",
// this bar uses "monitor_index": 2,
"path/to/bar_config_3.json"
]
}
```
Komorebi uses an internal map to keep track of monitor to config indices, this map is called `monitor_usr_idx_map` it is
an internal variable to komorebi that you don't need to do anything with, but you can see it with the [
`komorebic state`](../cli/state.md) command (in case you need to debug something).
At first, komorebi will load all monitors and set the internal index map (`monitor_usr_idx_map`) as:
```json
{
// This is monitor A
"0": 0,
// This is monitor B
"1": 1,
// This is monitor C
"2": 2
}
```
Which kind of seems unnecessary, but imagine that then you disconnect monitor B (or it goes to sleep). Then komorebi
will only have 2 monitors with index 0 and 1, so the above map will be updated to this:
```jsonc
[
"0": 0, // This is monitor A
"2": 1, // This is now monitor C, because monitor B disconnected
]
```
So now the bar intended to be for monitor B, which was looking for index "1" on that map, doesn't see it and knows it
should be disabled. And the bar for monitor C looks at that map and knows that it's index "2" now maps to index 1 so it
uses that index internally to get all the correct values about the monitor.
If you didn't have the `display_index_preferences` set, then when you disconnected monitor B, komorebi wouldn't know
how to map the indices and would use default behaviour which would result in a map like this:
```json
{
// This is monitor A
"0": 0,
// This is monitor C, because monitor B disconnected. However the bars will think it is monitor B because it has index "1"
"1": 1
}
```
# Multiple Monitors on different machines
You can use the same `komorebi.json` to configure two different setups and then synchronize your config across machines.
However, if you do this it is important to be aware of a few things.
Firstly, using `display_index_preferences` is required in this case.
You will need to get the `serial_number_id` or `device_id` of all the monitors of all your setups. With that information
you would then set your config like this:
```json
{
"display_index_preferences": {
"0": "HOME_MONITOR_1_ID",
"1": "HOME_MONITOR_2_ID",
"2": "WORK_MONITOR_1_ID",
"3": "WORK_MONITOR_2_ID"
},
"monitors": [
// HOME_MONITOR_1
{
"workspaces": [
// ...
]
},
// HOME_MONITOR_2
{
"workspaces": [
// ...
]
},
// WORK_MONITOR_1
{
"workspaces": [
// ...
]
},
// WORK_MONITOR_2
{
"workspaces": [
// ...
]
}
]
}
```
> [!NOTE]
>
> *You can't use the same config on two different monitors, you have to make a duplicated config for each monitor!*
Then on the bar configs you need to set the bar's monitor index like this:
```json
// HOME_MONITOR_1_BAR.json
{
"monitor_index": 0
//...
}
```
```json
// HOME_MONITOR_2_BAR.json
{
"monitor_index": 1
//...
}
```
```json
// WORK_MONITOR_1_BAR.json
{
"monitor_index": 2
//...
}
```
```json
// WORK_MONITOR_2_BAR.json
{
"monitor_index": 3
//...
}
```
Although you will only ever have 2 monitors connected at any one time, and they'll always have index 0 and 1, the
above config will still work on both physical configurations.
This is because komorebi will apply the appropriate config to the loaded monitors and will create a map of the user
index (the index defined in the user config) to the actual monitor index, and the bar will use that map to know if it
should be enabled, and where it should be drawn.
### Things to keep in mind
* If you are using a laptop connected to one monitor at work and a different one at home, the work monitor and the home
monitor are considered different monitors by komorebi
* When you disconnect from work, komorebi will keep the work monitor cached
* You can still use a laptop alone without any monitor and if you need a window that was on the other monitor you can
press the taskbar icon or use `alt + tab` to bring it to focus and that window will now be part of the laptop monitor
* If you then reconnect the work monitor, the cached version will be applied with all its windows (except any window(s)
you might have moved to another monitor)
* If however, instead of reconnecting the work monitor, you connect the home monitor, then the work monitor will still
remain cached, and komorebi will load the home monitor from the cache (if it exists)
* Sometimes when you disconnect/reconnect a monitor the event might be missed by komorebi, meaning that Windows will
show you both monitors but komorebi won't know about the existence of one of them
* If you notice this type of weird behaviour, always run the [
`komorebic monitor-info`](../cli/monitor-information.md)
command and validate if one of the monitors is missing
* To fix this you can try disconnecting and reconnecting the monitor again, or restarting komorebi

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.35/schema.bar.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.bar.json",
"monitor": 0,
"font_family": "JetBrains Mono",
"theme": {

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.35/schema.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",

View File

@@ -1,46 +0,0 @@
# Focusing Windows
Windows can be focused in a direction (left, down, up, right) using the [`komorebic focus`](../cli/focus.md) command.
```
# example showing how you might bind this command
alt + h : komorebic focus left
alt + j : komorebic focus down
alt + k : komorebic focus up
alt + l : komorebic focus right
```
Windows can be focused in a cycle direction (previous, next) using the [`komorebic cycle-focus`](../cli/cycle-focus.md)
command.
```
# example showing you might bind this command
alt + shift + oem_4 : komorebic cycle-focus previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-focus next # oem_6 is ]
```
It is possible to attempt to focus the first window, on any workspace, matching an exe using the [
`komorebic eager-focus`](../cli/eager-focus.md) command.
```
# example showing how you might bind this command
win + 1 : komorebic eager-focus firefox.exe
```
The window at the largest tile can be focused using the [`komorebic promote-focus`](../cli/promote-focus.md) command.
```
# example showing how you might bind this command
alt + return : komorebic promote-focus
```
The behaviour when attempting to call `komorebic focus` when at the left or right edge of a monitor is determined by
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
When set to `Workspace`, the next workspace on the same monitor will be focused.
When set to `Monitor`, the focused workspace on the next monitor in the given direction will be focused.

View File

@@ -1,59 +0,0 @@
# Focusing Workspaces
Workspaces on the focused monitor can be focused by their index using the [
`komorebic focus-workspace`](../cli/focus-workspace.md) command.
If this command is called with an index for a workspace which does not exist, that workspace, and all workspace indexes
required to get to that workspace, will be created.
```
# example showing how you might bind this command
alt + 1 : komorebic focus-workspace 0
alt + 2 : komorebic focus-workspace 1
alt + 3 : komorebic focus-workspace 2
```
Workspaces on the focused monitor can be focused in a cycle direction (previous, next) using the [
`komorebic cycle-workspace`](../cli/cycle-workspace.md) command.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-workspace previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-workspace next # oem_6 is ]
```
Workspaces on other monitors can be focused by both the monitor index and the workspace index using the [
`komorebic focus-monitor-workspace`](../cli/focus-monitor-workspace.md) command.
```
# example showing how you might bind this command
alt + 1 : komorebic focus-monitor-workspace 0 0
alt + 2 : komorebic focus-monitor-workspace 0 1
alt + 3 : komorebic focus-monitor-workspace 1 0
```
Workspaces on any monitor can be focused by their name (given that all workspace names across all monitors are unique)
using the [`komorebic focus-named-workspace`](../cli/focus-named-workspace.md) command.
```
# example showing how you might bind this command
alt + c : komorebic focus-named-workspace coding
```
Workspaces on all monitors can be set to the same index (emulating single workspaces which span across all monitors)
using the [`komorebic focus-workspaces`](../cli/focus-workspaces.md) command.
```
# example showing how you might bind this command
alt + 1 : komorebic focus-workspaces 0
alt + 2 : komorebic focus-workspaces 1
alt + 3 : komorebic focus-workspaces 2
```
The last focused workspace on the focused monitor can be re-focused using the [
`komorebic focus-last-workspace`](../cli/focus-last-workspace) command.

View File

@@ -1,59 +0,0 @@
# Moving Windows Across Workspaces
Windows can be moved to another workspace on the focused monitor using the [
`komorebic move-to-workspace`](../cli/move-to-workspace.md) command. This command will also move your focus to the
target workspace.
```
# example showing how you might bind this command
alt + shift + 1 : komorebic move-to-workspace 0
alt + shift + 2 : komorebic move-to-workspace 1
alt + shift + 3 : komorebic move-to-workspace 2
```
Windows can be sent to another workspace on the focused monitor using the [
`komorebic send-to-workspace`](../cli/send-to-workspace.md) command. This command will keep your focus on the origin
workspace.
```
# example showing how you might bind this command
alt + shift + 1 : komorebic send-to-workspace 0
alt + shift + 2 : komorebic send-to-workspace 1
alt + shift + 3 : komorebic send-to-workspace 2
```
Windows can be moved to another workspace on the focused monitor in a cycle direction (previous, next) using the [
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will also move your focus
to the target workspace.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-move-to-workspace previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-move-to-workspace next # oem_6 is ]
```
Windows can be sent to another workspace on the focused monitor in a cycle direction (previous, next) using the [
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will keep your focus on
the origin workspace.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-send-to-workspace previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-send-to-workspace next # oem_6 is ]
```
Windows can be moved or sent to the focused workspace on a another monitor using the [
`komorebic move-to-monitor`](../cli/move-to-monitor.md) and [`komorebic send-to-monitor`](../cli/send-to-monitor)
commands.
Windows can be moved or sent to the focused workspace on a monitor in a cycle direction (previous, next) using the [
`komorebic cycle-move-to-monitor`](../cli/cycle-move-to-monitor.md) and [
`komorebic cycle-send-to-monitor`](../cli/cycle-send-to-monitor.md) commands.
Windows can be moved or sent to a named workspace on any monitor (given that all workspace names across all monitors are
unique) using the [`komorebic move-to-named-workspace`](../cli/move-to-named-workspace.md) and [
`komorebic send-to-named-workspace`](../cli/send-to-named-workspace.md) commands

View File

@@ -1,50 +0,0 @@
# Moving Windows
Windows can be moved in a direction (left, down, up, right) using the [`komorebic move`](../cli/move.md) command.
```
# example showing how you might bind this command
alt + shift + h : komorebic move left
alt + shift + j : komorebic move down
alt + shift + k : komorebic move up
alt + shift + l : komorebic move right
```
Windows can be moved in a cycle direction (previous, next) using the [`komorebic cycle-move`](../cli/cycle-move.md)
command.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-move previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-move next # oem_6 is ]
```
The focused window can be moved to the largest tile using the [`komorebic promote`](../cli/promote.md) command.
```
# example showing how you might bind this command
alt + shift + return : komorebic promote
```
The behaviour when attempting to call `komorebic move` when at the left or right edge of a monitor is determined by
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
When set to `Workspace`, the focused window will be moved to the next workspace on the focused monitor in the given
direction
When set to `Monitor`, the focused window will be moved to the focused workspace on the next monitor in the given
direction.
The behaviour when calling `komorebic move` with `cross_boundary_behaviour` set to `Monitor` can be further refined with
the [`cross_monitor_move_behaviour`](https://komorebi.lgug2z.com/schema#cross_monitor_move_behaviour) configuration
option.
When set to `Swap`, the focused window will be swapped with the window at the corresponding edge of the adjacent monitor
When set to `Insert`, the focused window will be inserted into the focused workspace on the adjacent monitor.
When set to `NoOp`, the focused window will not be moved across a monitor boundary, though focusing across monitor
boundaries will continue to function.

View File

@@ -1,52 +0,0 @@
# Stacking Windows
Windows can be stacked in a direction (left, down, up, right) using the [`komorebic stack`](../cli/stack.md) command.
```
# example showing how you might bind this command
alt + left : komorebic stack left
alt + down : komorebic stack down
alt + up : komorebic stack up
alt + right : komorebic stack right
```
Windows can be popped from a stack using the [`komorebic unstack`](../cli/unstack.md) command.
```
# example showing how you might bind this command
alt + oem_1 : komorebic unstack # oem_1 is ;
```
Windows in a stack can be focused in a cycle direction (previous, next) using the [
`komorebic cycle-stack`](../cli/cycle-stack.md) command.
```
# example showing how you might bind this command
alt + oem_4 : komorebic cycle-stack previous # oem_4 is [
alt + oem_6 : komorebic cycle-stack next # oem_6 is ]
```
Windows in a stack can have their positions in the stack moved in a cycle direction (previous, next) using the [
`komorebic cycle-stack-index`](../cli/cycle-stack-index.md) command.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-stack-index previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-stack-index next # oem_6 is ]
```
Windows in a stack can be focused by their index in the stack using the [
`komorebic focus-stack-window`](../cli/focus-stack-window.md) command.
All windows on the focused workspace can be combined into a single stack using the [
`komorebic stack-all`](../cli/stack-all.md) command.
All windows in a focused stack can be popped using the [`komorebic unstack-all`](../cli/unstack-all.md) command.
It is possible to tell the window manager to stack the next opened window on top of the currently focused window by
using the [
`komorebic toggle-workspace-window-container-behaviour`](../cli/toggle-workspace-window-container-behaviour.md) command.

View File

@@ -9,7 +9,6 @@ edition = "2021"
komorebi-client = { path = "../komorebi-client" }
komorebi-themes = { path = "../komorebi-themes" }
chrono-tz = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
color-eyre = { workspace = true }
@@ -21,12 +20,11 @@ egui-phosphor = "0.9"
font-loader = "0.11"
hotwatch = { workspace = true }
image = "0.25"
lazy_static = { workspace = true }
netdev = "0.33"
netdev = "0.32"
num = "0.4"
num-derive = "0.4"
num-traits = "0.2"
random_word = { version = "0.5", features = ["en"] }
random_word = { version = "0.4", features = ["en"] }
reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true, optional = true }
serde = { workspace = true }

View File

@@ -4,15 +4,15 @@ use crate::config::KomobarTheme;
use crate::config::MonitorConfigOrIndex;
use crate::config::Position;
use crate::config::PositionConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiNotificationState;
use crate::process_hwnd;
use crate::render::Color32Ext;
use crate::render::Grouping;
use crate::render::RenderConfig;
use crate::render::RenderExt;
use crate::widgets::komorebi::Komorebi;
use crate::widgets::komorebi::KomorebiNotificationState;
use crate::widgets::widget::BarWidget;
use crate::widgets::widget::WidgetConfig;
use crate::widget::BarWidget;
use crate::widget::WidgetConfig;
use crate::KomorebiEvent;
use crate::BAR_HEIGHT;
use crate::DEFAULT_PADDING;

View File

@@ -1,7 +1,7 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;

View File

@@ -1,5 +1,5 @@
use crate::render::Grouping;
use crate::widgets::widget::WidgetConfig;
use crate::widget::WidgetConfig;
use crate::DEFAULT_PADDING;
use eframe::egui::Pos2;
use eframe::egui::TextBuffer;

View File

@@ -1,7 +1,7 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;

View File

@@ -1,9 +1,7 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use chrono::Local;
use chrono_tz::Tz;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
@@ -13,8 +11,6 @@ use eframe::egui::Ui;
use eframe::egui::WidgetText;
use serde::Deserialize;
use serde::Serialize;
use std::time::Duration;
use std::time::Instant;
/// Custom format with additive modifiers for integer format specifiers
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -42,7 +38,7 @@ impl CustomModifiers {
}
// get the strftime value of modifier
let formatted_modifier = Local::now().format(modifier).to_string();
let formatted_modifier = chrono::Local::now().format(modifier).to_string();
// find the gotten value in the original output
if let Some(pos) = modified_output.find(&formatted_modifier) {
@@ -68,35 +64,14 @@ pub struct DateConfig {
pub format: DateFormat,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
///
/// Use a custom format to display additional information, i.e.:
/// ```json
/// {
/// "Date": {
/// "enable": true,
/// "format": { "Custom": "%D %Z (Tokyo)" },
/// "timezone": "Asia/Tokyo"
/// }
///}
/// ```
pub timezone: Option<String>,
}
impl From<DateConfig> for Date {
fn from(value: DateConfig) -> Self {
let data_refresh_interval = 1;
Self {
enable: value.enable,
format: value.format,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
timezone: value.timezone,
data_refresh_interval,
last_state: String::new(),
last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
}
}
}
@@ -146,46 +121,19 @@ pub struct Date {
pub enable: bool,
pub format: DateFormat,
label_prefix: LabelPrefix,
timezone: Option<String>,
data_refresh_interval: u64,
last_state: String,
last_updated: Instant,
}
impl Date {
fn output(&mut self) -> String {
let mut output = self.last_state.clone();
let now = Instant::now();
let formatted = chrono::Local::now()
.format(&self.format.fmt_string())
.to_string();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
let formatted = match &self.timezone {
Some(timezone) => match timezone.parse::<Tz>() {
Ok(tz) => Local::now()
.with_timezone(&tz)
.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
Err(_) => format!("Invalid timezone: {}", timezone),
},
None => Local::now()
.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
};
// if custom modifiers are used, apply them
output = match &self.format {
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
_ => formatted,
};
self.last_state.clone_from(&output);
self.last_updated = now;
// if custom modifiers are used, apply them
match &self.format {
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
_ => formatted,
}
output
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;

View File

@@ -2,12 +2,12 @@ use crate::bar::apply_theme;
use crate::config::DisplayFormat;
use crate::config::KomobarTheme;
use crate::config::WorkspacesDisplayFormat;
use crate::komorebi_layout::KomorebiLayout;
use crate::render::Grouping;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi;
use crate::widgets::komorebi_layout::KomorebiLayout;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use crate::ICON_CACHE;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_INDEX;
@@ -403,7 +403,6 @@ impl BarWidget for Komorebi {
if layer_frame.clicked()
&& komorebi_client::send_batch([
SocketMessage::FocusMonitorAtCursor,
SocketMessage::MouseFollowsFocus(false),
SocketMessage::ToggleWorkspaceLayer,
SocketMessage::MouseFollowsFocus(

View File

@@ -1,7 +1,7 @@
use crate::config::DisplayFormat;
use crate::komorebi::KomorebiLayoutConfig;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::komorebi::KomorebiLayoutConfig;
use eframe::egui::vec2;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
@@ -105,22 +105,12 @@ impl KomorebiLayout {
}
}
KomorebiLayout::Monocle => {
if komorebi_client::send_batch([
SocketMessage::FocusMonitorAtCursor,
SocketMessage::ToggleMonocle,
])
.is_err()
{
if komorebi_client::send_message(&SocketMessage::ToggleMonocle).is_err() {
tracing::error!("could not send message to komorebi: ToggleMonocle");
}
}
KomorebiLayout::Floating => {
if komorebi_client::send_batch([
SocketMessage::FocusMonitorAtCursor,
SocketMessage::ToggleTiling,
])
.is_err()
{
if komorebi_client::send_message(&SocketMessage::ToggleTiling).is_err() {
tracing::error!("could not send message to komorebi: ToggleTiling");
}
}

View File

@@ -1,9 +1,21 @@
mod bar;
mod battery;
mod config;
mod cpu;
mod date;
mod keyboard;
mod komorebi;
mod komorebi_layout;
mod media;
mod memory;
mod network;
mod render;
mod selected_frame;
mod storage;
mod time;
mod ui;
mod widgets;
mod update;
mod widget;
use crate::bar::Komobar;
use crate::config::KomobarConfig;
@@ -337,7 +349,7 @@ fn main() -> color_eyre::Result<()> {
let ctx_komorebi = cc.egui_ctx.clone();
std::thread::spawn(move || {
let subscriber_name = format!("komorebi-bar-{}", random_word::get(random_word::Lang::En));
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
filter_state_changes: true,

View File

@@ -1,7 +1,7 @@
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;

View File

@@ -1,7 +1,7 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;

View File

@@ -1,7 +1,7 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;

View File

@@ -1,7 +1,7 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;

View File

@@ -2,10 +2,7 @@ use crate::bar::Alignment;
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use chrono::Local;
use chrono::NaiveTime;
use chrono_tz::Tz;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
@@ -17,58 +14,8 @@ use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::epaint::StrokeKind;
use lazy_static::lazy_static;
use serde::Deserialize;
use serde::Serialize;
use std::time::Duration;
use std::time::Instant;
lazy_static! {
static ref TIME_RANGES: Vec<(&'static str, NaiveTime)> = {
vec![
(
egui_phosphor::regular::MOON,
NaiveTime::from_hms_opt(0, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::ALARM,
NaiveTime::from_hms_opt(6, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::BREAD,
NaiveTime::from_hms_opt(6, 1, 0).expect("invalid"),
),
(
egui_phosphor::regular::BARBELL,
NaiveTime::from_hms_opt(6, 30, 0).expect("invalid"),
),
(
egui_phosphor::regular::COFFEE,
NaiveTime::from_hms_opt(8, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::CLOCK,
NaiveTime::from_hms_opt(8, 30, 0).expect("invalid"),
),
(
egui_phosphor::regular::HAMBURGER,
NaiveTime::from_hms_opt(12, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::CLOCK_AFTERNOON,
NaiveTime::from_hms_opt(12, 30, 0).expect("invalid"),
),
(
egui_phosphor::regular::FORK_KNIFE,
NaiveTime::from_hms_opt(18, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::MOON_STARS,
NaiveTime::from_hms_opt(18, 30, 0).expect("invalid"),
),
]
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@@ -79,40 +26,14 @@ pub struct TimeConfig {
pub format: TimeFormat,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
///
/// Use a custom format to display additional information, i.e.:
/// ```json
/// {
/// "Time": {
/// "enable": true,
/// "format": { "Custom": "%T %Z (Tokyo)" },
/// "timezone": "Asia/Tokyo"
/// }
///}
/// ```
pub timezone: Option<String>,
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)
pub changing_icon: Option<bool>,
}
impl From<TimeConfig> for Time {
fn from(value: TimeConfig) -> Self {
// using 1 second made the widget look "less accurate" and lagging (especially having multiple with seconds).
// This is still better than getting an update every frame
let data_refresh_interval = 500;
Self {
enable: value.enable,
format: value.format,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
timezone: value.timezone,
changing_icon: value.changing_icon.unwrap_or_default(),
data_refresh_interval_millis: data_refresh_interval,
last_state: TimeOutput::new(),
last_updated: Instant::now()
.checked_sub(Duration::from_millis(data_refresh_interval))
.unwrap(),
}
}
}
@@ -162,94 +83,20 @@ impl TimeFormat {
}
}
#[derive(Clone, Debug)]
struct TimeOutput {
label: String,
icon: String,
}
impl TimeOutput {
fn new() -> Self {
Self {
label: String::new(),
icon: String::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct Time {
pub enable: bool,
pub format: TimeFormat,
label_prefix: LabelPrefix,
timezone: Option<String>,
changing_icon: bool,
data_refresh_interval_millis: u64,
last_state: TimeOutput,
last_updated: Instant,
}
impl Time {
fn output(&mut self) -> TimeOutput {
let mut output = self.last_state.clone();
let now = Instant::now();
if now.duration_since(self.last_updated)
> Duration::from_millis(self.data_refresh_interval_millis)
{
let (formatted, current_time) = match &self.timezone {
Some(timezone) => match timezone.parse::<Tz>() {
Ok(tz) => {
let dt = Local::now().with_timezone(&tz);
(
dt.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
Some(dt.time()),
)
}
Err(_) => (format!("Invalid timezone: {:?}", timezone), None),
},
None => {
let dt = Local::now();
(
dt.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
Some(dt.time()),
)
}
};
if current_time.is_none() {
return TimeOutput {
label: formatted,
icon: egui_phosphor::regular::WARNING_CIRCLE.to_string(),
};
}
let current_range = match &self.changing_icon {
true => TIME_RANGES
.iter()
.rev()
.find(|&(_, start)| current_time.unwrap() > *start)
.cloned(),
false => None,
}
.unwrap_or((egui_phosphor::regular::CLOCK, NaiveTime::default()));
output = TimeOutput {
label: formatted,
icon: current_range.0.to_string(),
};
self.last_state.clone_from(&output);
self.last_updated = now;
}
output
fn output(&mut self) -> String {
chrono::Local::now()
.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string()
}
fn paint_binary_circle(
@@ -432,13 +279,15 @@ impl BarWidget for Time {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let mut output = self.output();
if !output.label.is_empty() {
let use_binary_circle = output.label.starts_with('c');
let use_binary_rectangle = output.label.starts_with('r');
if !output.is_empty() {
let use_binary_circle = output.starts_with('c');
let use_binary_rectangle = output.starts_with('r');
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => output.icon,
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::CLOCK.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
@@ -447,12 +296,12 @@ impl BarWidget for Time {
);
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
output.label.insert_str(0, "TIME: ");
output.insert_str(0, "TIME: ");
}
if !use_binary_circle && !use_binary_rectangle {
layout_job.append(
&output.label,
&output,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
@@ -475,9 +324,9 @@ impl BarWidget for Time {
if use_binary_circle || use_binary_rectangle {
let ordered_output = if is_reversed {
output.label.chars().rev().collect()
output.chars().rev().collect()
} else {
output.label
output
};
for (section_index, section) in

View File

@@ -1,7 +1,7 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;

View File

@@ -1,26 +1,26 @@
use crate::battery::Battery;
use crate::battery::BatteryConfig;
use crate::cpu::Cpu;
use crate::cpu::CpuConfig;
use crate::date::Date;
use crate::date::DateConfig;
use crate::keyboard::Keyboard;
use crate::keyboard::KeyboardConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiConfig;
use crate::media::Media;
use crate::media::MediaConfig;
use crate::memory::Memory;
use crate::memory::MemoryConfig;
use crate::network::Network;
use crate::network::NetworkConfig;
use crate::render::RenderConfig;
use crate::widgets::battery::Battery;
use crate::widgets::battery::BatteryConfig;
use crate::widgets::cpu::Cpu;
use crate::widgets::cpu::CpuConfig;
use crate::widgets::date::Date;
use crate::widgets::date::DateConfig;
use crate::widgets::keyboard::Keyboard;
use crate::widgets::keyboard::KeyboardConfig;
use crate::widgets::komorebi::Komorebi;
use crate::widgets::komorebi::KomorebiConfig;
use crate::widgets::media::Media;
use crate::widgets::media::MediaConfig;
use crate::widgets::memory::Memory;
use crate::widgets::memory::MemoryConfig;
use crate::widgets::network::Network;
use crate::widgets::network::NetworkConfig;
use crate::widgets::storage::Storage;
use crate::widgets::storage::StorageConfig;
use crate::widgets::time::Time;
use crate::widgets::time::TimeConfig;
use crate::widgets::update::Update;
use crate::widgets::update::UpdateConfig;
use crate::storage::Storage;
use crate::storage::StorageConfig;
use crate::time::Time;
use crate::time::TimeConfig;
use crate::update::Update;
use crate::update::UpdateConfig;
use eframe::egui::Context;
use eframe::egui::Ui;
use serde::Deserialize;

View File

@@ -1,13 +0,0 @@
pub mod battery;
pub mod cpu;
pub mod date;
pub mod keyboard;
pub mod komorebi;
mod komorebi_layout;
pub mod media;
pub mod memory;
pub mod network;
pub mod storage;
pub mod time;
pub mod update;
pub mod widget;

View File

@@ -52,7 +52,6 @@ pub use komorebi::workspace::Workspace;
pub use komorebi::workspace::WorkspaceGlobals;
pub use komorebi::workspace::WorkspaceLayer;
pub use komorebi::AnimationsConfig;
pub use komorebi::AppSpecificConfigurationPath;
pub use komorebi::AspectRatio;
pub use komorebi::BorderColours;
pub use komorebi::CrossBoundaryBehaviour;

View File

@@ -10,7 +10,7 @@ komorebi-client = { path = "../komorebi-client" }
eframe = { workspace = true }
egui_extras = { workspace = true }
random_word = { version = "0.5", features = ["en"] }
random_word = { version = "0.4", features = ["en"] }
serde_json = { workspace = true }
windows-core = { workspace = true }
windows = { workspace = true }

View File

@@ -101,7 +101,7 @@ impl From<&komorebi_client::Workspace> for WorkspaceConfig {
let name = value
.name()
.to_owned()
.unwrap_or_else(|| random_word::get(random_word::Lang::En).to_string());
.unwrap_or_else(|| random_word::gen(random_word::Lang::En).to_string());
Self {
layout,

View File

@@ -51,22 +51,18 @@ use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::LoadCursorW;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SetCursor;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use windows::Win32::UI::WindowsAndMessaging::WM_SETCURSOR;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows_core::BOOL;
use windows_core::PCWSTR;
@@ -340,16 +336,6 @@ impl Border {
) -> LRESULT {
unsafe {
match message {
WM_SETCURSOR => match LoadCursorW(None, IDC_ARROW) {
Ok(cursor) => {
SetCursor(Some(cursor));
LRESULT(0)
}
Err(error) => {
tracing::error!("{error}");
LRESULT(1)
}
},
WM_CREATE => {
let mut border_pointer: *mut Border =
GetWindowLongPtrW(window, GWLP_USERDATA) as _;

View File

@@ -403,9 +403,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
}
// Update the border's monitor idx in case it changed
border.monitor_idx = Some(monitor_idx);
let rect = WindowsApi::window_rect(focused_window_hwnd)?;
border.window_rect = rect;
@@ -520,9 +517,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
}
// Update the border's monitor idx in case it changed
border.monitor_idx = Some(monitor_idx);
// avoid getting into a thread restart loop if we try to look up
// rect info for a window that has been destroyed by the time
// we get here
@@ -579,9 +573,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.window_kind = new_focus_state;
// Update the border's monitor idx in case it changed
border.monitor_idx = Some(monitor_idx);
let rect = WindowsApi::window_rect(window.hwnd)?;
border.window_rect = rect;

View File

@@ -140,114 +140,3 @@ impl Container {
self.windows.focus(idx);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contains_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should return true for existing windows
assert!(container.contains_window(1));
assert_eq!(container.idx_for_window(1), Some(1));
// Should return false since window 4 doesn't exist
assert!(!container.contains_window(4));
assert_eq!(container.idx_for_window(4), None);
}
#[test]
fn test_remove_window_by_idx() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Remove window 1
container.remove_window_by_idx(1);
// Should only have 2 windows left
assert_eq!(container.windows().len(), 2);
// Should return false since window 1 was removed
assert!(!container.contains_window(1));
}
#[test]
fn test_remove_focused_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should be focused on the last created window
assert_eq!(container.focused_window_idx(), 2);
// Remove the focused window
container.remove_focused_window();
// Should be focused on the window before the removed one
assert_eq!(container.focused_window_idx(), 1);
// Should only have 2 windows left
assert_eq!(container.windows().len(), 2);
}
#[test]
fn test_add_window() {
let mut container = Container::default();
container.add_window(Window::from(1));
assert_eq!(container.windows().len(), 1);
assert_eq!(container.focused_window_idx(), 0);
assert!(container.contains_window(1));
}
#[test]
fn test_focus_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should focus on the last created window
assert_eq!(container.focused_window_idx(), 2);
// focus on the window at index 1
container.focus_window(1);
// Should be focused on window 1
assert_eq!(container.focused_window_idx(), 1);
// focus on the window at index 0
container.focus_window(0);
// Should be focused on window 0
assert_eq!(container.focused_window_idx(), 0);
}
#[test]
fn test_idx_for_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should return the index of the window
assert_eq!(container.idx_for_window(1), Some(1));
// Should return None since window 4 doesn't exist
assert_eq!(container.idx_for_window(4), None);
}
}

View File

@@ -65,7 +65,6 @@ use crate::core::config_generation::WorkspaceMatchingRule;
use color_eyre::Result;
use os_info::Version;
use parking_lot::Mutex;
use parking_lot::RwLock;
use regex::Regex;
use serde::Deserialize;
use serde::Serialize;
@@ -132,8 +131,8 @@ lazy_static! {
static ref TRANSPARENCY_BLACKLIST: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref DISPLAY_INDEX_PREFERENCES: Arc<RwLock<HashMap<usize, String>>> =
Arc::new(RwLock::new(HashMap::new()));
static ref DISPLAY_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, String>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref WORKSPACE_MATCHING_RULES: Arc<Mutex<Vec<WorkspaceMatchingRule>>> =
Arc::new(Mutex::new(Vec::new()));
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
@@ -173,8 +172,6 @@ lazy_static! {
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
]));
static ref DUPLICATE_MONITOR_SERIAL_IDS: Arc<RwLock<Vec<String>>> =
Arc::new(RwLock::new(Vec::new()));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =

View File

@@ -17,7 +17,6 @@ use crate::core::Rect;
use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
use crate::workspace::WorkspaceLayer;
use crate::DefaultLayout;
use crate::Layout;
use crate::OperationDirection;
@@ -383,7 +382,6 @@ impl Monitor {
};
target_workspace.floating_windows_mut().push(window);
target_workspace.set_layer(WorkspaceLayer::Floating);
} else {
let container = workspace
.remove_focused_container()
@@ -400,8 +398,6 @@ impl Monitor {
Some(workspace) => workspace,
};
target_workspace.set_layer(WorkspaceLayer::Tiling);
if let Some(direction) = direction {
self.add_container_with_direction(
container,
@@ -468,139 +464,3 @@ impl Monitor {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_container() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add container to the default workspace
m.add_container(Container::default(), Some(0)).unwrap();
// Should contain a container in the current focused workspace
let workspace = m.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 1);
}
#[test]
fn test_remove_workspace_by_idx() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// Create workspace 2
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Create workspace 3
m.focus_workspace(new_workspace_index + 1).unwrap();
// Should have 3 workspaces
assert_eq!(m.workspaces().len(), 3);
// Remove workspace 1
m.remove_workspace_by_idx(1);
// Should have only 2 workspaces
assert_eq!(m.workspaces().len(), 2);
}
#[test]
fn test_remove_workspaces() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// Create workspace 2
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Create workspace 3
m.focus_workspace(new_workspace_index + 1).unwrap();
// Should have 3 workspaces
assert_eq!(m.workspaces().len(), 3);
// Remove all workspaces
m.remove_workspaces();
// All workspaces should be removed
assert_eq!(m.workspaces().len(), 0);
}
#[test]
fn test_focus_workspace() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// Focus workspace 2
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Should be focused on workspace 2
assert_eq!(m.focused_workspace_idx(), 1);
}
#[test]
fn test_new_workspace_idx() {
let m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
// Should be the last workspace index: 1
assert_eq!(new_workspace_index, 1);
}
}

View File

@@ -12,8 +12,6 @@ use crate::NotificationEvent;
use crate::State;
use crate::WindowManager;
use crate::WindowsApi;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::WORKSPACE_MATCHING_RULES;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
@@ -68,49 +66,17 @@ pub fn send_notification(notification: MonitorNotification) {
}
pub fn insert_in_monitor_cache(serial_or_device_id: &str, monitor: Monitor) {
let dip = DISPLAY_INDEX_PREFERENCES.read();
let mut dip_ids = dip.values();
let preferred_id = if dip_ids.any(|id| id == monitor.device_id()) {
monitor.device_id().clone()
} else if dip_ids.any(|id| Some(id) == monitor.serial_number_id().as_ref()) {
monitor.serial_number_id().clone().unwrap_or_default()
} else {
serial_or_device_id.to_string()
};
let mut monitor_cache = MONITOR_CACHE
.get_or_init(|| Mutex::new(HashMap::new()))
.lock();
monitor_cache.insert(preferred_id, monitor);
monitor_cache.insert(serial_or_device_id.to_string(), monitor);
}
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
let all_displays = win32_display_data::connected_displays_all()
Ok(win32_display_data::connected_displays_all()
.flatten()
.collect::<Vec<_>>();
let mut serial_id_map = HashMap::new();
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
*serial_id_map.entry(id.clone()).or_insert(0) += 1;
}
}
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
if serial_id_map.get(id).copied().unwrap_or_default() > 1 {
let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();
if !dupes.contains(id) {
(*dupes).push(id.clone());
}
}
}
}
Ok(all_displays
.into_iter()
.map(|mut display| {
.map(|display| {
let path = display.device_path;
let (device, device_id) = if path.is_empty() {
@@ -127,13 +93,6 @@ pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
if let Some(id) = &display.serial_number_id {
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
if dupes.contains(id) {
display.serial_number_id = None;
}
}
monitor::new(
display.hmonitor,
display.size.into(),
@@ -301,15 +260,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Make sure that in our state any attached displays have the latest Win32 data
for monitor in wm.monitors_mut() {
for attached in &attached_devices {
let serial_number_ids_match = if let (Some(attached_snid), Some(m_snid)) =
(attached.serial_number_id(), monitor.serial_number_id())
if attached.serial_number_id().eq(monitor.serial_number_id())
|| attached.device_id().eq(monitor.device_id())
{
attached_snid.eq(m_snid)
} else {
false
};
if serial_number_ids_match || attached.device_id().eq(monitor.device_id()) {
monitor.set_id(attached.id());
monitor.set_device(attached.device().clone());
monitor.set_device_id(attached.device_id().clone());
@@ -427,18 +380,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
workspace_rules.remove(i);
}
// Let's add their state to the cache for later, make sure to use what
// the user set as preference as the id.
let dip = DISPLAY_INDEX_PREFERENCES.read();
let mut dip_ids = dip.values();
let preferred_id = if dip_ids.any(|id| id == m.device_id()) {
m.device_id().clone()
} else if dip_ids.any(|id| Some(id) == m.serial_number_id().as_ref()) {
m.serial_number_id().clone().unwrap_or_default()
} else {
id
};
monitor_cache.insert(preferred_id, m.clone());
// Let's add their state to the cache for later
monitor_cache.insert(id, m.clone());
}
}

View File

@@ -250,11 +250,9 @@ impl WindowManager {
if let Some((monitor_idx, workspace_idx)) = monitor_workspace_indices {
if monitor_idx != focused_monitor_idx {
self.focus_monitor(monitor_idx)?;
let focused_ws_idx = self.focused_workspace_idx()?;
if focused_ws_idx != workspace_idx {
self.focus_workspace(workspace_idx)?;
}
} else if workspace_idx != focused_workspace_idx {
}
if workspace_idx != focused_workspace_idx {
self.focus_workspace(workspace_idx)?;
}
}
@@ -333,9 +331,11 @@ impl WindowManager {
SocketMessage::UnstackAll => self.unstack_all()?,
SocketMessage::CycleStack(direction) => {
self.cycle_container_window_in_direction(direction)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::CycleStackIndex(direction) => {
self.cycle_container_window_index_in_direction(direction)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::FocusStackWindow(idx) => {
// In case you are using this command on a bar on a monitor
@@ -346,6 +346,7 @@ impl WindowManager {
self.focus_monitor(monitor_idx)?;
}
self.focus_container_window(idx)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::ForceFocus => {
let focused_window = self.focused_window()?;
@@ -1154,7 +1155,7 @@ impl WindowManager {
);
}
SocketMessage::DisplayIndexPreference(index_preference, ref display) => {
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.write();
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.insert(index_preference, display.clone());
}
SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {

View File

@@ -395,19 +395,8 @@ impl WindowManager {
&& !matches!(event, WindowManagerEvent::Manage(_)));
if behaviour.float_override {
// Center floating windows if we are already on the `Floating`
// layer and the window doesn't match a `floating_windows` rule and
// the workspace is not a floating workspace
let center_spawned_floats =
matches!(workspace.layer, WorkspaceLayer::Floating)
&& !should_float
&& workspace.tile;
workspace.floating_windows_mut().push(window);
workspace.set_layer(WorkspaceLayer::Floating);
if center_spawned_floats {
let mut floating_window = window;
floating_window.center(&workspace.globals().work_area)?;
}
self.update_focused_workspace(false, false)?;
} else {
match behaviour.current_behaviour {

View File

@@ -302,16 +302,6 @@ impl From<&Monitor> for MonitorConfig {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum AppSpecificConfigurationPath {
/// A single applications.json file
Single(PathBuf),
/// Multiple applications.json files
Multiple(Vec<PathBuf>),
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.json` static configuration file reference for `v0.1.35`
@@ -352,7 +342,7 @@ pub struct StaticConfig {
pub mouse_follows_focus: Option<bool>,
/// Path to applications.json from komorebi-application-specific-configurations (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub app_specific_configuration_path: Option<AppSpecificConfigurationPath>,
pub app_specific_configuration_path: Option<PathBuf>,
/// Width of the window border (default: 8)
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_width")]
@@ -750,7 +740,7 @@ impl From<&WindowManager> for StaticConfig {
.collect::<Vec<_>>(),
),
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.read().clone()),
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.lock().clone()),
stackbar: None,
animation: None,
theme: None,
@@ -778,7 +768,7 @@ impl StaticConfig {
}
if let Some(display_index_preferences) = &self.display_index_preferences {
let mut preferences = DISPLAY_INDEX_PREFERENCES.write();
let mut preferences = DISPLAY_INDEX_PREFERENCES.lock();
preferences.clone_from(display_index_preferences);
}
@@ -1014,35 +1004,140 @@ impl StaticConfig {
}
if let Some(path) = &self.app_specific_configuration_path {
match path {
AppSpecificConfigurationPath::Single(path) => handle_asc_file(
path,
&mut ignore_identifiers,
&mut object_name_change_identifiers,
&mut layered_identifiers,
&mut tray_and_multi_window_identifiers,
&mut manage_identifiers,
&mut floating_applications,
&mut transparency_blacklist,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?,
AppSpecificConfigurationPath::Multiple(paths) => {
for path in paths {
handle_asc_file(
path,
&mut ignore_identifiers,
&mut object_name_change_identifiers,
&mut layered_identifiers,
&mut tray_and_multi_window_identifiers,
&mut manage_identifiers,
&mut floating_applications,
&mut transparency_blacklist,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?
match path.extension() {
None => {}
Some(ext) => match ext.to_string_lossy().to_string().as_str() {
"yaml" => {
tracing::info!("loading applications.yaml from: {}", path.display());
let path = resolve_home_path(path)?;
let content = std::fs::read_to_string(path)?;
let asc = ApplicationConfigurationGenerator::load(&content)?;
for mut entry in asc {
if let Some(rules) = &mut entry.ignore_identifiers {
populate_rules(
rules,
&mut ignore_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(ref options) = entry.options {
let options = options.clone();
for o in options {
match o {
ApplicationOptions::ObjectNameChange => {
populate_option(
&mut entry,
&mut object_name_change_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::Layered => {
populate_option(
&mut entry,
&mut layered_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::TrayAndMultiWindow => {
populate_option(
&mut entry,
&mut tray_and_multi_window_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::Force => {
populate_option(
&mut entry,
&mut manage_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::BorderOverflow => {} // deprecated
}
}
}
}
}
}
"json" => {
tracing::info!("loading applications.json from: {}", path.display());
let path = resolve_home_path(path)?;
let mut asc = ApplicationSpecificConfiguration::load(&path)?;
for entry in asc.values_mut() {
match entry {
AscApplicationRulesOrSchema::Schema(_) => {}
AscApplicationRulesOrSchema::AscApplicationRules(entry) => {
if let Some(rules) = &mut entry.ignore {
populate_rules(
rules,
&mut ignore_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.manage {
populate_rules(
rules,
&mut manage_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.floating {
populate_rules(
rules,
&mut floating_applications,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.transparency_ignore {
populate_rules(
rules,
&mut transparency_blacklist,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.tray_and_multi_window {
populate_rules(
rules,
&mut tray_and_multi_window_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.layered {
populate_rules(
rules,
&mut layered_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.object_name_change {
populate_rules(
rules,
&mut object_name_change_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.slow_application {
populate_rules(
rules,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?;
}
}
}
}
}
_ => {}
},
}
}
@@ -1058,16 +1153,7 @@ impl StaticConfig {
let mut value: Self = serde_json::from_str(&content)?;
if let Some(path) = &mut value.app_specific_configuration_path {
match path {
AppSpecificConfigurationPath::Single(path) => {
*path = resolve_home_path(&*path)?;
}
AppSpecificConfigurationPath::Multiple(paths) => {
for path in paths {
*path = resolve_home_path(&*path)?;
}
}
}
*path = resolve_home_path(&*path)?;
}
if let Some(monitors) = &mut value.monitors {
@@ -1190,7 +1276,7 @@ impl StaticConfig {
let mut wm = wm.lock();
let configs_with_preference: Vec<_> =
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
let mut configs_used = Vec::new();
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
@@ -1200,7 +1286,7 @@ impl StaticConfig {
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
(monitor
.serial_number_id()
@@ -1299,7 +1385,7 @@ impl StaticConfig {
.filter(|i| !configs_used.contains(i))
{
let id = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.get(i).cloned()
};
if let (Some(id), Some(monitor_config)) =
@@ -1359,7 +1445,7 @@ impl StaticConfig {
value.apply_globals()?;
let configs_with_preference: Vec<_> =
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
let mut configs_used = Vec::new();
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
@@ -1369,7 +1455,7 @@ impl StaticConfig {
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
(monitor
.serial_number_id()
@@ -1471,7 +1557,7 @@ impl StaticConfig {
.filter(|i| !configs_used.contains(i))
{
let id = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.get(i).cloned()
};
if let (Some(id), Some(monitor_config)) =
@@ -1636,134 +1722,6 @@ fn populate_rules(
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn handle_asc_file(
path: &PathBuf,
ignore_identifiers: &mut Vec<MatchingRule>,
object_name_change_identifiers: &mut Vec<MatchingRule>,
layered_identifiers: &mut Vec<MatchingRule>,
tray_and_multi_window_identifiers: &mut Vec<MatchingRule>,
manage_identifiers: &mut Vec<MatchingRule>,
floating_applications: &mut Vec<MatchingRule>,
transparency_blacklist: &mut Vec<MatchingRule>,
slow_application_identifiers: &mut Vec<MatchingRule>,
regex_identifiers: &mut HashMap<String, Regex>,
) -> Result<()> {
match path.extension() {
None => {}
Some(ext) => match ext.to_string_lossy().to_string().as_str() {
"yaml" => {
tracing::info!("loading applications.yaml from: {}", path.display());
let path = resolve_home_path(path)?;
let content = std::fs::read_to_string(path)?;
let asc = ApplicationConfigurationGenerator::load(&content)?;
for mut entry in asc {
if let Some(rules) = &mut entry.ignore_identifiers {
populate_rules(rules, ignore_identifiers, regex_identifiers)?;
}
if let Some(ref options) = entry.options {
let options = options.clone();
for o in options {
match o {
ApplicationOptions::ObjectNameChange => {
populate_option(
&mut entry,
object_name_change_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::Layered => {
populate_option(
&mut entry,
layered_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::TrayAndMultiWindow => {
populate_option(
&mut entry,
tray_and_multi_window_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::Force => {
populate_option(
&mut entry,
manage_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::BorderOverflow => {} // deprecated
}
}
}
}
}
"json" => {
tracing::info!("loading applications.json from: {}", path.display());
let path = resolve_home_path(path)?;
let mut asc = ApplicationSpecificConfiguration::load(&path)?;
for entry in asc.values_mut() {
match entry {
AscApplicationRulesOrSchema::Schema(_) => {}
AscApplicationRulesOrSchema::AscApplicationRules(entry) => {
if let Some(rules) = &mut entry.ignore {
populate_rules(rules, ignore_identifiers, regex_identifiers)?;
}
if let Some(rules) = &mut entry.manage {
populate_rules(rules, manage_identifiers, regex_identifiers)?;
}
if let Some(rules) = &mut entry.floating {
populate_rules(rules, floating_applications, regex_identifiers)?;
}
if let Some(rules) = &mut entry.transparency_ignore {
populate_rules(rules, transparency_blacklist, regex_identifiers)?;
}
if let Some(rules) = &mut entry.tray_and_multi_window {
populate_rules(
rules,
tray_and_multi_window_identifiers,
regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.layered {
populate_rules(rules, layered_identifiers, regex_identifiers)?;
}
if let Some(rules) = &mut entry.object_name_change {
populate_rules(
rules,
object_name_change_identifiers,
regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.slow_application {
populate_rules(
rules,
slow_application_identifiers,
regex_identifiers,
)?;
}
}
}
}
}
_ => {}
},
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::StaticConfig;

File diff suppressed because it is too large Load Diff

View File

@@ -153,7 +153,6 @@ use crate::windows_callbacks;
use crate::Window;
use crate::WindowManager;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::MONITOR_INDEX_PREFERENCES;
macro_rules! as_ptr {
@@ -259,30 +258,7 @@ impl WindowsApi {
let monitors = &mut wm.monitors;
let monitor_usr_idx_map = &mut wm.monitor_usr_idx_map;
let all_displays = win32_display_data::connected_displays_all()
.flatten()
.collect::<Vec<_>>();
let mut serial_id_map = HashMap::new();
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
*serial_id_map.entry(id.clone()).or_insert(0) += 1;
}
}
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
if serial_id_map.get(id).copied().unwrap_or_default() > 1 {
let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();
if !dupes.contains(id) {
(*dupes).push(id.clone());
}
}
}
}
'read: for mut display in all_displays {
'read: for display in win32_display_data::connected_displays_all().flatten() {
let path = display.device_path.clone();
let (device, device_id) = if path.is_empty() {
@@ -305,13 +281,6 @@ impl WindowsApi {
}
}
if let Some(id) = &display.serial_number_id {
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
if dupes.contains(id) {
display.serial_number_id = None;
}
}
let m = monitor::new(
display.hmonitor,
display.size.into(),
@@ -330,7 +299,7 @@ impl WindowsApi {
}
}
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
for (index, id) in &*display_index_preferences {
if m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || id.eq(m.device_id())
{
@@ -365,7 +334,7 @@ impl WindowsApi {
// Rebuild monitor index map
*monitor_usr_idx_map = HashMap::new();
let mut added_monitor_idxs = Vec::new();
for (index, id) in &*DISPLAY_INDEX_PREFERENCES.read() {
for (index, id) in &*DISPLAY_INDEX_PREFERENCES.lock() {
if let Some(m_idx) = monitors.elements().iter().position(|m| {
m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || m.device_id() == id
}) {
@@ -1003,7 +972,7 @@ impl WindowsApi {
}
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
for mut display in win32_display_data::connected_displays_all().flatten() {
for display in win32_display_data::connected_displays_all().flatten() {
if display.hmonitor == hmonitor {
let path = display.device_path;
@@ -1021,13 +990,6 @@ impl WindowsApi {
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
if let Some(id) = &display.serial_number_id {
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
if dupes.contains(id) {
display.serial_number_id = None;
}
}
let monitor = monitor::new(
hmonitor,
display.size.into(),

View File

@@ -282,7 +282,7 @@ impl Workspace {
}
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
if let Some(container) = self.monocle_container() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window() {
container.restore();
window.focus(mouse_follows_focus)?;
@@ -318,16 +318,10 @@ impl Workspace {
if self.maximized_window().is_none() && matches!(self.layer, WorkspaceLayer::Tiling) {
window.focus(mouse_follows_focus)?;
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.restore();
maximized_window.focus(mouse_follows_focus)?;
} else if let Some(floating_window) = self.floating_windows().first() {
floating_window.focus(mouse_follows_focus)?;
}
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.restore();
maximized_window.focus(mouse_follows_focus)?;
} else if let Some(floating_window) = self.floating_windows().first() {
floating_window.focus(mouse_follows_focus)?;
}
Ok(())
@@ -1635,462 +1629,3 @@ impl Workspace {
self.focus_container(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contains_window() {
// Create default workspace
let mut workspace = Workspace::default();
// Add a window to the container
let mut container = Container::default();
container.windows_mut().push_back(Window::from(0));
// Add container
workspace.add_container_to_back(container);
// Should be true
assert!(workspace.contains_window(0));
// Should be false
assert!(!workspace.is_empty())
}
#[test]
fn test_add_container_to_back() {
let mut workspace = Workspace::default();
{
// Container with 3 windows
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Get focused container. Should be the index of the last container added
let container = workspace.focused_container_mut().unwrap();
// Should be focused on the container with 1 window
assert_eq!(container.windows().len(), 1);
}
#[test]
fn test_add_container_to_front() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_front(container);
}
{
// Container with 3 windows
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_front(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Get focused container. Should be the index of the last container added
let container = workspace.focused_container_mut().unwrap();
// Should be focused on the container with 3 windows
assert_eq!(container.windows().len(), 3);
}
#[test]
fn test_remove_focused_container() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Should be focused on the container at index 1
assert_eq!(workspace.focused_container_idx(), 1);
// Store the container at index 1 before removal
let container_to_remove = workspace.containers().get(1).cloned();
workspace.remove_focused_container();
// Should only have 1 container
assert_eq!(workspace.containers().len(), 1);
// Should be focused on the container at index 0
assert_eq!(workspace.focused_container_idx(), 0);
// Ensure the container at index 1 before removal is no longer present
assert!(container_to_remove.is_some());
assert!(!workspace
.containers()
.contains(&container_to_remove.unwrap()));
}
#[test]
fn test_insert_container_at_idx() {
let mut workspace = Workspace::default();
for i in 0..4 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 4 containers
assert_eq!(workspace.containers().len(), 4);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 3);
// Insert a container at index 4
workspace.insert_container_at_idx(4, Container::default());
// Should have 5 containers
assert_eq!(workspace.containers().len(), 5);
// Should be focused on the newly inserted container
assert_eq!(workspace.focused_container_idx(), 4);
}
#[test]
fn test_remove_container_by_idx() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Store the container at index 1 before removal
let container_to_remove = workspace.containers().get(1).cloned();
// Remove the container at index 1
workspace.remove_container_by_idx(1);
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Ensure the container at index 1 before removal is no longer present
assert!(container_to_remove.is_some());
assert!(!workspace
.containers()
.contains(&container_to_remove.unwrap()));
}
#[test]
fn test_remove_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Store the container at index 2 before removal
let container_to_remove = workspace.containers().get(2).cloned();
// Remove the container at index 2
workspace.remove_container(2);
// Should be focused on the previous container which is index 1
assert_eq!(workspace.focused_container_idx(), 1);
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Ensure the container at index 1 before removal is no longer present
assert!(container_to_remove.is_some());
assert!(!workspace
.containers()
.contains(&container_to_remove.unwrap()));
}
#[test]
fn test_focus_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Focus on container 1
workspace.focus_container(1);
assert_eq!(workspace.focused_container_idx(), 1);
// Focus on container 0
workspace.focus_container(0);
assert_eq!(workspace.focused_container_idx(), 0);
// Focus on container 2
workspace.focus_container(2);
assert_eq!(workspace.focused_container_idx(), 2);
}
#[test]
fn test_focus_previous_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Focus on the previous container
workspace.focus_previous_container();
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 1);
}
#[test]
fn test_focus_last_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Change focus to the first container for the test
workspace.focus_container(0);
assert_eq!(workspace.focused_container_idx(), 0);
// Focus on the last container
workspace.focus_last_container();
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 2);
}
#[test]
fn test_focus_first_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Focus on the first container
workspace.focus_first_container();
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 0);
}
#[test]
fn test_swap_containers() {
let mut workspace = Workspace::default();
{
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
{
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
{
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 1);
// Should have 1 window
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
}
// Swap containers 0 and 1
workspace.swap_containers(0, 1);
{
// Should be focused on container 0
assert_eq!(workspace.focused_container_idx(), 1);
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 3);
}
}
#[test]
fn test_new_container_for_window() {
let mut workspace = Workspace::default();
{
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Add new window to container
workspace.new_container_for_window(Window::from(2));
// Container 0 should have 1 window
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
// Should return true that window 2 exists
assert!(workspace.contains_window(2));
}
#[test]
fn test_move_window_to_container() {
let mut workspace = Workspace::default();
{
// Container with 0 windows
let container = Container::default();
workspace.add_container_to_back(container);
}
{
// Container with 3 windows
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
// Move A Window from container 1 to container 0
workspace.move_window_to_container(0).unwrap();
// Focus on container 0
workspace.focus_container(0);
// Container 0 should have 1 window
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
}
#[test]
fn test_remove_window() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
// Remove window 1
workspace.remove_window(1).ok();
// Should have 2 windows
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 2);
// Check that window 1 is removed
assert!(!workspace.contains_window(1));
}
#[test]
fn test_new_container_for_focused_window() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
// Add focused window to new container
workspace.new_container_for_focused_window().ok();
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
{
// Inspect new container. Should contain 1 window. Window name should be 0
workspace.focus_container(1);
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
assert!(workspace.contains_window(0));
}
}
}

View File

@@ -25,7 +25,6 @@ use fs_tail::TailedFile;
use komorebi_client::resolve_home_path;
use komorebi_client::send_message;
use komorebi_client::send_query;
use komorebi_client::AppSpecificConfigurationPath;
use komorebi_client::ApplicationSpecificConfiguration;
use lazy_static::lazy_static;
use miette::NamedSource;
@@ -1661,12 +1660,11 @@ fn main() -> Result<()> {
None => {
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n");
}
Some(AppSpecificConfigurationPath::Single(path)) => {
Some(path) => {
if !Path::exists(Path::new(&path)) {
println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
}
}
_ => {}
}
}

View File

@@ -55,12 +55,6 @@ nav:
- Installation: installation.md
- Example configurations: example-configurations.md
- Troubleshooting: troubleshooting.md
- Usage:
- usage/focusing-windows.md
- usage/moving-windows.md
- usage/stacking-windows.md
- usage/focusing-workspaces.md
- usage/moving-windows-across-workspaces.md
- Common workflows:
- common-workflows/komorebi-config-home.md
- common-workflows/autostart.md
@@ -77,7 +71,6 @@ nav:
- common-workflows/dynamic-layout-switching.md
- common-workflows/set-display-index.md
- common-workflows/multiple-bar-instances.md
- common-workflows/multi-monitor-setup.md
- Configuration reference: https://komorebi.lgug2z.com/schema
- Bar reference: https://komorebi-bar.lgug2z.com/schema
- CLI reference:

View File

@@ -265,10 +265,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Date\": { \"enable\": true, \"format\": { \"Custom\": \"%D %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -896,10 +892,6 @@
"format"
],
"properties": {
"changing_icon": {
"description": "Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)",
"type": "boolean"
},
"enable": {
"description": "Enable the Time widget",
"type": "boolean"
@@ -996,10 +988,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Time\": { \"enable\": true, \"format\": { \"Custom\": \"%T %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -1675,10 +1663,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Date\": { \"enable\": true, \"format\": { \"Custom\": \"%D %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -2306,10 +2290,6 @@
"format"
],
"properties": {
"changing_icon": {
"description": "Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)",
"type": "boolean"
},
"enable": {
"description": "Enable the Time widget",
"type": "boolean"
@@ -2406,10 +2386,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Time\": { \"enable\": true, \"format\": { \"Custom\": \"%T %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -3018,10 +2994,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Date\": { \"enable\": true, \"format\": { \"Custom\": \"%D %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -3649,10 +3621,6 @@
"format"
],
"properties": {
"changing_icon": {
"description": "Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)",
"type": "boolean"
},
"enable": {
"description": "Enable the Time widget",
"type": "boolean"
@@ -3749,10 +3717,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Time\": { \"enable\": true, \"format\": { \"Custom\": \"%T %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}

View File

@@ -131,19 +131,7 @@
},
"app_specific_configuration_path": {
"description": "Path to applications.json from komorebi-application-specific-configurations (default: None)",
"anyOf": [
{
"description": "A single applications.json file",
"type": "string"
},
{
"description": "Multiple applications.json files",
"type": "array",
"items": {
"type": "string"
}
}
]
"type": "string"
},
"bar_configurations": {
"description": "Komorebi status bar configuration files for multiple instances on different monitors",