Skip to content

Commit

Permalink
feat: add window effects for hiding title bar and changing corner sty…
Browse files Browse the repository at this point in the history
…le (#735)
  • Loading branch information
JonasWischeropp authored Sep 30, 2024
1 parent ddbb8d5 commit 4e5c662
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 18 deletions.
12 changes: 10 additions & 2 deletions packages/wm/src/app_command.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{iter, path::PathBuf};

use anyhow::{bail, Context};
use clap::{error::KindFormatter, Args, Parser, ValueEnum};
use clap::{error::KindFormatter, ArgAction, Args, Parser, ValueEnum};
use serde::{Deserialize, Deserializer, Serialize};
use tracing::{warn, Level};
use uuid::Uuid;
Expand All @@ -26,7 +26,8 @@ use crate::{
windows::{
commands::{
ignore_window, move_window_in_direction, move_window_to_workspace,
resize_window, set_window_size, update_window_state,
resize_window, set_title_bar_visibility, set_window_size,
update_window_state,
},
traits::WindowGetters,
WindowState,
Expand Down Expand Up @@ -205,6 +206,10 @@ pub enum InvokeCommand {
},
SetMinimized,
SetTiling,
SetTitleBarVisibility {
#[clap(required = true, action = ArgAction::Set)]
visible: bool,
},
ShellExec {
#[clap(required = true, trailing_var_arg = true)]
command: Vec<String>,
Expand Down Expand Up @@ -485,6 +490,9 @@ impl InvokeCommand {
_ => Ok(()),
}
}
InvokeCommand::SetTitleBarVisibility { visible } => {
set_title_bar_visibility(*visible, subject_container)
}
InvokeCommand::ShellExec { command } => {
shell_exec(&command.join(" "))
}
Expand Down
55 changes: 45 additions & 10 deletions packages/wm/src/common/commands/platform_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
traits::{CommonGetters, PositionGetters},
Container, WindowContainer,
},
user_config::{CursorJumpTrigger, UserConfig},
user_config::{CursorJumpTrigger, UserConfig, WindowEffectConfig},
windows::traits::WindowGetters,
wm_event::WmEvent,
wm_state::WmState,
Expand Down Expand Up @@ -176,22 +176,57 @@ fn apply_window_effects(

let window_effects = &config.value.window_effects;

let effect_config = match is_focused {
true => &window_effects.focused_window,
false => &window_effects.other_windows,
};

// Skip if both focused + non-focused window effects are disabled.
if !window_effects.focused_window.border.enabled
&& !window_effects.other_windows.border.enabled
if window_effects.focused_window.border.enabled
|| window_effects.other_windows.border.enabled
{
return;
apply_border_effect(&window, effect_config);
};

let border_config = match is_focused {
true => &config.value.window_effects.focused_window.border,
false => &config.value.window_effects.other_windows.border,
};
if window_effects.focused_window.hide_title_bar.enabled
|| window_effects.other_windows.hide_title_bar.enabled
{
apply_hide_title_bar_effect(&window, effect_config);
}

if window_effects.focused_window.corner.enabled
|| window_effects.other_windows.corner.enabled
{
apply_corner_effect(&window, effect_config);
}
}

let border_color = match border_config.enabled {
true => Some(&border_config.color),
fn apply_border_effect(
window: &WindowContainer,
effect_config: &WindowEffectConfig,
) {
let border_color = match effect_config.border.enabled {
true => Some(&effect_config.border.color),
false => None,
};

_ = window.native().set_border_color(border_color);
}

fn apply_hide_title_bar_effect(
window: &WindowContainer,
effect_config: &WindowEffectConfig,
) {
_ = window
.native()
.set_title_bar_visibility(!effect_config.hide_title_bar.enabled);
}

fn apply_corner_effect(
window: &WindowContainer,
effect_config: &WindowEffectConfig,
) {
_ = window
.native()
.set_corner_style(effect_config.corner.style.clone());
}
76 changes: 70 additions & 6 deletions packages/wm/src/common/platform/native_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use windows::{
Graphics::Dwm::{
DwmGetWindowAttribute, DwmSetWindowAttribute, DWMWA_BORDER_COLOR,
DWMWA_CLOAKED, DWMWA_COLOR_NONE, DWMWA_EXTENDED_FRAME_BOUNDS,
DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_DEFAULT, DWMWCP_DONOTROUND,
DWMWCP_ROUND, DWMWCP_ROUNDSMALL,
},
System::Threading::{
OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32,
Expand All @@ -21,21 +23,23 @@ use windows::{
EnumWindows, GetClassNameW, GetWindow, GetWindowLongPtrW,
GetWindowRect, GetWindowTextW, GetWindowThreadProcessId, IsIconic,
IsWindowVisible, IsZoomed, SendNotifyMessageW,
SetForegroundWindow, SetWindowPos, ShowWindowAsync, GWL_EXSTYLE,
GWL_STYLE, GW_OWNER, HWND_NOTOPMOST, HWND_TOPMOST,
SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_HIDEWINDOW,
SWP_NOACTIVATE, SWP_NOCOPYBITS, SWP_NOSENDCHANGING,
SetForegroundWindow, SetWindowLongPtrW, SetWindowPos,
ShowWindowAsync, GWL_EXSTYLE, GWL_STYLE, GW_OWNER, HWND_NOTOPMOST,
HWND_TOPMOST, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED,
SWP_HIDEWINDOW, SWP_NOACTIVATE, SWP_NOCOPYBITS, SWP_NOMOVE,
SWP_NOOWNERZORDER, SWP_NOSENDCHANGING, SWP_NOSIZE, SWP_NOZORDER,
SWP_SHOWWINDOW, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE,
SW_SHOWNA, WINDOW_EX_STYLE, WINDOW_STYLE, WM_CLOSE, WS_CAPTION,
WS_CHILD, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_MAXIMIZEBOX,
WS_THICKFRAME,
WS_CHILD, WS_DLGFRAME, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
WS_MAXIMIZEBOX, WS_THICKFRAME,
},
},
},
};

use crate::{
common::{Color, LengthValue, Memo, Rect, RectDelta},
user_config::CornerStyle,
windows::WindowState,
};

Expand Down Expand Up @@ -325,6 +329,66 @@ impl NativeWindow {
Ok(())
}

pub fn set_corner_style(
&self,
corner_type: CornerStyle,
) -> anyhow::Result<()> {
let corner_preference = match corner_type {
CornerStyle::WindowsDefault => DWMWCP_DEFAULT,
CornerStyle::Square => DWMWCP_DONOTROUND,
CornerStyle::Round => DWMWCP_ROUND,
CornerStyle::SmallRound => DWMWCP_ROUNDSMALL,
};

unsafe {
DwmSetWindowAttribute(
HWND(self.handle),
DWMWA_WINDOW_CORNER_PREFERENCE,
&(corner_preference.0) as *const _ as _,
std::mem::size_of::<i32>() as u32,
)?;
}

Ok(())
}

pub fn set_title_bar_visibility(
&self,
visible: bool,
) -> anyhow::Result<()> {
unsafe {
let style = GetWindowLongPtrW(HWND(self.handle), GWL_STYLE);

let new_style = match visible {
true => style | (WS_DLGFRAME.0 as isize),
false => style & !(WS_DLGFRAME.0 as isize),
};

if new_style != style {
SetWindowLongPtrW(HWND(self.handle), GWL_STYLE, new_style);
SetWindowPos(
HWND(self.handle),
HWND_NOTOPMOST,
0,
0,
0,
0,
SWP_FRAMECHANGED
| SWP_NOMOVE
| SWP_NOSIZE
| SWP_NOZORDER
| SWP_NOOWNERZORDER
| SWP_NOACTIVATE
| SWP_NOCOPYBITS
| SWP_NOSENDCHANGING
| SWP_ASYNCWINDOWPOS,
)?;
}
}

Ok(())
}

/// Gets the window's position, including the window's frame. Excludes
/// the window's shadow borders.
///
Expand Down
58 changes: 58 additions & 0 deletions packages/wm/src/user_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,14 @@ pub struct WindowEffectsConfig {
pub struct WindowEffectConfig {
/// Config for optionally applying a colored border.
pub border: BorderEffectConfig,

/// Config for optionally hiding the title bar.
#[serde(default)]
pub hide_title_bar: HideTitleBarEffectConfig,

/// Config for optionally changing the corner style.
#[serde(default)]
pub corner: CornerEffectConfig,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand All @@ -519,6 +527,35 @@ pub struct BorderEffectConfig {
pub color: Color,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "camelCase"))]
pub struct HideTitleBarEffectConfig {
/// Whether to enable the effect.
#[serde(default = "default_bool::<false>")]
pub enabled: bool,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "camelCase"))]
pub struct CornerEffectConfig {
/// Whether to enable the effect.
#[serde(default = "default_bool::<false>")]
pub enabled: bool,

/// Style of the window corners.
#[serde(default)]
pub style: CornerStyle,
}

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CornerStyle {
WindowsDefault,
Square,
Round,
SmallRound,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all(serialize = "camelCase"))]
pub struct WindowRuleConfig {
Expand Down Expand Up @@ -611,3 +648,24 @@ const fn default_blue() -> Color {
fn default_window_rule_on() -> Vec<WindowRuleEvent> {
vec![WindowRuleEvent::Manage, WindowRuleEvent::TitleChange]
}

impl Default for CornerStyle {
fn default() -> Self {
CornerStyle::WindowsDefault
}
}

impl Default for HideTitleBarEffectConfig {
fn default() -> Self {
HideTitleBarEffectConfig { enabled: false }
}
}

impl Default for CornerEffectConfig {
fn default() -> Self {
CornerEffectConfig {
enabled: false,
style: CornerStyle::WindowsDefault,
}
}
}
2 changes: 2 additions & 0 deletions packages/wm/src/windows/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod move_window_in_direction;
mod move_window_to_workspace;
mod resize_window;
mod run_window_rules;
mod set_title_bar_visibility;
mod set_window_size;
mod unmanage_window;
mod update_window_state;
Expand All @@ -14,6 +15,7 @@ pub use move_window_in_direction::*;
pub use move_window_to_workspace::*;
pub use resize_window::*;
pub use run_window_rules::*;
pub use set_title_bar_visibility::*;
pub use set_window_size::*;
pub use unmanage_window::*;
pub use update_window_state::*;
16 changes: 16 additions & 0 deletions packages/wm/src/windows/commands/set_title_bar_visibility.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::{
containers::{traits::CommonGetters, Container},
windows::traits::WindowGetters,
};

pub fn set_title_bar_visibility(
title_bar_is_visible: bool,
subject_container: Container,
) -> anyhow::Result<()> {
let window = subject_container.as_window_container()?;
window
.native()
.set_title_bar_visibility(title_bar_is_visible)?;

Ok(())
}

0 comments on commit 4e5c662

Please sign in to comment.