Compare commits

..

20 Commits

Author SHA1 Message Date
LGUG2Z
cc51f62c3a chore(release): v0.1.33 2025-01-11 13:33:13 -08:00
Csaba
b1db417df5 feat(bar): opt to hide battery widget when charged 2025-01-09 15:49:51 -08:00
LGUG2Z
996a556984 chore(clippy): apply new rust 1.84.0 lints 2025-01-09 15:48:41 -08:00
LGUG2Z
c71e61fb1e chore(deps): cargo update 2025-01-08 21:39:21 -08:00
LGUG2Z
2d97ee101d feat(config): use global padding when omitted on ws
Simplifying my software for the masses
2025-01-08 20:47:16 -08:00
LGUG2Z
a4f69238b7 fix(wm): preserve new padding when loading state
This commit is a follow up to 7bf1521363,
ensuring that if a user has changed global padding options, that they
will be preserved from the initialized window manager state and applied
on top of the dumped state which is being restored.
2025-01-08 20:24:23 -08:00
alex-ds13
96f7eb1d31 fix(bar): apply position on start
For some reason, when calling the `window.set_position` when creating
the Komobar or even when applying the config on the first frame the
actual EGUI's window size wasn't changing. This commit adds a new field
to `Komobar` called `size_rect` so that we can store the expected size
rect of the window according to the config, so that we don't have to be
calculating it all the time. This field is updated on `apply_config`.

Now on `update` of the bar we check if the current size using the EGUI
Context is the expected `size_rect`, if it is we do nothing, if it is
not we update the bar position. This makes sure that on start the bar
will resize to the users config correctly! Now the resize of the bar
only happens here.

This commit also adds the `hwnd` field to `Komobar` so that we don't
have to be calling `process_hwnd()` all the time.
2025-01-07 16:39:38 -08:00
LGUG2Z
28cd4a8801 fix(wm): skip destroyed windows on rule enforcement
This commit introduces an if let binding to only process windows which
still exist when attempting to enforce workspace rules.

Previously, calls to functions such as Window::exe might have returned
an error if a window which had been destroyed but not yet removed from
the state was examined by the enforce_workspace_rules fn. Now, such
windows will fail the if let binding and be skipped entirely, eventually
being removed by the core event processing loop.
2025-01-04 21:14:33 -08:00
LGUG2Z
3aa92a1255 feat(bar): add update widget
This commit adds a new widget, "Update", which will check for komorebi
version updates using the cargo package version of the running binary
and the latest release returned from the GitHub API.

If the latest release is newer than the current cargo package version, a
widget will be shown, which can be clicked to open the changelog of the
latest release.
2025-01-04 21:14:33 -08:00
LGUG2Z
281980b010 fix(wm): avoid obvious border manager thread crash
This commit adds an early exit from the border manager's event
processing loop whenever a window which still exists in the state but
has been destroyed is encountered. Instead of returning an error, the
'containers loop will now skip ahead to the next iteration.

This commit also makes an adjustment to the frequency with which the
reaper sends border manager notifications - a single notification is now
sent at the end of each iteration if necessary, rather than one
notification per workspace.
2025-01-04 21:14:33 -08:00
LGUG2Z
c063302c91 feat(cli): add stackbar-mode command
This commit adds a new komorebic command "stackbar-mode" to allow users
to change stackbar modes programmatically.
2025-01-04 21:14:33 -08:00
LGUG2Z
ba52dc3378 fix(wm): add uncloak as a notif override event
If a user triggers the workspace reconciliator by clicking on an app in
the start bar or via alt-tab, a notification should be sent to
subscribers such as komorebi-bar so that the focused workspace can be
updated.

The various komorebi reconciliators and manager modules don't emit
events to subscribers themselves (yet?), so for now we can pass on the
uncloak event.

Maybe we can look into expanding the Notification enum in the future.

fix #1211
2025-01-04 21:14:33 -08:00
LGUG2Z
44716fdc98 fix(wm): avoid focused ws rule enforcement deadlock
This commit adds mutex lock scoping in
WindowManager::enforce_workspace_rule to avoid a deadlock when
should_update_focused_workspace evaluates to true.

fix #1212
2025-01-04 21:14:33 -08:00
LGUG2Z
4b30cecba9 feat(config): allow specifying layout flip on ws
This commit adds support for specifying a layout flip axis for each
workspace in the static configuration file.
2025-01-04 21:14:33 -08:00
LGUG2Z
d45cd729e8 feat(cli): allow checking of arbitrary config files
This commit adds an optional --komorebi-config flag to the check command
to allow users to check a komorebi.json file in an arbitrary location.
2025-01-04 21:14:27 -08:00
LGUG2Z
5a8f48c6b9 chore(dev): begin v0.1.33-dev 2025-01-03 18:20:23 -08:00
LGUG2Z
4b9d811499 chore(release): v0.1.32 2025-01-01 11:23:43 -08:00
LGUG2Z
d520a2bf74 docs(mkdocs): run docgen 2025-01-01 10:43:19 -08:00
LGUG2Z
7ef4fd81c0 feat(cli): add version update checks
This commit adds version update checks and feedback to the komorebic
start and check commands.
2024-12-31 10:58:25 -08:00
LGUG2Z
083ab65077 feat(docs): individual commercial use licensing
This commit updates various docs with information on the long-promised
individual commercial use license which will be available to purchase
from 01 Jan 2025 onwards.
2024-12-31 10:02:40 -08:00
52 changed files with 1154 additions and 308 deletions

