mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-15 21:32:26 +01:00
Plan step 10, S3. The two default paddings, the aspect ratio of a window toggled to floating (centre with resize), layout_defaults and display_index_preferences are no longer statics: their writers (apply_globals through SharedDelta::apply, the DisplayIndexPreference command) and all readers use settings() together. The semantics are the statics': a missing padding or display_index_preferences key keeps the value, a missing layout_defaults clears it, a missing aspect ratio is Standard (not the initial Widescreen), and the command inserts one entry. Readers get an owned copy from one snapshot (accessors in settings.rs). BEHAVIOUR only in this: apply_globals used to store these keys one at a time and now publishes the group in one update, so a thread can no longer see a half-applied reload of them. Value semantics are pinned by crates/komorebi-settings tests/reload.rs and shared.rs (reload merges, a few keys reset, a load never touches groups it does not name). Windows: window.rs, windows_api.rs, workspace.rs, monitor.rs, monitor_reconciliator, stackbar_manager (the gap reads the container padding from settings), state.rs, static_config.rs, process_command.rs, lib.rs. MONITOR_INDEX_PREFERENCES stays a static (Windows only). Verified: Windows clippy -D warnings --locked --all-targets on komorebi-windows (cross): clean. e2e not run: config_reload, layouts, workspaces, stackbars (container padding gap) are needed.
1421 lines
47 KiB
Rust
1421 lines
47 KiB
Rust
use crate::AnimationStyle;
|
|
use crate::FLOATING_APPLICATIONS;
|
|
use crate::HIDDEN_HWNDS;
|
|
use crate::HIDING_BEHAVIOUR;
|
|
use crate::IGNORE_IDENTIFIERS;
|
|
use crate::LAYERED_WHITELIST;
|
|
use crate::MANAGE_IDENTIFIERS;
|
|
use crate::NO_TITLEBAR;
|
|
use crate::PERMAIGNORE_CLASSES;
|
|
use crate::REGEX_IDENTIFIERS;
|
|
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
|
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
|
use crate::WSL2_UI_PROCESSES;
|
|
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
|
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
|
|
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
|
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
|
use crate::animation::ANIMATION_MANAGER;
|
|
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
|
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
|
use crate::animation::AnimationEngine;
|
|
use crate::animation::RenderDispatcher;
|
|
use crate::animation::ghost_dispatcher::GhostDispatcher;
|
|
use crate::animation::lerp::Lerp;
|
|
use crate::animation::prefix::AnimationPrefix;
|
|
use crate::animation::prefix::new_animation_key;
|
|
use crate::animation::transition;
|
|
use crate::border_manager;
|
|
use crate::cloaked;
|
|
use crate::com::SetCloak;
|
|
use crate::core::ApplicationIdentifier;
|
|
use crate::core::HidingBehaviour;
|
|
use crate::core::Rect;
|
|
use crate::core::config_generation::IdWithIdentifier;
|
|
use crate::core::config_generation::MatchingRule;
|
|
use crate::core::config_generation::MatchingStrategy;
|
|
use crate::live_resize;
|
|
use crate::styles::ExtendedWindowStyle;
|
|
use crate::styles::WindowStyle;
|
|
use crate::transparency_manager;
|
|
use crate::window_manager_event::WindowManagerEvent;
|
|
use crate::windows_api;
|
|
use crate::windows_api::WindowsApi;
|
|
use crate::windows_backend::backend;
|
|
use color_eyre::eyre;
|
|
use crossbeam_utils::atomic::AtomicConsume;
|
|
use komorebi_backend::WindowId;
|
|
pub use komorebi_config::aspect_ratio::AspectRatio;
|
|
pub use komorebi_config::aspect_ratio::PredefinedAspectRatio;
|
|
use regex::Regex;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use serde::Serializer;
|
|
use serde::ser::SerializeStruct;
|
|
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::AtomicI32;
|
|
use std::sync::atomic::Ordering;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
use windows::Win32::Foundation::HWND;
|
|
|
|
pub static MINIMUM_WIDTH: AtomicI32 = AtomicI32::new(0);
|
|
pub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);
|
|
|
|
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct Window {
|
|
pub hwnd: isize,
|
|
}
|
|
|
|
/// What names a [`Window`]: the type the shared container and workspace code compares ids as
|
|
pub type PlatformWindowId = <Window as komorebi_core::ContainerWindow>::Id;
|
|
|
|
/// The handle of the window that has the focus, if any window does
|
|
pub fn foreground_window_id() -> Option<PlatformWindowId> {
|
|
WindowsApi::foreground_window().ok()
|
|
}
|
|
|
|
/// Where the pointer is, if the system says
|
|
pub fn cursor_position() -> Option<(i32, i32)> {
|
|
WindowsApi::cursor_pos()
|
|
.ok()
|
|
.map(|point| (point.x, point.y))
|
|
}
|
|
|
|
impl From<isize> for Window {
|
|
fn from(value: isize) -> Self {
|
|
Self { hwnd: value }
|
|
}
|
|
}
|
|
|
|
impl From<HWND> for Window {
|
|
fn from(value: HWND) -> Self {
|
|
Self {
|
|
hwnd: value.0 as isize,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::module_name_repetitions)]
|
|
#[derive(Debug, Clone, Serialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct WindowDetails {
|
|
pub title: String,
|
|
pub exe: String,
|
|
pub class: String,
|
|
}
|
|
|
|
impl From<Window> for WindowDetails {
|
|
fn from(value: Window) -> Self {
|
|
Self {
|
|
title: value.title().unwrap_or_default(),
|
|
exe: value.exe().unwrap_or_default(),
|
|
class: value.class().unwrap_or_default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for Window {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
let mut display = format!("(hwnd: {}", self.hwnd);
|
|
|
|
if let Some(title) = self.title() {
|
|
write!(display, ", title: {title}")?;
|
|
}
|
|
|
|
if let Some(exe) = self.exe() {
|
|
write!(display, ", exe: {exe}")?;
|
|
}
|
|
|
|
if let Some(class) = self.class() {
|
|
write!(display, ", class: {class}")?;
|
|
}
|
|
|
|
write!(display, ")")?;
|
|
|
|
write!(f, "{display}")
|
|
}
|
|
}
|
|
|
|
impl Serialize for Window {
|
|
fn serialize<S>(&self, serializer: S) -> eyre::Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let mut state = serializer.serialize_struct("Window", 5)?;
|
|
state.serialize_field("hwnd", &self.hwnd)?;
|
|
state.serialize_field(
|
|
"title",
|
|
&self
|
|
.title()
|
|
.unwrap_or_else(|| String::from("could not get window title")),
|
|
)?;
|
|
state.serialize_field(
|
|
"exe",
|
|
&self
|
|
.exe()
|
|
.unwrap_or_else(|| String::from("could not get window exe")),
|
|
)?;
|
|
state.serialize_field(
|
|
"class",
|
|
&self
|
|
.class()
|
|
.unwrap_or_else(|| String::from("could not get window class")),
|
|
)?;
|
|
state.serialize_field(
|
|
"rect",
|
|
&WindowsApi::window_rect(self.hwnd).unwrap_or_default(),
|
|
)?;
|
|
state.end()
|
|
}
|
|
}
|
|
|
|
struct TransparencyRenderDispatcher {
|
|
hwnd: isize,
|
|
start_opacity: u8,
|
|
target_opacity: u8,
|
|
style: AnimationStyle,
|
|
is_opaque: bool,
|
|
}
|
|
|
|
impl TransparencyRenderDispatcher {
|
|
const PREFIX: AnimationPrefix = AnimationPrefix::Transparency;
|
|
|
|
pub fn new(
|
|
hwnd: isize,
|
|
is_opaque: bool,
|
|
start_opacity: u8,
|
|
target_opacity: u8,
|
|
style: AnimationStyle,
|
|
) -> Self {
|
|
Self {
|
|
hwnd,
|
|
start_opacity,
|
|
target_opacity,
|
|
style,
|
|
is_opaque,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RenderDispatcher for TransparencyRenderDispatcher {
|
|
fn get_animation_key(&self) -> String {
|
|
new_animation_key(TransparencyRenderDispatcher::PREFIX, self.hwnd.to_string())
|
|
}
|
|
|
|
fn pre_render(&self) -> eyre::Result<()> {
|
|
//transparent
|
|
if !self.is_opaque {
|
|
let window = Window::from(self.hwnd);
|
|
let mut ex_style = window.ex_style()?;
|
|
ex_style.insert(ExtendedWindowStyle::LAYERED);
|
|
window.update_ex_style(&ex_style)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn render(&self, progress: f64) -> eyre::Result<()> {
|
|
WindowsApi::set_transparent(
|
|
self.hwnd,
|
|
self.start_opacity
|
|
.lerp(self.target_opacity, progress, self.style),
|
|
)
|
|
}
|
|
|
|
fn post_render(&self) -> eyre::Result<()> {
|
|
//opaque
|
|
if self.is_opaque {
|
|
let window = Window::from(self.hwnd);
|
|
let mut ex_style = window.ex_style()?;
|
|
ex_style.remove(ExtendedWindowStyle::LAYERED);
|
|
window.update_ex_style(&ex_style)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Window {
|
|
pub const fn hwnd(self) -> HWND {
|
|
HWND(windows_api::as_ptr!(self.hwnd))
|
|
}
|
|
|
|
pub fn move_to_area(&mut self, current_area: &Rect, target_area: &Rect) -> eyre::Result<()> {
|
|
let current_rect = WindowsApi::window_rect(self.hwnd)?;
|
|
let x_diff = target_area.left - current_area.left;
|
|
let y_diff = target_area.top - current_area.top;
|
|
let x_ratio = f32::abs((target_area.right as f32) / (current_area.right as f32));
|
|
let y_ratio = f32::abs((target_area.bottom as f32) / (current_area.bottom as f32));
|
|
let window_relative_x = current_rect.left - current_area.left;
|
|
let window_relative_y = current_rect.top - current_area.top;
|
|
let corrected_relative_x = (window_relative_x as f32 * x_ratio) as i32;
|
|
let corrected_relative_y = (window_relative_y as f32 * y_ratio) as i32;
|
|
let window_x = current_area.left + corrected_relative_x;
|
|
let window_y = current_area.top + corrected_relative_y;
|
|
let left = x_diff + window_x;
|
|
let top = y_diff + window_y;
|
|
|
|
let corrected_width = (current_rect.right as f32 * x_ratio) as i32;
|
|
let corrected_height = (current_rect.bottom as f32 * y_ratio) as i32;
|
|
|
|
let new_rect = Rect {
|
|
left,
|
|
top,
|
|
right: corrected_width,
|
|
bottom: corrected_height,
|
|
};
|
|
|
|
let is_maximized = &new_rect == target_area;
|
|
if is_maximized {
|
|
windows_api::WindowsApi::unmaximize_window(self.hwnd);
|
|
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
|
|
let move_enabled = animation_enabled
|
|
.get(&GhostDispatcher::PREFIX)
|
|
.is_some_and(|v| *v);
|
|
drop(animation_enabled);
|
|
|
|
if move_enabled || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst) {
|
|
let anim_count = ANIMATION_MANAGER
|
|
.lock()
|
|
.count_in_progress(GhostDispatcher::PREFIX);
|
|
self.set_position(&new_rect, true)?;
|
|
let hwnd = self.hwnd;
|
|
// Wait for the animation to finish before maximizing the window again, otherwise
|
|
// we would be maximizing the window on the current monitor anyway
|
|
thread::spawn(move || {
|
|
let mut new_anim_count = ANIMATION_MANAGER
|
|
.lock()
|
|
.count_in_progress(GhostDispatcher::PREFIX);
|
|
let mut max_wait = 2000; // Max waiting time. No one will be using an animation longer than 2s, right? RIGHT??? WHY?
|
|
while new_anim_count > anim_count && max_wait > 0 {
|
|
thread::sleep(Duration::from_millis(10));
|
|
new_anim_count = ANIMATION_MANAGER
|
|
.lock()
|
|
.count_in_progress(GhostDispatcher::PREFIX);
|
|
max_wait -= 1;
|
|
}
|
|
windows_api::WindowsApi::maximize_window(hwnd);
|
|
});
|
|
} else {
|
|
self.set_position(&new_rect, true)?;
|
|
windows_api::WindowsApi::maximize_window(self.hwnd);
|
|
}
|
|
} else {
|
|
self.set_position(&new_rect, true)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn center(&mut self, work_area: &Rect, resize: bool) -> eyre::Result<()> {
|
|
let (target_width, target_height) = if resize {
|
|
let (aspect_ratio_width, aspect_ratio_height) =
|
|
crate::settings::floating_window_aspect_ratio().width_and_height();
|
|
let target_height = work_area.bottom / 2;
|
|
let target_width = (target_height * aspect_ratio_width) / aspect_ratio_height;
|
|
(target_width, target_height)
|
|
} else {
|
|
let current_rect = WindowsApi::window_rect(self.hwnd)?;
|
|
(current_rect.right, current_rect.bottom)
|
|
};
|
|
|
|
let x = work_area.left + ((work_area.right - target_width) / 2);
|
|
let y = work_area.top + ((work_area.bottom - target_height) / 2);
|
|
|
|
self.set_position(
|
|
&Rect {
|
|
left: x,
|
|
top: y,
|
|
right: target_width,
|
|
bottom: target_height,
|
|
},
|
|
true,
|
|
)
|
|
}
|
|
|
|
pub fn set_position(&self, layout: &Rect, top: bool) -> eyre::Result<()> {
|
|
// a window of the workspace that has been switched to slides in from the
|
|
// side of the monitor, which is so even if it is where the layout puts it
|
|
// already, as it was when it was hidden
|
|
if let Some(slid) = transition::slide_in(*self, layout, top) {
|
|
return slid;
|
|
}
|
|
|
|
// and it is put where it goes by that animation, which a layout that is
|
|
// done while it is on its way is not to cancel
|
|
if transition::is_sliding_in(self.hwnd) {
|
|
return Ok(());
|
|
}
|
|
|
|
// a window that the user has hold of by an edge is where they put it, until
|
|
// they let go
|
|
if live_resize::is_dragging(self.hwnd) {
|
|
return Ok(());
|
|
}
|
|
|
|
let window_rect = WindowsApi::window_rect(self.hwnd)?;
|
|
|
|
if window_rect.eq(layout) {
|
|
if live_resize::is_stepping() {
|
|
tracing::debug!(
|
|
"live resize: hwnd {} is where it is to be, {layout:?}",
|
|
self.hwnd
|
|
);
|
|
live_resize::follow(self.hwnd, &window_rect, layout);
|
|
}
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
// the neighbours of a window being dragged follow it as it goes, which is
|
|
// not to be seen catching up
|
|
if live_resize::is_stepping() {
|
|
// what a window that is resized is seen to do is covered: the cover is where the
|
|
// window is to be, which is the pointer's, and the window is sent there when
|
|
// the drag is over
|
|
if live_resize::follow(self.hwnd, &window_rect, layout) {
|
|
return Ok(());
|
|
}
|
|
|
|
if live_resize::is_catching_up(self.hwnd, layout) {
|
|
tracing::debug!(
|
|
"live resize: hwnd {} is at {window_rect:?}, not yet where it was sent, left alone for {layout:?}",
|
|
self.hwnd
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
tracing::debug!(
|
|
"live resize: hwnd {} sent to {layout:?} from {window_rect:?} ({})",
|
|
self.hwnd,
|
|
if live_resize::is_pacing() {
|
|
"step"
|
|
} else {
|
|
"end"
|
|
}
|
|
);
|
|
live_resize::sent(self.hwnd, layout);
|
|
return WindowsApi::position_window(self.hwnd, layout, top, true);
|
|
}
|
|
|
|
// a command that resizes windows puts them where they go at once: one that is on its
|
|
// way somewhere is stopped where it is, first
|
|
if live_resize::is_in_place() {
|
|
let key = new_animation_key(GhostDispatcher::PREFIX, self.hwnd.to_string());
|
|
if ANIMATION_MANAGER.lock().in_progress(&key) {
|
|
AnimationEngine::cancel(&key);
|
|
}
|
|
|
|
return WindowsApi::position_window(self.hwnd, layout, top, true);
|
|
}
|
|
|
|
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
|
|
let move_enabled = animation_enabled.get(&GhostDispatcher::PREFIX);
|
|
|
|
if move_enabled.is_some_and(|enabled| *enabled)
|
|
|| ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)
|
|
{
|
|
let duration = Duration::from_millis(
|
|
*ANIMATION_DURATION_PER_ANIMATION
|
|
.lock()
|
|
.get(&GhostDispatcher::PREFIX)
|
|
.unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),
|
|
);
|
|
let style = *ANIMATION_STYLE_PER_ANIMATION
|
|
.lock()
|
|
.get(&GhostDispatcher::PREFIX)
|
|
.unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());
|
|
|
|
let render_dispatcher =
|
|
GhostDispatcher::new(self.hwnd, window_rect, *layout, top, style);
|
|
|
|
AnimationEngine::animate(render_dispatcher, duration)
|
|
} else {
|
|
WindowsApi::position_window(self.hwnd, layout, top, true)
|
|
}
|
|
}
|
|
|
|
pub fn is_maximized(self) -> bool {
|
|
WindowsApi::is_zoomed(self.hwnd)
|
|
}
|
|
|
|
pub fn is_miminized(self) -> bool {
|
|
WindowsApi::is_iconic(self.hwnd)
|
|
}
|
|
|
|
pub fn is_visible(self) -> bool {
|
|
WindowsApi::is_window_visible(self.hwnd)
|
|
}
|
|
|
|
pub fn hide_with_border(self, hide_border: bool) {
|
|
// a workspace that is sliding out hides its windows itself, when their
|
|
// ghosts are up
|
|
if transition::is_exiting(self.hwnd) {
|
|
return;
|
|
}
|
|
|
|
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
|
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
|
|
programmatically_hidden_hwnds.push(self.hwnd);
|
|
}
|
|
|
|
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
|
|
|
|
#[allow(deprecated)]
|
|
match *hiding_behaviour {
|
|
HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd),
|
|
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd),
|
|
HidingBehaviour::Cloak => {
|
|
SetCloak(self.hwnd(), 1, 2);
|
|
cloaked::record(&programmatically_hidden_hwnds);
|
|
}
|
|
}
|
|
if hide_border {
|
|
border_manager::hide_border(self.hwnd);
|
|
}
|
|
}
|
|
|
|
pub fn hide(self) {
|
|
self.hide_with_border(true);
|
|
}
|
|
|
|
pub fn restore_with_border(self, restore_border: bool) {
|
|
// a workspace that is sliding in shows its windows itself, when they have
|
|
// got there
|
|
if transition::is_entering(self.hwnd) {
|
|
return;
|
|
}
|
|
|
|
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
|
if let Some(idx) = programmatically_hidden_hwnds
|
|
.iter()
|
|
.position(|&hwnd| hwnd == self.hwnd)
|
|
{
|
|
programmatically_hidden_hwnds.remove(idx);
|
|
}
|
|
|
|
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
|
|
|
|
#[allow(deprecated)]
|
|
match *hiding_behaviour {
|
|
HidingBehaviour::Hide | HidingBehaviour::Minimize => {
|
|
WindowsApi::restore_window(self.hwnd);
|
|
}
|
|
HidingBehaviour::Cloak => {
|
|
SetCloak(self.hwnd(), 1, 0);
|
|
cloaked::record(&programmatically_hidden_hwnds);
|
|
}
|
|
}
|
|
if restore_border {
|
|
border_manager::show_border(self.hwnd);
|
|
}
|
|
}
|
|
|
|
pub fn restore(self) {
|
|
self.restore_with_border(true);
|
|
}
|
|
|
|
pub fn minimize(self) {
|
|
let exe = self.exe().unwrap_or_default();
|
|
if !exe.contains("komorebi-bar") {
|
|
WindowsApi::minimize_window(self.hwnd);
|
|
}
|
|
}
|
|
|
|
pub fn close(self) -> eyre::Result<()> {
|
|
WindowsApi::close_window(self.hwnd)
|
|
}
|
|
|
|
pub fn maximize_native(self) {
|
|
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
|
if let Some(idx) = programmatically_hidden_hwnds
|
|
.iter()
|
|
.position(|&hwnd| hwnd == self.hwnd)
|
|
{
|
|
programmatically_hidden_hwnds.remove(idx);
|
|
}
|
|
|
|
WindowsApi::maximize_window(self.hwnd);
|
|
}
|
|
|
|
pub fn unmaximize_native(self) {
|
|
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
|
if let Some(idx) = programmatically_hidden_hwnds
|
|
.iter()
|
|
.position(|&hwnd| hwnd == self.hwnd)
|
|
{
|
|
programmatically_hidden_hwnds.remove(idx);
|
|
}
|
|
|
|
WindowsApi::unmaximize_window(self.hwnd);
|
|
}
|
|
|
|
// The operations the shared code calls on a window go through the backend, as on KDE
|
|
// (`Window::focus`, `maximize` and `unmaximize` there call `backend()` too). The Windows
|
|
// primitives they end in are `focus_native`, `maximize_native` and `unmaximize_native`, which
|
|
// `WindowsBackend` calls; they stay public for the animation code that is inside the focus.
|
|
|
|
/// Gives the window the focus (and the pointer, if `mouse_follows_focus`).
|
|
pub fn focus(self, mouse_follows_focus: bool) -> eyre::Result<()> {
|
|
Ok(backend().focus(WindowId::from_hwnd(self.hwnd), mouse_follows_focus)?)
|
|
}
|
|
|
|
/// Maximizes the window; a window that is gone is left alone.
|
|
pub fn maximize(self) {
|
|
let _ = backend().maximize(WindowId::from_hwnd(self.hwnd), true);
|
|
}
|
|
|
|
/// Undoes `maximize`; a window that is gone is left alone.
|
|
pub fn unmaximize(self) {
|
|
let _ = backend().maximize(WindowId::from_hwnd(self.hwnd), false);
|
|
}
|
|
|
|
pub fn focus_native(self, mouse_follows_focus: bool) -> eyre::Result<()> {
|
|
// Focusing a window that is hidden shows it, which is not for it to be
|
|
// when it is yet to slide in: it is focused when its animation shows it
|
|
if transition::is_entering(self.hwnd) {
|
|
transition::focus_when_shown(self.hwnd, mouse_follows_focus);
|
|
return Ok(());
|
|
}
|
|
|
|
// If the target window is already focused, do nothing.
|
|
if let Ok(ihwnd) = WindowsApi::foreground_window()
|
|
&& ihwnd == self.hwnd
|
|
{
|
|
// Center cursor in Window
|
|
if mouse_follows_focus {
|
|
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd)?)?;
|
|
}
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
WindowsApi::raise_and_focus_window(self.hwnd)?;
|
|
|
|
// Center cursor in Window
|
|
if mouse_follows_focus {
|
|
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd)?)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn is_focused(self) -> bool {
|
|
WindowsApi::foreground_window().unwrap_or_default() == self.hwnd
|
|
}
|
|
|
|
pub fn transparent(self) -> eyre::Result<()> {
|
|
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
|
|
let transparent_enabled = animation_enabled.get(&TransparencyRenderDispatcher::PREFIX);
|
|
|
|
if transparent_enabled.is_some_and(|enabled| *enabled)
|
|
|| ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)
|
|
{
|
|
let duration = Duration::from_millis(
|
|
*ANIMATION_DURATION_PER_ANIMATION
|
|
.lock()
|
|
.get(&TransparencyRenderDispatcher::PREFIX)
|
|
.unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),
|
|
);
|
|
let style = *ANIMATION_STYLE_PER_ANIMATION
|
|
.lock()
|
|
.get(&TransparencyRenderDispatcher::PREFIX)
|
|
.unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());
|
|
|
|
let render_dispatcher = TransparencyRenderDispatcher::new(
|
|
self.hwnd,
|
|
false,
|
|
WindowsApi::get_transparent(self.hwnd).unwrap_or(255),
|
|
transparency_manager::TRANSPARENCY_ALPHA.load_consume(),
|
|
style,
|
|
);
|
|
|
|
AnimationEngine::animate(render_dispatcher, duration)
|
|
} else {
|
|
let mut ex_style = self.ex_style()?;
|
|
ex_style.insert(ExtendedWindowStyle::LAYERED);
|
|
self.update_ex_style(&ex_style)?;
|
|
WindowsApi::set_transparent(
|
|
self.hwnd,
|
|
transparency_manager::TRANSPARENCY_ALPHA.load_consume(),
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn opaque(self) -> eyre::Result<()> {
|
|
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
|
|
let transparent_enabled = animation_enabled.get(&TransparencyRenderDispatcher::PREFIX);
|
|
|
|
if transparent_enabled.is_some_and(|enabled| *enabled)
|
|
|| ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)
|
|
{
|
|
let duration = Duration::from_millis(
|
|
*ANIMATION_DURATION_PER_ANIMATION
|
|
.lock()
|
|
.get(&TransparencyRenderDispatcher::PREFIX)
|
|
.unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),
|
|
);
|
|
let style = *ANIMATION_STYLE_PER_ANIMATION
|
|
.lock()
|
|
.get(&TransparencyRenderDispatcher::PREFIX)
|
|
.unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());
|
|
|
|
let render_dispatcher = TransparencyRenderDispatcher::new(
|
|
self.hwnd,
|
|
true,
|
|
WindowsApi::get_transparent(self.hwnd)
|
|
.unwrap_or(transparency_manager::TRANSPARENCY_ALPHA.load_consume()),
|
|
255,
|
|
style,
|
|
);
|
|
|
|
AnimationEngine::animate(render_dispatcher, duration)
|
|
} else {
|
|
let mut ex_style = self.ex_style()?;
|
|
ex_style.remove(ExtendedWindowStyle::LAYERED);
|
|
self.update_ex_style(&ex_style)
|
|
}
|
|
}
|
|
|
|
pub fn set_accent(self, colour: u32) -> eyre::Result<()> {
|
|
WindowsApi::set_window_accent(self.hwnd, Some(colour))
|
|
}
|
|
|
|
pub fn remove_accent(self) -> eyre::Result<()> {
|
|
WindowsApi::set_window_accent(self.hwnd, None)
|
|
}
|
|
|
|
#[cfg(target_pointer_width = "64")]
|
|
pub fn update_style(self, style: &WindowStyle) -> eyre::Result<()> {
|
|
WindowsApi::update_style(self.hwnd, isize::try_from(style.bits())?)
|
|
}
|
|
|
|
#[cfg(target_pointer_width = "32")]
|
|
pub fn update_style(self, style: &WindowStyle) -> eyre::Result<()> {
|
|
WindowsApi::update_style(self.hwnd, i32::try_from(style.bits())?)
|
|
}
|
|
|
|
#[cfg(target_pointer_width = "64")]
|
|
pub fn update_ex_style(self, style: &ExtendedWindowStyle) -> eyre::Result<()> {
|
|
WindowsApi::update_ex_style(self.hwnd, isize::try_from(style.bits())?)
|
|
}
|
|
|
|
#[cfg(target_pointer_width = "32")]
|
|
pub fn update_ex_style(self, style: &ExtendedWindowStyle) -> eyre::Result<()> {
|
|
WindowsApi::update_ex_style(self.hwnd, i32::try_from(style.bits())?)
|
|
}
|
|
|
|
pub fn style(self) -> eyre::Result<WindowStyle> {
|
|
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd)?)?;
|
|
Ok(WindowStyle::from_bits_truncate(bits))
|
|
}
|
|
|
|
pub fn ex_style(self) -> eyre::Result<ExtendedWindowStyle> {
|
|
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd)?)?;
|
|
Ok(ExtendedWindowStyle::from_bits_truncate(bits))
|
|
}
|
|
|
|
pub fn title(self) -> Option<String> {
|
|
WindowsApi::window_text_w(self.hwnd).ok()
|
|
}
|
|
|
|
pub fn path(self) -> Option<String> {
|
|
let lookup = || -> eyre::Result<String> {
|
|
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd);
|
|
let handle = WindowsApi::process_handle(process_id)?;
|
|
let path = WindowsApi::exe_path(handle);
|
|
WindowsApi::close_process(handle)?;
|
|
path
|
|
};
|
|
|
|
lookup().ok()
|
|
}
|
|
|
|
pub fn exe(self) -> Option<String> {
|
|
let lookup = || -> eyre::Result<String> {
|
|
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd);
|
|
let handle = WindowsApi::process_handle(process_id)?;
|
|
let exe = WindowsApi::exe(handle);
|
|
WindowsApi::close_process(handle)?;
|
|
exe
|
|
};
|
|
|
|
lookup().ok()
|
|
}
|
|
|
|
pub fn process_id(self) -> u32 {
|
|
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd);
|
|
process_id
|
|
}
|
|
|
|
pub fn class(self) -> Option<String> {
|
|
WindowsApi::real_window_class_w(self.hwnd).ok()
|
|
}
|
|
|
|
pub fn is_cloaked(self) -> eyre::Result<bool> {
|
|
WindowsApi::is_window_cloaked(self.hwnd)
|
|
}
|
|
|
|
pub fn is_window(self) -> bool {
|
|
WindowsApi::is_window(self.hwnd)
|
|
}
|
|
|
|
// The operation names of the shared `komorebi_backend::Backend` (docs/dev/seam.md, section 2).
|
|
// `set_rect`, `alive`, `window_rect` and `unminimize` only delegate; `focus`, `raise`, `hide`,
|
|
// `restore`, `minimize` and `close` above already carry the shared name.
|
|
|
|
/// Set frame: `set_position` without raising the window to the front.
|
|
pub fn set_rect(self, rect: &Rect) -> eyre::Result<()> {
|
|
self.set_position(rect, false)
|
|
}
|
|
|
|
/// Alive check: Windows still has the window (`IsWindow`).
|
|
pub fn alive(self) -> bool {
|
|
self.is_window()
|
|
}
|
|
|
|
/// Frame query: the visible bounds (without the shadow), see `WindowsApi::window_rect`.
|
|
pub fn window_rect(self) -> eyre::Result<Rect> {
|
|
WindowsApi::window_rect(self.hwnd)
|
|
}
|
|
|
|
/// Windows has no unminimize of its own: `SW_SHOWNOACTIVATE`, as `restore` ends in for the
|
|
/// `Hide` and `Minimize` hiding behaviours, without the `HIDDEN_HWNDS` and border bookkeeping.
|
|
pub fn unminimize(self) {
|
|
WindowsApi::restore_window(self.hwnd);
|
|
}
|
|
|
|
pub fn remove_title_bar(self) -> eyre::Result<()> {
|
|
let mut style = self.style()?;
|
|
style.remove(WindowStyle::CAPTION);
|
|
style.remove(WindowStyle::THICKFRAME);
|
|
self.update_style(&style)
|
|
}
|
|
|
|
pub fn add_title_bar(self) -> eyre::Result<()> {
|
|
let mut style = self.style()?;
|
|
style.insert(WindowStyle::CAPTION);
|
|
style.insert(WindowStyle::THICKFRAME);
|
|
self.update_style(&style)
|
|
}
|
|
|
|
/// Raise the window to the top of the Z order, but do not activate or focus
|
|
/// it. Use raise_and_focus_window to activate and focus a window.
|
|
/// It also checks if there is a border attached to this window and if it is
|
|
/// it raises it as well.
|
|
pub fn raise(self) -> eyre::Result<()> {
|
|
WindowsApi::raise_window(self.hwnd)?;
|
|
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
|
|
WindowsApi::raise_window(border_info.border_hwnd)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Lower the window to the bottom of the Z order, but do not activate or focus
|
|
/// it.
|
|
/// It also checks if there is a border attached to this window and if it is
|
|
/// it lowers it as well.
|
|
pub fn lower(self) -> eyre::Result<()> {
|
|
WindowsApi::lower_window(self.hwnd)?;
|
|
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
|
|
WindowsApi::lower_window(border_info.border_hwnd)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(fields(exe, title), skip(debug))]
|
|
pub fn should_manage(
|
|
self,
|
|
event: Option<WindowManagerEvent>,
|
|
debug: &mut RuleDebug,
|
|
) -> eyre::Result<bool> {
|
|
if !self.is_window() {
|
|
return Ok(false);
|
|
}
|
|
|
|
debug.is_window = true;
|
|
|
|
let rect = WindowsApi::window_rect(self.hwnd).unwrap_or_default();
|
|
|
|
if rect.right < MINIMUM_WIDTH.load(Ordering::SeqCst) {
|
|
return Ok(false);
|
|
}
|
|
|
|
debug.has_minimum_width = true;
|
|
|
|
if rect.bottom < MINIMUM_HEIGHT.load(Ordering::SeqCst) {
|
|
return Ok(false);
|
|
}
|
|
|
|
debug.has_minimum_height = true;
|
|
|
|
if self.title().is_none() {
|
|
return Ok(false);
|
|
}
|
|
|
|
debug.has_title = true;
|
|
|
|
let is_cloaked = self.is_cloaked().unwrap_or_default();
|
|
|
|
debug.is_cloaked = is_cloaked;
|
|
|
|
let mut allow_cloaked = false;
|
|
|
|
if let Some(event) = event
|
|
&& matches!(
|
|
event,
|
|
WindowManagerEvent::Hide(_, _) | WindowManagerEvent::Cloak(_, _)
|
|
)
|
|
{
|
|
allow_cloaked = true;
|
|
}
|
|
|
|
debug.allow_cloaked = allow_cloaked;
|
|
|
|
match (allow_cloaked, is_cloaked) {
|
|
// If allowing cloaked windows, we don't need to check the cloaked status
|
|
(true, _) |
|
|
// If not allowing cloaked windows, we need to ensure the window is not cloaked
|
|
(false, false) => {
|
|
if let (Some(title), Some(exe_name), Some(class), Some(path)) = (self.title(), self.exe(), self.class(), self.path()) {
|
|
debug.title = Some(title.clone());
|
|
debug.exe_name = Some(exe_name.clone());
|
|
debug.class = Some(class.clone());
|
|
debug.path = Some(path.clone());
|
|
// calls for styles can fail quite often for events with windows that aren't really "windows"
|
|
// since we have moved up calls of should_manage to the beginning of the process_event handler,
|
|
// we should handle failures here gracefully to be able to continue the execution of process_event
|
|
if let (Ok(style), Ok(ex_style)) = (&self.style(), &self.ex_style()) {
|
|
debug.window_style = Some(*style);
|
|
debug.extended_window_style = Some(*ex_style);
|
|
let eligible = window_is_eligible(self.hwnd, &title, &exe_name, &class, &path, style, ex_style, event, debug);
|
|
debug.should_manage = eligible;
|
|
return Ok(eligible);
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
pub struct RuleDebug {
|
|
pub should_manage: bool,
|
|
pub is_window: bool,
|
|
pub has_minimum_width: bool,
|
|
pub has_minimum_height: bool,
|
|
pub has_title: bool,
|
|
pub is_cloaked: bool,
|
|
pub allow_cloaked: bool,
|
|
pub allow_layered_transparency: bool,
|
|
pub window_style: Option<WindowStyle>,
|
|
pub extended_window_style: Option<ExtendedWindowStyle>,
|
|
pub title: Option<String>,
|
|
pub exe_name: Option<String>,
|
|
pub class: Option<String>,
|
|
pub path: Option<String>,
|
|
pub matches_permaignore_class: Option<String>,
|
|
pub matches_ignore_identifier: Option<MatchingRule>,
|
|
pub matches_managed_override: Option<MatchingRule>,
|
|
pub matches_layered_whitelist: Option<MatchingRule>,
|
|
pub matches_floating_applications: Option<MatchingRule>,
|
|
pub matches_wsl2_gui: Option<String>,
|
|
pub matches_no_titlebar: Option<MatchingRule>,
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn window_is_eligible(
|
|
hwnd: isize,
|
|
title: &String,
|
|
exe_name: &String,
|
|
class: &String,
|
|
path: &str,
|
|
style: &WindowStyle,
|
|
ex_style: &ExtendedWindowStyle,
|
|
event: Option<WindowManagerEvent>,
|
|
debug: &mut RuleDebug,
|
|
) -> bool {
|
|
{
|
|
let permaignore_classes = PERMAIGNORE_CLASSES.lock();
|
|
if permaignore_classes.contains(class) {
|
|
debug.matches_permaignore_class = Some(class.clone());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Every place that holds two of the rule lists takes them in the order documented at the
|
|
// statics in lib.rs, so all the lists this check reads are taken up front, in that order.
|
|
let floating_identifiers = FLOATING_APPLICATIONS.lock();
|
|
let ignore_identifiers = IGNORE_IDENTIFIERS.lock();
|
|
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
|
let layered_whitelist = LAYERED_WHITELIST.lock();
|
|
let slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();
|
|
let titlebars_removed = NO_TITLEBAR.lock();
|
|
let regex_identifiers = REGEX_IDENTIFIERS.lock();
|
|
|
|
let should_ignore = if let Some(rule) = should_act(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
&ignore_identifiers,
|
|
®ex_identifiers,
|
|
) {
|
|
debug.matches_ignore_identifier = Some(rule);
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
let managed_override = if let Some(rule) = should_act(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
&manage_identifiers,
|
|
®ex_identifiers,
|
|
) {
|
|
debug.matches_managed_override = Some(rule);
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
if let Some(rule) = should_act(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
&floating_identifiers,
|
|
®ex_identifiers,
|
|
) {
|
|
debug.matches_floating_applications = Some(rule);
|
|
}
|
|
|
|
if should_ignore && !managed_override {
|
|
return false;
|
|
}
|
|
|
|
let mut allow_layered = if let Some(rule) = should_act(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
&layered_whitelist,
|
|
®ex_identifiers,
|
|
) {
|
|
debug.matches_layered_whitelist = Some(rule);
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
let known_layered_hwnds = transparency_manager::known_hwnds();
|
|
|
|
allow_layered = if known_layered_hwnds.contains(&hwnd)
|
|
// we always want to process hide events for windows with transparency, even on other
|
|
// monitors, because we don't want to be left with ghost tiles
|
|
|| matches!(event, Some(WindowManagerEvent::Hide(_, _)))
|
|
{
|
|
debug.allow_layered_transparency = true;
|
|
true
|
|
} else {
|
|
allow_layered
|
|
};
|
|
|
|
let allow_wsl2_gui = {
|
|
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
|
|
let allow = wsl2_ui_processes.contains(exe_name);
|
|
if allow {
|
|
debug.matches_wsl2_gui = Some(exe_name.clone())
|
|
}
|
|
|
|
allow
|
|
};
|
|
|
|
let allow_titlebar_removed = if let Some(rule) = should_act(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
&titlebars_removed,
|
|
®ex_identifiers,
|
|
) {
|
|
debug.matches_no_titlebar = Some(rule);
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
let should_sleep = should_act(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
&slow_application_identifiers,
|
|
®ex_identifiers,
|
|
)
|
|
.is_some();
|
|
|
|
// Nothing below reads a rule list: release them all before sleeping, so a config load or a
|
|
// rule command is never kept waiting for the slow-application compensation time.
|
|
drop(floating_identifiers);
|
|
drop(ignore_identifiers);
|
|
drop(manage_identifiers);
|
|
drop(layered_whitelist);
|
|
drop(slow_application_identifiers);
|
|
drop(titlebars_removed);
|
|
drop(regex_identifiers);
|
|
|
|
if should_sleep {
|
|
std::thread::sleep(Duration::from_millis(
|
|
SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),
|
|
));
|
|
}
|
|
|
|
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
|
|
&& !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)
|
|
// Get a lot of dupe events coming through that make the redrawing go crazy
|
|
// on FocusChange events if I don't filter out this one. But, if we are
|
|
// allowing a specific layered window on the whitelist (like Steam), it should
|
|
// pass this check
|
|
&& (allow_layered || !ex_style.contains(ExtendedWindowStyle::LAYERED))
|
|
|| managed_override
|
|
{
|
|
return true;
|
|
} else if let Some(event) = event {
|
|
tracing::debug!(
|
|
"ignoring (exe: {}, title: {}, event: {})",
|
|
exe_name,
|
|
title,
|
|
event
|
|
);
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
|
|
/// [`should_act`] for a window, which has one class here
|
|
pub fn should_act_window(
|
|
title: &str,
|
|
exe_name: &str,
|
|
class: &str,
|
|
path: &str,
|
|
identifiers: &[MatchingRule],
|
|
regex_identifiers: &HashMap<String, Regex>,
|
|
) -> Option<MatchingRule> {
|
|
should_act(title, exe_name, class, path, identifiers, regex_identifiers)
|
|
}
|
|
|
|
/// [`should_act_individual`] for a window, which has one class here
|
|
pub fn should_act_window_individual(
|
|
title: &str,
|
|
exe_name: &str,
|
|
class: &str,
|
|
path: &str,
|
|
identifier: &IdWithIdentifier,
|
|
regex_identifiers: &HashMap<String, Regex>,
|
|
) -> bool {
|
|
should_act_individual(title, exe_name, class, path, identifier, regex_identifiers)
|
|
}
|
|
|
|
pub fn should_act(
|
|
title: &str,
|
|
exe_name: &str,
|
|
class: &str,
|
|
path: &str,
|
|
identifiers: &[MatchingRule],
|
|
regex_identifiers: &HashMap<String, Regex>,
|
|
) -> Option<MatchingRule> {
|
|
let mut matching_rule = None;
|
|
for rule in identifiers {
|
|
match rule {
|
|
MatchingRule::Simple(identifier) => {
|
|
if should_act_individual(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
identifier,
|
|
regex_identifiers,
|
|
) {
|
|
matching_rule = Some(rule.clone());
|
|
};
|
|
}
|
|
MatchingRule::Composite(identifiers) => {
|
|
let mut composite_results = vec![];
|
|
for identifier in identifiers {
|
|
composite_results.push(should_act_individual(
|
|
title,
|
|
exe_name,
|
|
class,
|
|
path,
|
|
identifier,
|
|
regex_identifiers,
|
|
));
|
|
}
|
|
|
|
if composite_results.iter().all(|&x| x) {
|
|
matching_rule = Some(rule.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
matching_rule
|
|
}
|
|
|
|
pub fn should_act_individual(
|
|
title: &str,
|
|
exe_name: &str,
|
|
class: &str,
|
|
path: &str,
|
|
identifier: &IdWithIdentifier,
|
|
regex_identifiers: &HashMap<String, Regex>,
|
|
) -> bool {
|
|
let mut should_act = false;
|
|
|
|
match identifier.matching_strategy {
|
|
None | Some(MatchingStrategy::Legacy) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if exe_name.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if path.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::Equals) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if title.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if class.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if exe_name.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if path.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::DoesNotEqual) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if !title.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if !class.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if !exe_name.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if !path.eq(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::StartsWith) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if title.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if class.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if exe_name.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if path.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::DoesNotStartWith) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if !title.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if !class.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if !exe_name.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if !path.starts_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::EndsWith) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if title.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if class.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if exe_name.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if path.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::DoesNotEndWith) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if !title.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if !class.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if !exe_name.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if !path.ends_with(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::Contains) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if title.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if class.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if exe_name.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if path.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::DoesNotContain) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if !title.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if !class.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if !exe_name.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if !path.contains(&identifier.id) {
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
Some(MatchingStrategy::Regex) => match identifier.kind {
|
|
ApplicationIdentifier::Title => {
|
|
if let Some(re) = regex_identifiers.get(&identifier.id)
|
|
&& re.is_match(title)
|
|
{
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Class => {
|
|
if let Some(re) = regex_identifiers.get(&identifier.id)
|
|
&& re.is_match(class)
|
|
{
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Exe => {
|
|
if let Some(re) = regex_identifiers.get(&identifier.id)
|
|
&& re.is_match(exe_name)
|
|
{
|
|
should_act = true;
|
|
}
|
|
}
|
|
ApplicationIdentifier::Path => {
|
|
if let Some(re) = regex_identifiers.get(&identifier.id)
|
|
&& re.is_match(path)
|
|
{
|
|
should_act = true;
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
should_act
|
|
}
|