Feature: Animation. Need fixes

At current state animation is working. Configs are available for it.
animation enable/disable
animation-duration {duration in ms}
animation-ease -e {ease func. See --help}

Two major problems for my implementation:
1. Google Chrome window is broken and I don't know how to implement fix
   for it.
2. Border window does not update properly with window. Dont know how to
   fix it.
This commit is contained in:
thearturca
2023-10-05 20:12:30 +03:00
committed by LGUG2Z
parent 9d1c0ad790
commit 95df970860
10 changed files with 670 additions and 68 deletions

View File

@@ -0,0 +1,43 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[strum(serialize_all = "snake_case")]
pub enum EaseEnum {
Linear,
EaseInSine,
EaseOutSine,
EaseInOutSine,
EaseInQuad,
EaseOutQuad,
EaseInOutQuad,
EaseInCubic,
EaseInOutCubic,
EaseInQuart,
EaseOutQuart,
EaseInOutQuart,
EaseInQuint,
EaseOutQuint,
EaseInOutQuint,
EaseInExpo,
EaseOutExpo,
EaseInOutExpo,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc,
EaseInBack,
EaseOutBack,
EaseInOutBack,
EaseInElastic,
EaseOutElastic,
EaseInOutElastic,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
}

View File

@@ -14,6 +14,7 @@ use serde::Serialize;
use strum::Display;
use strum::EnumString;
pub use animation::EaseEnum;
pub use arrangement::Arrangement;
pub use arrangement::Axis;
pub use custom_layout::CustomLayout;
@@ -24,6 +25,7 @@ pub use layout::Layout;
pub use operation_direction::OperationDirection;
pub use rect::Rect;
pub mod animation;
pub mod arrangement;
pub mod config_generation;
pub mod custom_layout;
@@ -127,6 +129,9 @@ pub enum SocketMessage {
WatchConfiguration(bool),
CompleteConfiguration,
AltFocusHack(bool),
Animate(bool),
AnimateDuration(u64),
AnimateEase(EaseEnum),
ActiveWindowBorder(bool),
ActiveWindowBorderColour(WindowKind, u32, u32, u32),
ActiveWindowBorderWidth(i32),

View File

@@ -1,55 +1,510 @@
use color_eyre::Result;
use komorebi_core::EaseEnum;
use komorebi_core::Rect;
use std::thread::sleep;
use schemars::JsonSchema;
use std::f64::consts::PI;
use std::time::Duration;
use std::time::Instant;
pub struct Animation;
use crate::ANIMATE_EASE;
pub trait Ease {
fn evaluate(t: f64) -> f64;
}
pub struct Linear;
impl Ease for Linear {
fn evaluate(t: f64) -> f64 {
t
}
}
pub struct EaseInSine;
impl Ease for EaseInSine {
fn evaluate(t: f64) -> f64 {
1.0 - f64::cos((t * PI) / 2.0)
}
}
pub struct EaseOutSine;
impl Ease for EaseOutSine {
fn evaluate(t: f64) -> f64 {
f64::sin((t * PI) / 2.0)
}
}
pub struct EaseInOutSine;
impl Ease for EaseInOutSine {
fn evaluate(t: f64) -> f64 {
-(f64::cos(PI * t) - 1.0) / 2.0
}
}
pub struct EaseInQuad;
impl Ease for EaseInQuad {
fn evaluate(t: f64) -> f64 {
t * t
}
}
pub struct EaseOutQuad;
impl Ease for EaseOutQuad {
fn evaluate(t: f64) -> f64 {
(1.0 - t).mul_add(-1.0 - t, 1.0)
}
}
pub struct EaseInOutQuad;
impl Ease for EaseInOutQuad {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(2) / 2.0
}
}
}
pub struct EaseInCubic;
impl Ease for EaseInCubic {
fn evaluate(t: f64) -> f64 {
t * t * t
}
}
pub struct EaseOutCubic;
impl Ease for EaseOutCubic {
fn evaluate(t: f64) -> f64 {
1.0 - (1.0 - t).powi(3)
}
}
pub struct EaseInOutCubic;
impl Ease for EaseInOutCubic {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(3) / 2.0
}
}
}
pub struct EaseInQuart;
impl Ease for EaseInQuart {
fn evaluate(t: f64) -> f64 {
t * t * t * t
}
}
pub struct EaseOutQuart;
impl Ease for EaseOutQuart {
fn evaluate(t: f64) -> f64 {
1.0 - (1.0 - t).powi(4)
}
}
pub struct EaseInOutQuart;
impl Ease for EaseInOutQuart {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
8.0 * t * t * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(4) / 2.0
}
}
}
pub struct EaseInQuint;
impl Ease for EaseInQuint {
fn evaluate(t: f64) -> f64 {
t * t * t * t * t
}
}
pub struct EaseOutQuint;
impl Ease for EaseOutQuint {
fn evaluate(t: f64) -> f64 {
1.0 - (1.0 - t).powi(5)
}
}
pub struct EaseInOutQuint;
impl Ease for EaseInOutQuint {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
16.0 * t * t * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(5) / 2.0
}
}
}
pub struct EaseInExpo;
impl Ease for EaseInExpo {
fn evaluate(t: f64) -> f64 {
if t == 0.0 {
return t;
}
10.0f64.mul_add(t, -10.0).exp2()
}
}
pub struct EaseOutExpo;
impl Ease for EaseOutExpo {
fn evaluate(t: f64) -> f64 {
if t == 1.0 {
return t;
}
1.0 - (-10.0 * t).exp2()
}
}
pub struct EaseInOutExpo;
impl Ease for EaseInOutExpo {
fn evaluate(t: f64) -> f64 {
if t == 0.0 || t == 1.0 {
return t;
}
if t < 0.5 {
20.0f64.mul_add(t, -10.0).exp2() / 2.0
} else {
(2.0 - (-20.0f64).mul_add(t, 10.0).exp2()) / 2.0
}
}
}
pub struct EaseInCirc;
impl Ease for EaseInCirc {
fn evaluate(t: f64) -> f64 {
1.0 - f64::sqrt(t.mul_add(-t, 1.0))
}
}
pub struct EaseOutCirc;
impl Ease for EaseOutCirc {
fn evaluate(t: f64) -> f64 {
f64::sqrt((t - 1.0).mul_add(-(t - 1.0), 1.0))
}
}
pub struct EaseInOutCirc;
impl Ease for EaseInOutCirc {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
(1.0 - f64::sqrt((2.0 * t).mul_add(-(2.0 * t), 1.0))) / 2.0
} else {
(f64::sqrt(
(-2.0f64)
.mul_add(t, 2.0)
.mul_add(-(-2.0f64).mul_add(t, 2.0), 1.0),
) + 1.0)
/ 2.0
}
}
}
pub struct EaseInBack;
impl Ease for EaseInBack {
fn evaluate(t: f64) -> f64 {
let c1 = 1.70158;
let c3 = c1 + 1.0;
(c3 * t * t).mul_add(t, -c1 * t * t)
}
}
pub struct EaseOutBack;
impl Ease for EaseOutBack {
fn evaluate(t: f64) -> f64 {
let c1: f64 = 1.70158;
let c3: f64 = c1 + 1.0;
c1.mul_add((t - 1.0).powi(2), c3.mul_add((t - 1.0).powi(3), 1.0))
}
}
pub struct EaseInOutBack;
impl Ease for EaseInOutBack {
fn evaluate(t: f64) -> f64 {
let c1: f64 = 1.70158;
let c2: f64 = c1 * 1.525;
if t < 0.5 {
((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0).mul_add(t, -c2)) / 2.0
} else {
((2.0f64.mul_add(t, -2.0))
.powi(2)
.mul_add((c2 + 1.0).mul_add(t.mul_add(2.0, -2.0), c2), 2.0))
/ 2.0
}
}
}
pub struct EaseInElastic;
impl Ease for EaseInElastic {
fn evaluate(t: f64) -> f64 {
if t == 1.0 || t == 0.0 {
return t;
}
let c4 = (2.0 * PI) / 3.0;
-(10.0f64.mul_add(t, -10.0).exp2()) * f64::sin(t.mul_add(10.0, -10.75) * c4)
}
}
pub struct EaseOutElastic;
impl Ease for EaseOutElastic {
fn evaluate(t: f64) -> f64 {
if t == 1.0 || t == 0.0 {
return t;
}
let c4 = (2.0 * PI) / 3.0;
(-10.0 * t)
.exp2()
.mul_add(f64::sin(t.mul_add(10.0, -0.75) * c4), 1.0)
}
}
pub struct EaseInOutElastic;
impl Ease for EaseInOutElastic {
fn evaluate(t: f64) -> f64 {
if t == 1.0 || t == 0.0 {
return t;
}
let c5 = (2.0 * PI) / 4.5;
if t < 0.5 {
-(20.0f64.mul_add(t, -10.0).exp2() * f64::sin(20.0f64.mul_add(t, -11.125) * c5)) / 2.0
} else {
((-20.0f64).mul_add(t, 10.0).exp2() * f64::sin(20.0f64.mul_add(t, -11.125) * c5)) / 2.0
+ 1.0
}
}
}
pub struct EaseInBounce;
impl Ease for EaseInBounce {
fn evaluate(t: f64) -> f64 {
1.0 - EaseOutBounce::evaluate(1.0 - t)
}
}
pub struct EaseOutBounce;
impl Ease for EaseOutBounce {
fn evaluate(t: f64) -> f64 {
let mut time = t;
let n1 = 7.5625;
let d1 = 2.75;
if t < 1.0 / d1 {
n1 * time * time
} else if time < 2.0 / d1 {
time -= 1.5 / d1;
(n1 * time).mul_add(time, 0.75)
} else if time < 2.5 / d1 {
time -= 2.25 / d1;
(n1 * time).mul_add(time, 0.9375)
} else {
time -= 2.625 / d1;
(n1 * time).mul_add(time, 0.984_375)
}
}
}
pub struct EaseInOutBounce;
impl Ease for EaseInOutBounce {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
(1.0 - EaseOutBounce::evaluate(2.0f64.mul_add(-t, 1.0))) / 2.0
} else {
(1.0 + EaseOutBounce::evaluate(2.0f64.mul_add(t, -1.0))) / 2.0
}
}
}
fn apply_ease_func(t: f64) -> f64 {
let ease = *ANIMATE_EASE.lock();
match ease {
EaseEnum::Linear => Linear::evaluate(t),
EaseEnum::EaseInSine => EaseInSine::evaluate(t),
EaseEnum::EaseOutSine => EaseOutSine::evaluate(t),
EaseEnum::EaseInOutSine => EaseInOutSine::evaluate(t),
EaseEnum::EaseInQuad => EaseInQuad::evaluate(t),
EaseEnum::EaseOutQuad => EaseOutQuad::evaluate(t),
EaseEnum::EaseInOutQuad => EaseInOutQuad::evaluate(t),
EaseEnum::EaseInCubic => EaseInCubic::evaluate(t),
EaseEnum::EaseInOutCubic => EaseInOutCubic::evaluate(t),
EaseEnum::EaseInQuart => EaseInQuart::evaluate(t),
EaseEnum::EaseOutQuart => EaseOutQuart::evaluate(t),
EaseEnum::EaseInOutQuart => EaseInOutQuart::evaluate(t),
EaseEnum::EaseInQuint => EaseInQuint::evaluate(t),
EaseEnum::EaseOutQuint => EaseOutQuint::evaluate(t),
EaseEnum::EaseInOutQuint => EaseInOutQuint::evaluate(t),
EaseEnum::EaseInExpo => EaseInExpo::evaluate(t),
EaseEnum::EaseOutExpo => EaseOutExpo::evaluate(t),
EaseEnum::EaseInOutExpo => EaseInOutExpo::evaluate(t),
EaseEnum::EaseInCirc => EaseInCirc::evaluate(t),
EaseEnum::EaseOutCirc => EaseOutCirc::evaluate(t),
EaseEnum::EaseInOutCirc => EaseInOutCirc::evaluate(t),
EaseEnum::EaseInBack => EaseInBack::evaluate(t),
EaseEnum::EaseOutBack => EaseOutBack::evaluate(t),
EaseEnum::EaseInOutBack => EaseInOutBack::evaluate(t),
EaseEnum::EaseInElastic => EaseInElastic::evaluate(t),
EaseEnum::EaseOutElastic => EaseOutElastic::evaluate(t),
EaseEnum::EaseInOutElastic => EaseInOutElastic::evaluate(t),
EaseEnum::EaseInBounce => EaseInBounce::evaluate(t),
EaseEnum::EaseOutBounce => EaseOutBounce::evaluate(t),
EaseEnum::EaseInOutBounce => EaseInOutBounce::evaluate(t),
}
}
#[derive(Debug, Clone, Copy, JsonSchema)]
pub struct Animation {
// is_cancel: AtomicBool,
// pub in_progress: AtomicBool,
is_cancel: bool,
pub in_progress: bool,
}
impl Default for Animation {
fn default() -> Self {
Animation {
// I'm not sure if this is the right way to do it
// I've tried to use Arc<Mutex<bool>> but it dooes not implement Copy trait
// and I dont want to rewrite everything cause I'm not experienced with rust
// Down here you can see the idea I've tried to achive like in any other OOP language
// My thought is that in order to prevent Google Chrome breaking render window
// I need to cancel animation if user starting new window movement. So window stops
// moving at one point and then fires new animation.
// But my approach does not work because of rust borrowing rules and wired pointers
// lifetime annotation that I dont know how to use.
is_cancel: false,
in_progress: false,
// is_cancel: AtomicBool::new(false),
// in_progress: AtomicBool::new(false),
}
}
}
impl Animation {
pub fn cancel(&mut self) -> Result<()> {
if !self.in_progress {
return Ok(());
}
self.is_cancel = true;
let max_duration = Duration::from_secs(1);
let spent_duration = Instant::now();
while self.in_progress {
if spent_duration.elapsed() >= max_duration {
break;
}
std::thread::sleep(Duration::from_millis(16));
}
Ok(())
}
pub fn lerp(x: i32, new_x: i32, t: f64) -> i32 {
(x as f64 + (new_x - x) as f64 * t) as i32
let time = apply_ease_func(t);
f64::from(new_x - x).mul_add(time, f64::from(x)) as i32
}
pub fn lerp_rect(original_rect: &Rect, new_rect: &Rect, t: f64) -> Rect {
let is_half_way = t > 0.5;
let mut rect = Rect::default();
rect.top = Animation::lerp(original_rect.top, new_rect.top, t);
rect.left = Animation::lerp(original_rect.left, new_rect.left, t);
rect.bottom = if is_half_way {
new_rect.bottom
} else {
original_rect.bottom
};
rect.right = if is_half_way {
new_rect.right
} else {
original_rect.right
};
rect
Rect {
left: Self::lerp(original_rect.left, new_rect.left, t),
top: Self::lerp(original_rect.top, new_rect.top, t),
right: Self::lerp(original_rect.right, new_rect.right, t),
bottom: Self::lerp(original_rect.bottom, new_rect.bottom, t),
}
}
pub fn animate(duration: Duration, mut f: impl FnMut(f64) -> Result<()>) -> bool {
pub fn animate(
&mut self,
duration: Duration,
mut f: impl FnMut(f64) -> Result<()>,
) -> Result<()> {
self.in_progress = true;
// set target frame time to match 240 fps (my max refresh rate of monitor)
// probably not the best way to do it is take actual monitor refresh rate
// or make it configurable
let target_frame_time = Duration::from_millis(1000 / 240);
let mut progress = 0.0;
let &animation_start = &Instant::now();
let animation_start = Instant::now();
// start animation
while progress < 1.0 {
let tick_start = Instant::now();
f(progress).unwrap();
progress = animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
if progress > 1.0 {
progress = 1.0;
// check if animation is cancelled
if self.is_cancel {
// cancel animation
// set all flags
self.is_cancel = !self.is_cancel;
self.in_progress = false;
return Ok(());
}
let tick_start = Instant::now();
// calculate progress
progress = animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
f(progress).ok();
// sleep until next frame
while tick_start.elapsed() < target_frame_time {
sleep(target_frame_time - tick_start.elapsed());
std::thread::sleep(target_frame_time - tick_start.elapsed());
}
}
f(progress).unwrap();
true
self.in_progress = false;
// limit progress to 1.0 if animation took longer
if progress > 1.0 {
progress = 1.0;
}
// process animation for 1.0 to set target position
f(progress)
}
}

View File

@@ -16,6 +16,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
#[cfg(feature = "deadlock_detection")]
@@ -26,6 +27,7 @@ use color_eyre::Result;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::Backoff;
use komorebi_core::EaseEnum;
use lazy_static::lazy_static;
use os_info::Version;
#[cfg(feature = "deadlock_detection")]
@@ -215,6 +217,8 @@ lazy_static! {
static ref BORDER_OFFSET: Arc<Mutex<Option<Rect>>> =
Arc::new(Mutex::new(None));
static ref ANIMATE_EASE: Arc<Mutex<EaseEnum>> = Arc::new(Mutex::new(EaseEnum::EaseInOutExpo));
// Use app-specific titlebar removal options where possible
// eg. Windows Terminal, IntelliJ IDEA, Firefox
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
@@ -238,6 +242,8 @@ pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(20);
// 0 0 0 aka pure black, I doubt anyone will want this as a border colour
pub const TRANSPARENCY_COLOUR: u32 = 0;
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
pub static ANIMATE_ENABLED: AtomicBool = AtomicBool::new(true);
pub static ANIMATE_DURATION: AtomicU64 = AtomicU64::new(250);
pub static HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0);

View File

@@ -47,6 +47,9 @@ use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::ALT_FOCUS_HACK;
use crate::ANIMATE_DURATION;
use crate::ANIMATE_EASE;
use crate::ANIMATE_ENABLED;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_COLOUR_MONOCLE;
use crate::BORDER_COLOUR_SINGLE;
@@ -1140,6 +1143,15 @@ impl WindowManager {
self.hide_border()?;
}
}
SocketMessage::Animate(enable) => {
ANIMATE_ENABLED.store(enable, Ordering::SeqCst);
}
SocketMessage::AnimateDuration(duration) => {
ANIMATE_DURATION.store(duration, Ordering::SeqCst);
}
SocketMessage::AnimateEase(ease) => {
*ANIMATE_EASE.lock() = ease;
}
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => {
match kind {
WindowKind::Single => {
@@ -1295,7 +1307,7 @@ impl WindowManager {
| SocketMessage::CycleMoveWindow(_)
| SocketMessage::MoveWindow(_) => {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let foreground_window = Window::new(foreground);
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;

View File

@@ -1,11 +1,12 @@
use crate::com::SetCloak;
use crate::ANIMATE_DURATION;
use crate::ANIMATE_ENABLED;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write as _;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use color_eyre::eyre::anyhow;
@@ -47,6 +48,7 @@ use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Clone, Copy, JsonSchema)]
pub struct Window {
pub(crate) hwnd: isize,
animation: Animation,
}
impl Display for Window {
@@ -106,6 +108,14 @@ impl Serialize for Window {
}
impl Window {
// for instantiation of animation struct
pub fn new(hwnd: isize) -> Self {
Window {
hwnd,
animation: Animation::default(),
}
}
pub const fn hwnd(self) -> HWND {
HWND(self.hwnd)
}
@@ -125,21 +135,37 @@ impl Window {
true,
)
}
pub fn animate_position(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> {
let duration = Duration::from_millis(200);
pub fn animate_position(&mut self, layout: &Rect, top: bool) -> Result<()> {
let hwnd = self.hwnd();
let curr_rect = WindowsApi::window_rect(hwnd).unwrap();
if assert_eq!(curr_rect, *layout) {
WindowsApi::position_window(hwnd, layout, top);
if curr_rect.left == layout.left
&& curr_rect.top == layout.top
&& curr_rect.bottom == layout.bottom
&& curr_rect.right == layout.right
{
WindowsApi::position_window(hwnd, layout, top)
} else {
let target_rect = *layout;
let duration = Duration::from_millis(ANIMATE_DURATION.load(Ordering::SeqCst));
let mut animation = self.animation;
std::thread::spawn(move || {
animation
.animate(duration, |progress: f64| {
let new_rect = Animation::lerp_rect(&curr_rect, &target_rect, progress);
if progress < 1.0 {
// using MoveWindow because it runs faster than SetWindowPos
// so animation have more fps and feel smoother
WindowsApi::move_window(hwnd, &new_rect, true)
} else {
WindowsApi::position_window(hwnd, &new_rect, top)
}
})
.unwrap();
});
Ok(())
}
let animate_window = |progress: f64| {
let new_rect = Animation::lerp_rect(&curr_rect, layout, progress);
WindowsApi::position_window(hwnd, &new_rect, top);
};
Animation::animate(duration, animate_window);
Ok(())
}
pub fn set_position(
@@ -173,14 +199,17 @@ impl Window {
rect.bottom += invisible_borders.bottom;
}
let hwnd = self.hwnd();
if ANIMATE_ENABLED.load(Ordering::SeqCst) {
// check if animation is in progress
if self.animation.in_progress {
// wait for cancle animation
self.animation.cancel().unwrap();
}
thread::spawn(move || {
Window::animate_position(hwnd, &rect, top).unwrap();
});
Ok(())
// WindowsApi::position_window(self.hwnd(), &rect, top)
self.animate_position(&rect, top)
} else {
WindowsApi::position_window(self.hwnd(), &rect, top)
}
}
pub fn hide(self) {

View File

@@ -217,7 +217,7 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn show_border(&self) -> Result<()> {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let foreground_window = Window::new(foreground);
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;
@@ -589,7 +589,7 @@ impl WindowManager {
// Hide the window we are about to remove if it is on the currently focused workspace
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
Window { hwnd: op.hwnd }.hide();
Window::new(op.hwnd).hide();
should_update_focused_workspace = true;
}
@@ -619,7 +619,7 @@ impl WindowManager {
.get_mut(op.target_workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
target_workspace.new_container_for_window(Window { hwnd: op.hwnd });
target_workspace.new_container_for_window(Window::new(op.hwnd));
}
// Only re-tile the focused workspace if we need to
@@ -663,14 +663,14 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn manage_focused_window(&mut self) -> Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Manage(Window { hwnd });
let event = WindowManagerEvent::Manage(Window::new(hwnd));
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
}
#[tracing::instrument(skip(self))]
pub fn unmanage_focused_window(&mut self) -> Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Unmanage(Window { hwnd });
let event = WindowManagerEvent::Unmanage(Window::new(hwnd));
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
}
@@ -708,13 +708,14 @@ impl WindowManager {
];
if !known_hwnd {
let class = Window { hwnd }.class()?;
let class = Window::new(hwnd).class()?;
// Some applications (Electron/Chromium-based, explorer) have (invisible?) overlays
// windows that we need to look beyond to find the actual window to raise
if overlay_classes.contains(&class) {
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
if let Some(exe_hwnd) = workspace.hwnd_from_exe(&Window { hwnd }.exe()?)
if let Some(exe_hwnd) =
workspace.hwnd_from_exe(&Window::new(hwnd).exe()?)
{
hwnd = exe_hwnd;
known_hwnd = true;
@@ -725,11 +726,11 @@ impl WindowManager {
}
if known_hwnd {
let event = WindowManagerEvent::Raise(Window { hwnd });
let event = WindowManagerEvent::Raise(Window::new(hwnd));
self.has_pending_raise_op = true;
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
} else {
tracing::debug!("not raising unknown window: {}", Window { hwnd });
tracing::debug!("not raising unknown window: {}", Window::new(hwnd,));
Ok(())
}
}
@@ -843,9 +844,7 @@ impl WindowManager {
} else if let Ok(window) = self.focused_window_mut() {
window.focus(self.mouse_follows_focus)?;
} else {
let desktop_window = Window {
hwnd: WindowsApi::desktop_window()?,
};
let desktop_window = Window::new(WindowsApi::desktop_window()?);
let rect = self.focused_monitor_size()?;
WindowsApi::center_cursor_in_rect(&rect)?;

View File

@@ -81,6 +81,7 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use windows::Win32::UI::WindowsAndMessaging::MoveWindow;
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassA;
@@ -354,6 +355,20 @@ impl WindowsApi {
.process()
}
pub fn move_window(hwnd: HWND, layout: &Rect, repaint: bool) -> Result<()> {
unsafe {
MoveWindow(
hwnd,
layout.left,
layout.top,
layout.right,
layout.bottom,
repaint,
)
}
.process()
}
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
// BOOL is returned but does not signify whether or not the operation was succesful
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow

View File

@@ -104,7 +104,7 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
let is_minimized = WindowsApi::is_iconic(hwnd);
if is_visible && is_window && !is_minimized {
let window = Window { hwnd: hwnd.0 };
let window = Window::new(hwnd.0);
if let Ok(should_manage) = window.should_manage(None) {
if should_manage {
@@ -132,7 +132,7 @@ pub extern "system" fn win_event_hook(
return;
}
let window = Window { hwnd: hwnd.0 };
let window = Window::new(hwnd.0);
let winevent = unsafe { ::std::mem::transmute(event) };
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
@@ -196,7 +196,7 @@ pub extern "system" fn hidden_window(
unsafe {
match message {
WM_DISPLAYCHANGE => {
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
let event_type = WindowManagerEvent::DisplayChange(Window::new(window.0));
WINEVENT_CALLBACK_CHANNEL
.lock()
.0
@@ -211,7 +211,7 @@ pub extern "system" fn hidden_window(
if wparam.0 as u32 == SPI_SETWORKAREA.0
|| wparam.0 as u32 == SPI_ICONVERTICALSPACING.0
{
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
let event_type = WindowManagerEvent::DisplayChange(Window::new(window.0));
WINEVENT_CALLBACK_CHANNEL
.lock()
.0
@@ -224,7 +224,7 @@ pub extern "system" fn hidden_window(
WM_DEVICECHANGE => {
#[allow(clippy::cast_possible_truncation)]
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
let event_type = WindowManagerEvent::DisplayChange(Window::new(window.0));
WINEVENT_CALLBACK_CHANNEL
.lock()
.0

View File

@@ -21,6 +21,7 @@ use color_eyre::Result;
use fs_tail::TailedFile;
use heck::ToKebabCase;
use komorebi_core::resolve_home_path;
use komorebi_core::EaseEnum;
use lazy_static::lazy_static;
use paste::paste;
use sysinfo::SystemExt;
@@ -627,6 +628,25 @@ struct ActiveWindowBorderOffset {
offset: i32,
}
#[derive(Parser, AhkFunction)]
struct Animate {
#[clap(value_enum)]
boolean_state: BooleanState,
}
#[derive(Parser, AhkFunction)]
struct AnimateDuration {
/// Desired animation durations in ms
duration: u64,
}
#[derive(Parser, AhkFunction)]
struct AnimateEase {
/// Desired ease function for animation
#[clap(value_enum, short, long, default_value = "linear")]
ease_func: EaseEnum,
}
#[derive(Parser, AhkFunction)]
#[allow(clippy::struct_excessive_bools)]
struct Start {
@@ -1059,6 +1079,15 @@ enum SubCommand {
/// Set the offset for the active window border
#[clap(arg_required_else_help = true)]
ActiveWindowBorderOffset(ActiveWindowBorderOffset),
/// Enable or disable the window move animation
#[clap(arg_required_else_help = true)]
Animate(Animate),
/// Set the duration for the window move animation in ms
#[clap(arg_required_else_help = true)]
AnimateDuration(AnimateDuration),
/// Set the ease function for the window move animation
#[clap(arg_required_else_help = true)]
AnimateEase(AnimateEase),
/// Enable or disable focus follows mouse for the operating system
#[clap(arg_required_else_help = true)]
FocusFollowsMouse(FocusFollowsMouse),
@@ -1986,6 +2015,15 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
SubCommand::ActiveWindowBorderOffset(arg) => {
send_message(&SocketMessage::ActiveWindowBorderOffset(arg.offset).as_bytes()?)?;
}
SubCommand::Animate(arg) => {
send_message(&SocketMessage::Animate(arg.boolean_state.into()).as_bytes()?)?;
}
SubCommand::AnimateDuration(arg) => {
send_message(&SocketMessage::AnimateDuration(arg.duration).as_bytes()?)?;
}
SubCommand::AnimateEase(arg) => {
send_message(&SocketMessage::AnimateEase(arg.ease_func).as_bytes()?)?;
}
SubCommand::ResizeDelta(arg) => {
send_message(&SocketMessage::ResizeDelta(arg.pixels).as_bytes()?)?;
}