429
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,13 +31,13 @@ tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
paste = "1"
sysinfo = "0.31"
sysinfo = "0.33"
uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "dd65e3f22d0521b78fcddde11abc2a3e9dcc32a8" }
windows-implement = { version = "0.58" }
windows-interface = { version = "0.58" }
windows-core = { version = "0.58" }
shadow-rs = "0.35"
shadow-rs = "0.37"
which = "7"
[workspace.dependencies.windows]

View File

@@ -389,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.31"}
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.33"}
use anyhow::Result;
use komorebi_client::Notification;

View File

@@ -3,13 +3,18 @@
```
Set the duration for movement animations in ms
Usage: komorebic.exe animation-duration <DURATION>
Usage: komorebic.exe animation-duration [OPTIONS] <DURATION>
Arguments:
<DURATION>
Desired animation durations in ms
Options:
-a, --animation-type <ANIMATION_TYPE>
Animation type to apply the duration to. If not specified, sets global duration
[possible values: movement, transparency]
-h, --help
Print help

View File

@@ -10,8 +10,14 @@ Options:
Desired ease function for animation
[default: linear]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo,
ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart,
ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ,
ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
-a, --animation-type <ANIMATION_TYPE>
Animation type to apply the style to. If not specified, sets global style
[possible values: movement, transparency]
-h, --help
Print help

View File

@@ -3,13 +3,18 @@
```
Enable or disable movement animations
Usage: komorebic.exe animation <BOOLEAN_STATE>
Usage: komorebic.exe animation [OPTIONS] <BOOLEAN_STATE>
Arguments:
<BOOLEAN_STATE>
[possible values: enable, disable]
Options:
-a, --animation-type <ANIMATION_TYPE>
Animation type to apply the state to. If not specified, sets global state
[possible values: movement, transparency]
-h, --help
Print help

View File

@@ -3,9 +3,12 @@
```
Check komorebi configuration and related files for common errors
Usage: komorebic.exe check
Usage: komorebic.exe check [OPTIONS]
Options:
-k, --komorebi-config <KOMOREBI_CONFIG>
Path to a static configuration JSON file
-h, --help
Print help

View File

@@ -0,0 +1,12 @@
# close-workspace
```
Close the focused workspace (must be empty and unnamed)
Usage: komorebic.exe close-workspace
Options:
-h, --help
Print help
```

View File

@@ -0,0 +1,16 @@
# cycle-stack-index
```
Cycle the index of the focused window in the focused stack in the specified cycle direction
Usage: komorebic.exe cycle-stack-index <CYCLE_DIRECTION>
Arguments:
<CYCLE_DIRECTION>
[possible values: previous, next]
Options:
-h, --help
Print help
```

16
docs/cli/eager-focus.md Normal file
View File

@@ -0,0 +1,16 @@
# eager-focus
```
Focus the first managed window matching the given exe
Usage: komorebic.exe eager-focus <EXE>
Arguments:
<EXE>
Case-sensitive exe identifier
Options:
-h, --help
Print help
```

View File

@@ -18,6 +18,9 @@ Options:
--bar
Enable autostart of komorebi-bar
--masir
Enable autostart of masir
-h, --help
Print help

View File

@@ -0,0 +1,12 @@
# enforce-workspace-rules
```
Enforce all workspace rules, including initial workspace rules that have already been applied
Usage: komorebic.exe enforce-workspace-rules
Options:
-h, --help
Print help
```

24
docs/cli/kill.md Normal file
View File

@@ -0,0 +1,24 @@
# kill
```
Kill background processes started by komorebic
Usage: komorebic.exe kill [OPTIONS]
Options:
--whkd
Kill whkd if it is running as a background process
--ahk
Kill ahk if it is running as a background process
--bar
Kill komorebi-bar if it is running as a background process
--masir
Kill masir if it is running as a background process
-h, --help
Print help
```

18
docs/cli/stackbar-mode.md Normal file
View File

@@ -0,0 +1,18 @@
# stackbar-mode
```
Set the stackbar mode
Usage: komorebic.exe stackbar-mode <MODE>
Arguments:
<MODE>
Desired stackbar mode
[possible values: always, never, on-stack]
Options:
-h, --help
Print help
```

View File

@@ -24,6 +24,12 @@ Options:
--bar
Start komorebi-bar in a background process
--masir
Start masir in a background process for focus-follows-mouse
--clean-state
Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi
-h, --help
Print help

View File

@@ -15,6 +15,9 @@ Options:
--bar
Stop komorebi-bar if it is running as a background process
--masir
Stop masir if it is running as a background process
-h, --help
Print help

View File

@@ -1,7 +1,8 @@
# toggle-workspace-float-override
```
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace previously it takes the opposite of the global value
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace
previously it takes the opposite of the global value
Usage: komorebic.exe toggle-workspace-float-override

View File

@@ -1,7 +1,8 @@
# toggle-workspace-window-container-behaviour
```
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the global value
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the
global value
Usage: komorebic.exe toggle-workspace-window-container-behaviour

View File

@@ -181,10 +181,10 @@ The `grid` layout does not support resizing windows tiles.
key bindings go to the left of the colon, and shell commands go to the right of the
colon.
Please remember that `whkd` does not support overriding Microsoft's limitations
on hotkey bindings that include the `Windows` key. If this is important to you,
I recommend using [AutoHotKey](https://autohotkey.com) to set up your key
bindings for `komorebic` commands instead.
As of [`v0.2.4`](https://github.com/LGUG2Z/whkd/releases/tag/v0.2.4), `whkd` can override most of Microsoft's
limitations on hotkey bindings that include the `win` key. However, you will still need
to [modify the registry](https://superuser.com/questions/1059511/how-to-disable-winl-in-windows-10) to prevent
`win + l` from locking the operating system.
```
{% include "./whkdrc.sample" %}
@@ -203,7 +203,7 @@ It is also possible to change a hotkey behavior depending on which application h
alt + n [
# ProcessName as shown by `Get-Process`
Firefox : echo "hello firefox"
# Spaces are fine, no quotes required
Google Chrome : echo "hello chrome"
]

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.31/schema.bar.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.33/schema.bar.json",
"monitor": {
"index": 0,
"work_area_offset": {
@@ -33,6 +33,11 @@
}
],
"right_widgets": [
{
"Update": {
"enable": true
}
},
{
"Media": {
"enable": true
@@ -73,4 +78,4 @@
}
}
]
}
}

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-bar"
version = "0.1.32"
version = "0.1.33"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -20,11 +20,12 @@ egui-phosphor = "0.8"
font-loader = "0.11"
hotwatch = { workspace = true }
image = "0.25"
netdev = "0.31"
netdev = "0.32"
num = "0.4"
num-derive = "0.4"
num-traits = "0.2"
random_word = { version = "0.4", features = ["en"] }
reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

View File

@@ -50,6 +50,7 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
pub struct Komobar {
pub hwnd: Option<isize>,
pub config: Arc<KomobarConfig>,
pub render_config: Rc<RefCell<RenderConfig>>,
pub komorebi_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
@@ -61,6 +62,7 @@ pub struct Komobar {
pub bg_color: Rc<RefCell<Color32>>,
pub bg_color_with_alpha: Rc<RefCell<Color32>>,
pub scale_factor: f32,
pub size_rect: komorebi_client::Rect,
applied_theme_on_first_frame: bool,
}
@@ -194,49 +196,9 @@ impl Komobar {
Self::add_custom_font(ctx, font_family);
}
let position = config.position.clone().unwrap_or(PositionConfig {
start: Some(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
}),
end: Some(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
}),
});
if let Some(hwnd) = process_hwnd() {
let start = position.start.unwrap_or(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
});
let end = position.end.unwrap_or(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
});
if end.y == 0.0 {
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
}
let rect = komorebi_client::Rect {
left: start.x as i32,
top: start.y as i32,
right: end.x as i32,
bottom: end.y as i32,
};
let window = komorebi_client::Window::from(hwnd);
match window.set_position(&rect, false) {
Ok(_) => {
tracing::info!("updated bar position");
}
Err(error) => {
tracing::error!("{}", error.to_string())
}
}
}
// Update the `size_rect` so that the bar position can be changed on the EGUI update
// function
self.update_size_rect(config.position.clone());
self.try_apply_theme(config, ctx);
@@ -307,7 +269,7 @@ impl Komobar {
Some(widget.komorebi_notification_state.clone());
}
Some(ref previous) => {
if widget.workspaces.map_or(false, |w| w.enable) {
if widget.workspaces.is_some_and(|w| w.enable) {
previous.borrow_mut().update_from_config(
&widget.komorebi_notification_state.borrow(),
);
@@ -361,6 +323,41 @@ impl Komobar {
self.config = config.clone().into();
}
/// Updates the `size_rect` field. Returns a bool indicating if the field was changed or not
fn update_size_rect(&mut self, position: Option<PositionConfig>) {
let position = position.unwrap_or(PositionConfig {
start: Some(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
}),
end: Some(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
}),
});
let start = position.start.unwrap_or(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
});
let end = position.end.unwrap_or(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
});
if end.y == 0.0 {
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
}
self.size_rect = komorebi_client::Rect {
left: start.x as i32,
top: start.y as i32,
right: end.x as i32,
bottom: end.y as i32,
};
}
fn try_apply_theme(&mut self, config: &KomobarConfig, ctx: &Context) {
match config.theme {
Some(theme) => {
@@ -454,6 +451,7 @@ impl Komobar {
config: Arc<KomobarConfig>,
) -> Self {
let mut komobar = Self {
hwnd: process_hwnd(),
config: config.clone(),
render_config: Rc::new(RefCell::new(RenderConfig::new())),
komorebi_notification_state: None,
@@ -465,6 +463,7 @@ impl Komobar {
bg_color: Rc::new(RefCell::new(Style::default().visuals.panel_fill)),
bg_color_with_alpha: Rc::new(RefCell::new(Style::default().visuals.panel_fill)),
scale_factor: cc.egui_ctx.native_pixels_per_point().unwrap_or(1.0),
size_rect: komorebi_client::Rect::default(),
applied_theme_on_first_frame: false,
};
@@ -535,6 +534,10 @@ impl eframe::App for Komobar {
}
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
if self.hwnd.is_none() {
self.hwnd = process_hwnd();
}
if self.scale_factor != ctx.native_pixels_per_point().unwrap_or(1.0) {
self.scale_factor = ctx.native_pixels_per_point().unwrap_or(1.0);
self.apply_config(
@@ -573,6 +576,31 @@ impl eframe::App for Komobar {
self.applied_theme_on_first_frame = true;
}
// Check if egui's Window size is the expected one, if not, update it
if let Some(current_rect) = ctx.input(|i| i.viewport().outer_rect) {
// Get the correct size according to scale factor
let current_rect = komorebi_client::Rect {
left: (current_rect.min.x * self.scale_factor) as i32,
top: (current_rect.min.y * self.scale_factor) as i32,
right: ((current_rect.max.x - current_rect.min.x) * self.scale_factor) as i32,
bottom: ((current_rect.max.y - current_rect.min.y) * self.scale_factor) as i32,
};
if self.size_rect != current_rect {
if let Some(hwnd) = self.hwnd {
let window = komorebi_client::Window::from(hwnd);
match window.set_position(&self.size_rect, false) {
Ok(_) => {
tracing::info!("updated bar position");
}
Err(error) => {
tracing::error!("{}", error.to_string())
}
}
}
}
}
let frame = if let Some(frame) = &self.config.frame {
Frame::none()
.inner_margin(Margin::symmetric(

View File

@@ -1,11 +1,11 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::Sense;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use schemars::JsonSchema;
@@ -14,6 +14,7 @@ use serde::Serialize;
use starship_battery::units::ratio::percent;
use starship_battery::Manager;
use starship_battery::State;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
@@ -21,6 +22,8 @@ use std::time::Instant;
pub struct BatteryConfig {
/// Enable the Battery widget
pub enable: bool,
/// Hide the widget if the battery is at full charge
pub hide_on_full_charge: Option<bool>,
/// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
@@ -33,6 +36,7 @@ impl From<BatteryConfig> for Battery {
Self {
enable: value.enable,
hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false),
manager: Manager::new().unwrap(),
last_state: String::new(),
data_refresh_interval,
@@ -52,6 +56,7 @@ pub enum BatteryState {
pub struct Battery {
pub enable: bool,
hide_on_full_charge: bool,
manager: Manager,
pub state: BatteryState,
data_refresh_interval: u64,
@@ -71,17 +76,22 @@ impl Battery {
if let Ok(mut batteries) = self.manager.batteries() {
if let Some(Ok(first)) = batteries.nth(0) {
let percentage = first.state_of_charge().get::<percent>();
match first.state() {
State::Charging => self.state = BatteryState::Charging,
State::Discharging => self.state = BatteryState::Discharging,
_ => {}
}
output = match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("BAT: {percentage:.0}%")
if percentage == 100.0 && self.hide_on_full_charge {
output = String::new()
} else {
match first.state() {
State::Charging => self.state = BatteryState::Charging,
State::Discharging => self.state = BatteryState::Discharging,
_ => {}
}
output = match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("BAT: {percentage:.0}%")
}
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"),
}
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"),
}
}
}
@@ -125,12 +135,18 @@ impl BarWidget for Battery {
},
);
config.apply_on_widget(true, ui, |ui| {
ui.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
);
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked()
{
if let Err(error) = Command::new("cmd.exe")
.args(["/C", "start", "ms-settings:batterysaver"])
.spawn()
{
eprintln!("{}", error)
}
}
});
}
}

View File

@@ -12,7 +12,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.bar.json` configuration file reference for `v0.1.32`
/// The `komorebi.bar.json` configuration file reference for `v0.1.33`
pub struct KomobarConfig {
/// Bar positioning options
#[serde(alias = "viewport")]

View File

@@ -13,6 +13,7 @@ mod selected_frame;
mod storage;
mod time;
mod ui;
mod update;
mod widget;
use crate::bar::Komobar;

View File

@@ -100,7 +100,7 @@ impl Network {
if let Some(friendly_name) = &interface.friendly_name {
self.default_interface.clone_from(friendly_name);
self.networks_network_activity.refresh();
self.networks_network_activity.refresh(true);
for (interface_name, data) in &self.networks_network_activity {
if friendly_name.eq(interface_name) {

View File

@@ -50,7 +50,7 @@ impl Storage {
fn output(&mut self) -> Vec<String> {
let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.disks.refresh();
self.disks.refresh(true);
self.last_updated = now;
}

158
komorebi-bar/src/update.rs Normal file
View File

@@ -0,0 +1,158 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct UpdateConfig {
/// Enable the Update widget
pub enable: bool,
/// Data refresh interval (default: 12 hours)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<UpdateConfig> for Update {
fn from(value: UpdateConfig) -> Self {
let data_refresh_interval = value.data_refresh_interval.unwrap_or(12);
let mut latest_version = String::new();
let client = reqwest::blocking::Client::new();
if let Ok(response) = client
.get("https://api.github.com/repos/LGUG2Z/komorebi/releases/latest")
.header("User-Agent", "komorebi-bar-version-checker")
.send()
{
#[derive(Deserialize)]
struct Release {
tag_name: String,
}
if let Ok(release) =
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
{
let trimmed = release.tag_name.trim_start_matches("v");
latest_version = trimmed.to_string();
}
}
Self {
enable: value.enable,
data_refresh_interval,
installed_version: env!("CARGO_PKG_VERSION").to_string(),
latest_version,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
}
}
}
pub struct Update {
pub enable: bool,
data_refresh_interval: u64,
installed_version: String,
latest_version: String,
label_prefix: LabelPrefix,
last_updated: Instant,
}
impl Update {
fn output(&mut self) -> String {
let now = Instant::now();
if now.duration_since(self.last_updated)
> Duration::from_secs((self.data_refresh_interval * 60) * 60)
{
let client = reqwest::blocking::Client::new();
if let Ok(response) = client
.get("https://api.github.com/repos/LGUG2Z/komorebi/releases/latest")
.header("User-Agent", "komorebi-bar-version-checker")
.send()
{
#[derive(Deserialize)]
struct Release {
tag_name: String,
}
if let Ok(release) =
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
{
let trimmed = release.tag_name.trim_start_matches("v");
self.latest_version = trimmed.to_string();
}
}
self.last_updated = now;
}
if self.latest_version > self.installed_version {
format!("Update available! v{}", self.latest_version)
} else {
String::new()
}
}
}
impl BarWidget for Update {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::ROCKET_LAUNCH.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,
);
layout_job.append(
&output,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
valign: Align::Center,
..Default::default()
},
);
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked()
{
if let Err(error) = Command::new("explorer.exe")
.args([format!(
"https://github.com/LGUG2Z/komorebi/releases/v{}",
self.latest_version
)])
.spawn()
{
eprintln!("{}", error)
}
}
});
}
}
}
}

View File

@@ -17,6 +17,8 @@ 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 schemars::JsonSchema;
@@ -38,6 +40,7 @@ pub enum WidgetConfig {
Network(NetworkConfig),
Storage(StorageConfig),
Time(TimeConfig),
Update(UpdateConfig),
}
impl WidgetConfig {
@@ -52,6 +55,7 @@ impl WidgetConfig {
WidgetConfig::Network(config) => Box::new(Network::from(*config)),
WidgetConfig::Storage(config) => Box::new(Storage::from(*config)),
WidgetConfig::Time(config) => Box::new(Time::from(config.clone())),
WidgetConfig::Update(config) => Box::new(Update::from(*config)),
}
}
@@ -61,19 +65,20 @@ impl WidgetConfig {
WidgetConfig::Cpu(config) => config.enable,
WidgetConfig::Date(config) => config.enable,
WidgetConfig::Komorebi(config) => {
config.workspaces.as_ref().map_or(false, |w| w.enable)
|| config.layout.as_ref().map_or(false, |w| w.enable)
|| config.focused_window.as_ref().map_or(false, |w| w.enable)
config.workspaces.as_ref().is_some_and(|w| w.enable)
|| config.layout.as_ref().is_some_and(|w| w.enable)
|| config.focused_window.as_ref().is_some_and(|w| w.enable)
|| config
.configuration_switcher
.as_ref()
.map_or(false, |w| w.enable)
.is_some_and(|w| w.enable)
}
WidgetConfig::Media(config) => config.enable,
WidgetConfig::Memory(config) => config.enable,
WidgetConfig::Network(config) => config.enable,
WidgetConfig::Storage(config) => config.enable,
WidgetConfig::Time(config) => config.enable,
WidgetConfig::Update(config) => config.enable,
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-client"
version = "0.1.32"
version = "0.1.33"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-gui"
version = "0.1.32"
version = "0.1.33"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-themes"
version = "0.1.32"
version = "0.1.33"
edition = "2021"
[dependencies]

View File

@@ -1,8 +1,7 @@
[package]
name = "komorebi"
version = "0.1.32"
version = "0.1.33"
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021"
@@ -48,7 +47,7 @@ windows-core = { workspace = true }
windows-implement = { workspace = true }
windows-interface = { workspace = true }
winput = "0.2"
winreg = "0.52"
winreg = "0.53"
[build-dependencies]
shadow-rs = { workspace = true }

View File

@@ -1,3 +1,5 @@
use shadow_rs::ShadowBuilder;
fn main() {
shadow_rs::new().unwrap();
ShadowBuilder::builder().build().unwrap();
}

View File

@@ -427,7 +427,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
borders.remove(id);
}
for (idx, c) in ws.containers().iter().enumerate() {
'containers: for (idx, c) in ws.containers().iter().enumerate() {
// Get the border entry for this container from the map or create one
let mut new_border = false;
let border = match borders.entry(c.id().clone()) {
@@ -471,7 +471,17 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let reference_hwnd =
c.focused_window().copied().unwrap_or_default().hwnd;
let rect = WindowsApi::window_rect(reference_hwnd)?;
// 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
let rect = match WindowsApi::window_rect(reference_hwnd) {
Ok(rect) => rect,
Err(_) => {
let _ = border.destroy();
borders.remove(c.id());
continue 'containers;
}
};
let should_invalidate = match last_focus_state {
None => true,

View File

@@ -406,7 +406,7 @@ impl Direction for CustomLayout {
}
let (column_idx, column) = self.column_with_idx(idx);
column.map_or(false, |column| match column {
column.is_some_and(|column| match column {
Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))
| Column::Tertiary(ColumnSplit::Horizontal) => {
self.column_for_container_idx(idx - 1) == column_idx
@@ -420,7 +420,7 @@ impl Direction for CustomLayout {
}
let (column_idx, column) = self.column_with_idx(idx);
column.map_or(false, |column| match column {
column.is_some_and(|column| match column {
Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))
| Column::Tertiary(ColumnSplit::Horizontal) => {
self.column_for_container_idx(idx + 1) == column_idx

View File

@@ -238,7 +238,9 @@ pub struct SubscribeOptions {
pub filter_state_changes: bool,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema)]
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema, ValueEnum,
)]
pub enum StackbarMode {
Always,
Never,

View File

@@ -298,6 +298,7 @@ pub fn notify_subscribers(notification: Notification, state_has_been_modified: b
| NotificationEvent::Socket(SocketMessage::ReloadStaticConfiguration(_))
| NotificationEvent::WindowManager(WindowManagerEvent::TitleUpdate(_, _))
| NotificationEvent::WindowManager(WindowManagerEvent::Show(_, _))
| NotificationEvent::WindowManager(WindowManagerEvent::Uncloak(_, _))
);
let notification = &serde_json::to_string(&notification)?;

View File

@@ -177,7 +177,7 @@ fn main() -> Result<()> {
SESSION_ID.store(session_id, Ordering::SeqCst);
let mut system = sysinfo::System::new_all();
system.refresh_processes(ProcessesToUpdate::All);
system.refresh_processes(ProcessesToUpdate::All, true);
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe".as_ref()).collect();

View File

@@ -1662,6 +1662,7 @@ impl WindowManager {
}
SocketMessage::StackbarMode(mode) => {
STACKBAR_MODE.store(mode);
self.retile_all(true)?;
}
SocketMessage::StackbarLabel(label) => {
STACKBAR_LABEL.store(label);

View File

@@ -34,6 +34,8 @@ pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
let mut wm = arc.lock();
let offset = wm.work_area_offset;
let mut update_borders = false;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let work_area = *monitor.work_area_size();
let window_based_work_area_offset = (
@@ -51,7 +53,7 @@ pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
let reaped_orphans = workspace.reap_orphans()?;
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
workspace.update(&work_area, offset, window_based_work_area_offset)?;
border_manager::send_notification(None);
update_borders = true;
tracing::info!(
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,
@@ -62,5 +64,9 @@ pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
}
}
}
if update_borders {
border_manager::send_notification(None);
}
}
}

View File

@@ -35,6 +35,7 @@ use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::workspace::Workspace;
use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
@@ -148,6 +149,9 @@ pub struct WorkspaceConfig {
/// (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override: Option<bool>,
/// Specify an axis on which to flip the selected layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_flip: Option<Axis>,
}
impl From<&Workspace> for WorkspaceConfig {
@@ -202,6 +206,7 @@ impl From<&Workspace> for WorkspaceConfig {
apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset()),
window_container_behaviour: *value.window_container_behaviour(),
float_override: *value.float_override(),
layout_flip: value.layout_flip(),
}
}
}
@@ -238,7 +243,7 @@ impl From<&Monitor> for MonitorConfig {
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.json` static configuration file reference for `v0.1.32`
/// The `komorebi.json` static configuration file reference for `v0.1.33`
pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required
#[serde(skip_serializing_if = "Option::is_none")]

View File

@@ -435,13 +435,22 @@ impl WindowManager {
if let Some(state_monitor) = state.monitors.elements().get(monitor_idx) {
if let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx)
{
// to make sure padding changes get applied for users after a quick restart
let container_padding = workspace.container_padding();
let workspace_padding = workspace.workspace_padding();
*workspace = state_workspace.clone();
workspace.set_container_padding(container_padding);
workspace.set_workspace_padding(workspace_padding);
if state_monitor.focused_workspace_idx() == workspace_idx {
focused_workspace = workspace_idx;
}
}
}
}
if let Err(error) = monitor.focus_workspace(focused_workspace) {
tracing::warn!(
"cannot focus workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
@@ -449,6 +458,7 @@ impl WindowManager {
error,
);
}
if let Err(error) = monitor.load_focused_workspace(mouse_follows_focus) {
tracing::warn!(
"cannot load focused workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
@@ -456,6 +466,7 @@ impl WindowManager {
error,
);
}
if let Err(error) = monitor.update_focused_workspace(offset) {
tracing::warn!(
"cannot update workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
@@ -720,75 +731,80 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
.focused_workspace_idx();
let workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
// Go through all the monitors and workspaces
for (i, monitor) in self.monitors().iter().enumerate() {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
// And all the visible windows (at the top of a container)
for window in workspace.visible_windows().into_iter().flatten() {
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
let exe_name = window.exe()?;
let title = window.title()?;
let class = window.class()?;
let path = window.path()?;
// scope mutex locks to avoid deadlock if should_update_focused_workspace evaluates to true
// at the end of this function
{
let workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
// Go through all the monitors and workspaces
for (i, monitor) in self.monitors().iter().enumerate() {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
// And all the visible windows (at the top of a container)
for window in workspace.visible_windows().into_iter().flatten() {
let mut already_moved_window_handles =
self.already_moved_window_handles.lock();
for rule in &*workspace_matching_rules {
let matched = match &rule.matching_rule {
MatchingRule::Simple(r) => should_act_individual(
&title,
&exe_name,
&class,
&path,
r,
&regex_identifiers,
),
MatchingRule::Composite(r) => {
let mut composite_results = vec![];
for identifier in r {
composite_results.push(should_act_individual(
if let (Ok(exe_name), Ok(title), Ok(class), Ok(path)) =
(window.exe(), window.title(), window.class(), window.path())
{
for rule in &*workspace_matching_rules {
let matched = match &rule.matching_rule {
MatchingRule::Simple(r) => should_act_individual(
&title,
&exe_name,
&class,
&path,
identifier,
r,
&regex_identifiers,
));
),
MatchingRule::Composite(r) => {
let mut composite_results = vec![];
for identifier in r {
composite_results.push(should_act_individual(
&title,
&exe_name,
&class,
&path,
identifier,
&regex_identifiers,
));
}
composite_results.iter().all(|&x| x)
}
};
if matched {
let floating = workspace.floating_windows().contains(window);
if rule.initial_only {
if !already_moved_window_handles.contains(&window.hwnd) {
already_moved_window_handles.insert(window.hwnd);
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
} else {
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
}
composite_results.iter().all(|&x| x)
}
};
if matched {
let floating = workspace.floating_windows().contains(window);
if rule.initial_only {
if !already_moved_window_handles.contains(&window.hwnd) {
already_moved_window_handles.insert(window.hwnd);
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
} else {
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
}
}

View File

@@ -133,10 +133,14 @@ impl Workspace {
if config.container_padding.is_some() {
self.set_container_padding(config.container_padding);
} else {
self.set_container_padding(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
}
if config.workspace_padding.is_some() {
self.set_workspace_padding(config.workspace_padding);
} else {
self.set_container_padding(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
}
if let Some(layout) = &config.layout {
@@ -187,6 +191,10 @@ impl Workspace {
self.set_float_override(config.float_override);
}
if config.layout_flip.is_some() {
self.set_layout_flip(config.layout_flip);
}
Ok(())
}

View File

@@ -1,8 +1,7 @@
[package]
name = "komorebic-no-console"
version = "0.1.32"
version = "0.1.33"
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021"

View File

@@ -1,8 +1,7 @@
[package]
name = "komorebic"
version = "0.1.32"
version = "0.1.33"
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021"

View File

@@ -1,3 +1,5 @@
use shadow_rs::ShadowBuilder;
fn main() {
if std::fs::metadata("applications.json").is_err() {
let applications_json = reqwest::blocking::get(
@@ -6,5 +8,5 @@ fn main() {
std::fs::write("applications.json", applications_json).unwrap();
}
shadow_rs::new().unwrap();
ShadowBuilder::builder().build().unwrap();
}

View File

@@ -35,6 +35,7 @@ use miette::SourceSpan;
use paste::paste;
use schemars::gen::SchemaSettings;
use schemars::schema_for;
use serde::Deserialize;
use sysinfo::ProcessesToUpdate;
use which::which;
use windows::Win32::Foundation::HWND;
@@ -720,6 +721,13 @@ struct BorderImplementation {
style: komorebi_client::BorderImplementation,
}
#[derive(Parser)]
struct StackbarMode {
/// Desired stackbar mode
#[clap(value_enum)]
mode: komorebi_client::StackbarMode,
}
#[derive(Parser)]
struct Animation {
#[clap(value_enum)]
@@ -921,6 +929,13 @@ struct EnableAutostart {
masir: bool,
}
#[derive(Parser)]
struct Check {
/// Path to a static configuration JSON file
#[clap(action, short, long)]
komorebi_config: Option<PathBuf>,
}
#[derive(Parser)]
struct ReplaceConfiguration {
/// Static configuration JSON file from which the configuration should be loaded
@@ -953,7 +968,7 @@ enum SubCommand {
/// Kill background processes started by komorebic
Kill(Kill),
/// Check komorebi configuration and related files for common errors
Check,
Check(Check),
/// Show the path to komorebi.json
#[clap(alias = "config")]
Configuration,
@@ -1360,6 +1375,9 @@ enum SubCommand {
/// Set the border implementation
#[clap(arg_required_else_help = true)]
BorderImplementation(BorderImplementation),
/// Set the stackbar mode
#[clap(arg_required_else_help = true)]
StackbarMode(StackbarMode),
/// Enable or disable transparency for unfocused windows
#[clap(arg_required_else_help = true)]
Transparency(Transparency),
@@ -1575,7 +1593,7 @@ fn main() -> Result<()> {
std::fs::remove_file(shortcut_file)?;
}
}
SubCommand::Check => {
SubCommand::Check(args) => {
let home_display = HOME_DIR.display();
if HAS_CUSTOM_CONFIG_HOME.load(Ordering::SeqCst) {
println!("KOMOREBI_CONFIG_HOME detected: {home_display}\n");
@@ -1590,7 +1608,15 @@ fn main() -> Result<()> {
println!("Looking for configuration files in {home_display}\n");
let static_config = HOME_DIR.join("komorebi.json");
let static_config = if let Some(static_config) = args.komorebi_config {
println!(
"Using an arbitrary configuration file passed to --komorebi-config flag\n"
);
static_config
} else {
HOME_DIR.join("komorebi.json")
};
let config_pwsh = HOME_DIR.join("komorebi.ps1");
let config_ahk = HOME_DIR.join("komorebi.ahk");
let config_whkd = WHKD_CONFIG_DIR.join("whkdrc");
@@ -1667,6 +1693,30 @@ fn main() -> Result<()> {
println!("No komorebi configuration found in {home_display}\n");
println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n");
}
let client = reqwest::blocking::Client::new();
if let Ok(response) = client
.get("https://api.github.com/repos/LGUG2Z/komorebi/releases/latest")
.header("User-Agent", "komorebic-version-checker")
.send()
{
let version = env!("CARGO_PKG_VERSION");
#[derive(Deserialize)]
struct Release {
tag_name: String,
}
if let Ok(release) =
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
{
let trimmed = release.tag_name.trim_start_matches("v");
if trimmed > version {
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
}
}
}
}
SubCommand::Configuration => {
let static_config = HOME_DIR.join("komorebi.json");
@@ -2050,7 +2100,7 @@ fn main() -> Result<()> {
};
let mut system = sysinfo::System::new_all();
system.refresh_processes(ProcessesToUpdate::All);
system.refresh_processes(ProcessesToUpdate::All, true);
let mut attempts = 0;
let mut running = system
@@ -2071,7 +2121,7 @@ fn main() -> Result<()> {
print!("Waiting for komorebi.exe to start...");
std::thread::sleep(Duration::from_secs(3));
system.refresh_processes(ProcessesToUpdate::All);
system.refresh_processes(ProcessesToUpdate::All, true);
if system
.processes_by_name("komorebi.exe".as_ref())
@@ -2246,6 +2296,30 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
let stdout = String::from_utf8(output.stdout)?;
println!("{stdout}");
}
let client = reqwest::blocking::Client::new();
if let Ok(response) = client
.get("https://api.github.com/repos/LGUG2Z/komorebi/releases/latest")
.header("User-Agent", "komorebic-version-checker")
.send()
{
let version = env!("CARGO_PKG_VERSION");
#[derive(Deserialize)]
struct Release {
tag_name: String,
}
if let Ok(release) =
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
{
let trimmed = release.tag_name.trim_start_matches("v");
if trimmed > version {
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
}
}
}
}
SubCommand::Stop(arg) => {
if arg.whkd {
@@ -2325,7 +2399,7 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
send_message(&SocketMessage::Stop)?;
}
let mut system = sysinfo::System::new_all();
system.refresh_processes(ProcessesToUpdate::All);
system.refresh_processes(ProcessesToUpdate::All, true);
if system.processes_by_name("komorebi.exe".as_ref()).count() >= 1 {
println!("komorebi is still running, attempting to force-quit");
@@ -2721,6 +2795,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::BorderImplementation(arg) => {
send_message(&SocketMessage::BorderImplementation(arg.style))?;
}
SubCommand::StackbarMode(arg) => {
send_message(&SocketMessage::StackbarMode(arg.mode))?;
}
SubCommand::Transparency(arg) => {
send_message(&SocketMessage::Transparency(arg.boolean_state.into()))?;
}

View File

@@ -77,6 +77,7 @@ nav:
- cli/quickstart.md
- cli/start.md
- cli/stop.md
- cli/kill.md
- cli/check.md
- cli/configuration.md
- cli/bar-configuration.md
@@ -103,9 +104,11 @@ nav:
- cli/force-focus.md
- cli/cycle-focus.md
- cli/cycle-move.md
- cli/eager-focus.md
- cli/stack.md
- cli/unstack.md
- cli/cycle-stack.md
- cli/cycle-stack-index.md
- cli/focus-stack-window.md
- cli/stack-all.md
- cli/unstack-all.md
@@ -129,6 +132,7 @@ nav:
- cli/focus-workspaces.md
- cli/focus-monitor-workspace.md
- cli/focus-named-workspace.md
- cli/close-workspace.md
- cli/cycle-monitor.md
- cli/cycle-workspace.md
- cli/move-workspace-to-monitor.md
@@ -196,6 +200,7 @@ nav:
- cli/clear-workspace-rules.md
- cli/clear-named-workspace-rules.md
- cli/clear-all-workspace-rules.md
- cli/enforce-workspace-rules.md
- cli/identify-object-name-change-application.md
- cli/identify-tray-application.md
- cli/identify-layered-application.md
@@ -207,6 +212,7 @@ nav:
- cli/border-offset.md
- cli/border-style.md
- cli/border-implementation.md
- cli/stackbar-mode.md
- cli/transparency.md
- cli/transparency-alpha.md
- cli/toggle-transparency.md

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "KomobarConfig",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.32`",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.33`",
"type": "object",
"required": [
"left_widgets",
@@ -36,6 +36,10 @@
"description": "Enable the Battery widget",
"type": "boolean"
},
"hide_on_full_charge": {
"description": "Hide the widget if the battery is at full charge",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
@@ -778,6 +782,66 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Update"
],
"properties": {
"Update": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 12 hours)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Update widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
}
]
}
@@ -1154,6 +1218,10 @@
"description": "Enable the Battery widget",
"type": "boolean"
},
"hide_on_full_charge": {
"description": "Hide the widget if the battery is at full charge",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
@@ -1896,6 +1964,66 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Update"
],
"properties": {
"Update": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 12 hours)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Update widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
}
]
}
@@ -2025,6 +2153,10 @@
"description": "Enable the Battery widget",
"type": "boolean"
},
"hide_on_full_charge": {
"description": "Hide the widget if the battery is at full charge",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
@@ -2767,6 +2899,66 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Update"
],
"properties": {
"Update": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 12 hours)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Update widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
}
]
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig",
"description": "The `komorebi.json` static configuration file reference for `v0.1.32`",
"description": "The `komorebi.json` static configuration file reference for `v0.1.33`",
"type": "object",
"properties": {
"animation": {
@@ -1227,6 +1227,15 @@
"RightMainVerticalStack"
]
},
"layout_flip": {
"description": "Specify an axis on which to flip the selected layout (default: None)",
"type": "string",
"enum": [
"Horizontal",
"Vertical",
"HorizontalAndVertical"
]
},
"layout_rules": {
"description": "Layout rules (default: None)",
"type": "object",