use bevy::ecs::entity::{Entity, EntityHashSet};
use bevy::ecs::hierarchy::{ChildOf, Children};
use bevy::ecs::lifecycle::{Add, Remove};
use bevy::ecs::observer::On;
use bevy::ecs::query::{Has, With, Without};
use bevy::ecs::system::{Commands, NonSendMut, Query, Res, ResMut, Single};
use log::{debug, error, info, trace, warn};
use notify::event::{DataChange, MetadataKind, ModifyKind};
use notify::{EventKind, Watcher};
use std::mem::take;
use std::time::Duration;
use stdext::function_name;
use super::{
ActiveDisplayMarker, BProcess, FocusedMarker, FreshMarker, MissionControlActive, OrphanedPane,
SpawnWindowTrigger, StrayFocusEvent, Timeout, Unmanaged, WMEventTrigger, WindowDraggedMarker,
};
use crate::config::{Config, WindowParams};
use crate::ecs::params::{ActiveDisplay, ActiveDisplayMut, Configuration};
use crate::ecs::{WindowSwipeMarker, reshuffle_around};
use crate::events::Event;
use crate::manager::{
Application, Display, Process, Window, WindowManager, WindowPadding, WindowPane,
};
use crate::util::symlink_target;
#[allow(clippy::needless_pass_by_value)]
pub(super) fn mouse_moved_trigger(
trigger: On<WMEventTrigger>,
windows: Query<&Window>,
focused_window: Query<&Window, With<FocusedMarker>>,
window_manager: Res<WindowManager>,
mut config: Configuration,
) {
let Event::MouseMoved { point } = trigger.event().0 else {
return;
};
if !config.focus_follows_mouse() {
return;
}
if config.mission_control_active() {
return;
}
if config.ffm_flag().is_some() {
trace!("{}: ffm_window_id > 0", function_name!());
return;
}
let Ok(window_id) = window_manager.find_window_at_point(&point) else {
debug!(
"{}: can not find window at point {point:?}",
function_name!()
);
return;
};
if focused_window
.single()
.is_ok_and(|window| window.id() == window_id)
{
trace!("{}: allready focused {}", function_name!(), window_id);
return;
}
let Some(window) = windows.iter().find(|window| window.id() == window_id) else {
trace!(
"{}: can not find focused window: {}",
function_name!(),
window_id
);
return;
};
if !window.is_eligible() {
trace!("{}: {} not eligible", function_name!(), window_id);
return;
}
let child_windows = window_manager
.0
.get_associated_windows(window_id)
.iter()
.filter_map(|child_wid| {
let child_window = windows.iter().find(|window| window.id() == *child_wid);
if child_window.is_none() {
warn!(
"{}: Unable to find child window {child_wid}.",
function_name!()
);
}
child_window
})
.collect::<Vec<_>>();
let valid_role = child_windows.into_iter().find(|window| {
window
.valid_role()
.inspect_err(|err| {
warn!(
"{}: Can not get role for children of {window_id}: {err}",
function_name!(),
);
})
.is_ok()
});
let window = valid_role.unwrap_or(window);
config.set_skip_reshuffle(true);
config.set_ffm_flag(Some(window.id()));
if let Ok(focused_window) = focused_window.single() {
window.focus_without_raise(focused_window);
} else {
window.focus_with_raise();
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn mouse_down_trigger(
trigger: On<WMEventTrigger>,
windows: Query<(&Window, Entity)>,
active_display: ActiveDisplay,
window_manager: Res<WindowManager>,
mission_control_active: Res<MissionControlActive>,
mut commands: Commands,
) {
let Event::MouseDown { point } = trigger.event().0 else {
return;
};
if mission_control_active.0 {
return;
}
trace!("{}: {point:?}", function_name!());
let Some((window, entity)) = window_manager
.0
.find_window_at_point(&point)
.ok()
.and_then(|window_id| windows.iter().find(|(window, _)| window.id() == window_id))
else {
return;
};
if !window.fully_visible(&active_display.bounds()) {
reshuffle_around(entity, &mut commands);
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn mouse_dragged_trigger(
trigger: On<WMEventTrigger>,
active_display: ActiveDisplay,
windows: Query<(Entity, &Window)>,
mut drag_marker: Query<(&mut Timeout, &mut WindowDraggedMarker)>,
window_manager: Res<WindowManager>,
mission_control_active: Res<MissionControlActive>,
mut commands: Commands,
) {
const DRAG_MARKER_TIMEOUT_MS: u64 = 3000;
let Event::MouseDragged { point } = trigger.event().0 else {
return;
};
if mission_control_active.0 {
return;
}
let Some((entity, window)) = window_manager
.0
.find_window_at_point(&point)
.ok()
.and_then(|window_id| windows.iter().find(|(_, window)| window.id() == window_id))
else {
return;
};
if let Ok((mut timeout, mut marker)) = drag_marker.single_mut() {
if entity != marker.entity {
let marker = marker.as_mut();
marker.entity = entity;
marker.display_id = active_display.id();
timeout.timer.reset();
}
} else {
debug!(
"{}: Adding a drag marker ({entity}, {}) to window id {}.",
function_name!(),
active_display.id(),
window.id(),
);
let timeout = Timeout::new(Duration::from_millis(DRAG_MARKER_TIMEOUT_MS), None);
commands.spawn((
timeout,
WindowDraggedMarker {
entity,
display_id: active_display.id(),
},
));
}
}
#[allow(clippy::needless_pass_by_value, clippy::type_complexity)]
pub(super) fn workspace_change_trigger(
trigger: On<WMEventTrigger>,
focused_window: Single<
(
&Window,
Entity,
Option<&WindowDraggedMarker>,
Has<Unmanaged>,
),
With<FocusedMarker>,
>,
mut active_display: ActiveDisplayMut,
window_manager: Res<WindowManager>,
mut commands: Commands,
) {
let Event::SpaceChanged = trigger.event().0 else {
return;
};
let Ok(workspace_id) = window_manager.active_display_space(active_display.id()) else {
return;
};
let Ok(panel) = active_display.display().active_panel(workspace_id) else {
return;
};
let (window, entity, drag_marker, unmanaged) = *focused_window;
if unmanaged || panel.index_of(entity).is_ok() {
return;
}
if drag_marker.is_some_and(|marker| marker.display_id != active_display.id()) {
debug!(
"{}: drag marker has the same display_id {}",
function_name!(),
active_display.id()
);
return;
}
let windows = window_manager.windows_in_workspace(workspace_id);
if !windows.is_ok_and(|windows| {
windows
.into_iter()
.any(|window_id| window.id() == window_id)
}) {
return;
}
debug!(
"{}: Window {} moved to workspace {workspace_id}.",
function_name!(),
window.id(),
);
active_display
.other()
.for_each(|mut display| display.remove_window(entity));
active_display.display().remove_window(entity);
if let Ok(panel) = active_display.active_panel() {
panel.append(entity);
}
reshuffle_around(entity, &mut commands);
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn display_change_trigger(
trigger: On<WMEventTrigger>,
displays: Query<(&Display, Entity, Has<ActiveDisplayMarker>)>,
window_manager: Res<WindowManager>,
mut commands: Commands,
) {
let Event::DisplayChanged = trigger.event().0 else {
return;
};
let Ok(active_id) = window_manager.active_display_id() else {
error!("{}: Unable to get active display id!", function_name!());
return;
};
for (display, entity, focused) in displays {
if focused && display.id() != active_id {
debug!(
"{}: Display id {} no longer active",
function_name!(),
display.id()
);
commands.entity(entity).try_remove::<ActiveDisplayMarker>();
}
if !focused && display.id() == active_id {
debug!(
"{}: Display id {} is active",
function_name!(),
display.id()
);
commands.entity(entity).try_insert(ActiveDisplayMarker);
}
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn active_display_trigger(
trigger: On<Add, ActiveDisplayMarker>,
mut displays: Query<&mut Display>,
drag_marker: Single<(Entity, &WindowDraggedMarker)>,
windows: Query<&Window, Without<Unmanaged>>,
window_manager: Res<WindowManager>,
mut commands: Commands,
) {
let Ok(mut active_display) = displays.get_mut(trigger.event().entity) else {
error!("{}: Can not find active display.", function_name!());
return;
};
let active_display_id = active_display.id();
let Ok(workspace_id) = window_manager.active_display_space(active_display_id) else {
return;
};
let Ok(panel) = active_display.active_panel(workspace_id) else {
return;
};
debug!(
"{}: Display ({active_display_id}) changed, panel {panel}.",
function_name!()
);
let WindowDraggedMarker { entity, display_id } = *drag_marker.1;
if display_id == active_display_id {
return;
}
let Ok(window) = windows.get(entity) else {
return;
};
if panel.index_of(entity).is_ok() {
return;
}
let windows = window_manager.windows_in_workspace(workspace_id);
if !windows.is_ok_and(|windows| {
windows
.into_iter()
.any(|window_id| window.id() == window_id)
}) {
return;
}
debug!(
"{}: Window {} dragged to display {active_display_id}.",
function_name!(),
window.id(),
);
if let Ok(panel) = active_display.active_panel_mut(workspace_id) {
panel.append(entity);
}
displays.iter_mut().for_each(|mut display| {
if display.id() != active_display_id {
display.remove_window(entity);
}
});
reshuffle_around(entity, &mut commands);
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn display_add_trigger(
trigger: On<WMEventTrigger>,
window_manager: Res<WindowManager>,
mut commands: Commands,
) {
let Event::DisplayAdded { display_id } = trigger.event().0 else {
return;
};
debug!("{}: Display Added: {display_id:?}", function_name!());
let Some(display) = window_manager
.0
.present_displays()
.into_iter()
.find(|display| display.id() == display_id)
else {
error!(
"{}: Unable to find added display id {display_id}!",
function_name!()
);
return;
};
for (id, pane) in &display.spaces {
debug!("{}: Space {id} - {pane}", function_name!());
}
commands.spawn(display);
commands.trigger(WMEventTrigger(Event::DisplayChanged));
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn display_remove_trigger(
trigger: On<WMEventTrigger>,
mut displays: Query<(&mut Display, Entity)>,
mut commands: Commands,
) {
const ORPHANED_SPACES_TIMEOUT_SEC: u64 = 5;
let Event::DisplayRemoved { display_id } = trigger.event().0 else {
return;
};
debug!("{}: Display Removed: {display_id:?}", function_name!());
let Some((mut display, entity)) = displays
.iter_mut()
.find(|(display, _)| display.id() == display_id)
else {
error!("{}: Unable to find removed display!", function_name!());
return;
};
for (id, pane) in take(&mut display.spaces)
.into_iter()
.filter(|(_, pane)| pane.len() > 0)
{
debug!("{}: adding {id} {pane} to orphaned list.", function_name!());
let timeout = Timeout::new(
Duration::from_secs(ORPHANED_SPACES_TIMEOUT_SEC),
Some(format!(
"{}: Orphaned pane {id} ({pane}) could not be re-inserted after {ORPHANED_SPACES_TIMEOUT_SEC}s.",
function_name!(),
)),
);
commands.spawn((timeout, OrphanedPane { id, pane }));
}
commands.entity(entity).despawn();
commands.trigger(WMEventTrigger(Event::DisplayChanged));
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn display_moved_trigger(
trigger: On<WMEventTrigger>,
mut displays: Query<(&mut Display, Entity)>,
window_manager: Res<WindowManager>,
mut commands: Commands,
) {
let Event::DisplayMoved { display_id } = trigger.event().0 else {
return;
};
debug!("{}: Display Moved: {display_id:?}", function_name!());
let Some((mut display, _)) = displays
.iter_mut()
.find(|(display, _)| display.id() == display_id)
else {
error!("{}: Unable to find moved display!", function_name!());
return;
};
let Some(moved_display) = window_manager
.0
.present_displays()
.into_iter()
.find(|display| display.id() == display_id)
else {
return;
};
*display = moved_display;
for (id, pane) in &display.spaces {
debug!("{}: Space {id} - {pane}", function_name!());
}
commands.trigger(WMEventTrigger(Event::DisplayChanged));
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn front_switched_trigger(
trigger: On<WMEventTrigger>,
focused_window: Query<(&Window, Entity), With<FocusedMarker>>,
processes: Query<(&BProcess, &Children)>,
applications: Query<&Application>,
mut config: Configuration,
mut commands: Commands,
) {
let Event::ApplicationFrontSwitched { ref psn } = trigger.event().0 else {
return;
};
let Some((BProcess(process), children)) =
processes.iter().find(|process| &process.0.psn() == psn)
else {
error!(
"{}: Unable to find process with PSN {psn:?}",
function_name!()
);
return;
};
if children.len() > 1 {
warn!(
"{}: Multiple apps registered to process '{}'.",
function_name!(),
process.name()
);
}
let Some(app) = children
.first()
.and_then(|entity| applications.get(*entity).ok())
else {
error!(
"{}: No application for process '{}'.",
function_name!(),
process.name()
);
return;
};
debug!("{}: {}", function_name!(), process.name());
if let Ok(focused_id) = app.focused_window_id().inspect_err(|err| {
warn!("{}: can not get current focus: {err}", function_name!());
}) {
commands.trigger(WMEventTrigger(Event::WindowFocused {
window_id: focused_id,
}));
} else if let Ok((_, entity)) = focused_window.single() {
debug!("{}: reseting focus.", function_name!());
config.set_ffm_flag(None);
commands.entity(entity).try_remove::<FocusedMarker>();
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn center_mouse_trigger(
trigger: On<Add, FocusedMarker>,
active_display: ActiveDisplay,
windows: Query<(&Window, Entity)>,
window_manager: Res<WindowManager>,
config: Configuration,
) {
let Ok((window, _)) = windows.get(trigger.event().entity) else {
return;
};
if config.mouse_follows_focus()
&& !config.skip_reshuffle()
&& config.ffm_flag().is_none_or(|id| id != window.id())
{
window_manager
.0
.center_mouse(window, &active_display.bounds());
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn window_focused_trigger(
trigger: On<WMEventTrigger>,
applications: Query<&Application>,
windows: Query<(&Window, Entity, &ChildOf, Has<FocusedMarker>)>,
mut config: Configuration,
mut commands: Commands,
) {
const STRAY_FOCUS_RETRY_SEC: u64 = 2;
let Event::WindowFocused { window_id } = trigger.event().0 else {
return;
};
let Some((window, entity, child, _)) = windows
.iter()
.find(|(window, _, _, _)| window.id() == window_id)
else {
let timeout = Timeout::new(Duration::from_secs(STRAY_FOCUS_RETRY_SEC), None);
commands.spawn((timeout, StrayFocusEvent(window_id)));
return;
};
for (window, entity, _, focused) in windows {
if focused && window.id() != window_id {
commands.entity(entity).try_remove::<FocusedMarker>();
}
if !focused && window.id() == window_id {
commands.entity(entity).try_insert(FocusedMarker);
}
}
debug!("{}: window id {}", function_name!(), window.id());
let Ok(app) = applications.get(child.parent()) else {
warn!(
"{}: Unable to get parent for window {}.",
function_name!(),
window.id()
);
return;
};
if !app.is_frontmost() {
return;
}
commands.entity(entity).try_insert(FocusedMarker);
config.set_ffm_flag(None);
if config.skip_reshuffle() {
config.set_skip_reshuffle(false);
} else {
reshuffle_around(entity, &mut commands);
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn swipe_gesture_trigger(
trigger: On<WMEventTrigger>,
focused_window: Single<(&Window, Entity), With<FocusedMarker>>,
active_display: ActiveDisplay,
config: Configuration,
mut commands: Commands,
) {
let Event::Swipe { ref deltas } = trigger.event().0 else {
return;
};
if config
.swipe_gesture_fingers()
.is_none_or(|fingers| deltas.len() != fingers)
{
return;
}
let swipe_resolution = 1.0 / active_display.bounds().size.width;
let delta = deltas.iter().sum::<f64>();
if delta.abs() < swipe_resolution {
return;
}
let (_, entity) = *focused_window;
commands.entity(entity).try_insert(WindowSwipeMarker(delta));
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn mission_control_trigger(
trigger: On<WMEventTrigger>,
mut mission_control_active: ResMut<MissionControlActive>,
) {
match trigger.event().0 {
Event::MissionControlShowAllWindows
| Event::MissionControlShowFrontWindows
| Event::MissionControlShowDesktop => {
mission_control_active.as_mut().0 = true;
}
Event::MissionControlExit => {
mission_control_active.as_mut().0 = false;
}
_ => (),
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn application_event_trigger(
trigger: On<WMEventTrigger>,
processes: Query<(&BProcess, Entity)>,
mut commands: Commands,
) {
const PROCESS_READY_TIMEOUT_SEC: u64 = 5;
let find_process = |psn| {
processes
.iter()
.find(|(BProcess(process), _)| &process.psn() == psn)
};
match &trigger.event().0 {
Event::ApplicationLaunched { psn, observer } => {
if find_process(psn).is_none() {
let process: BProcess = Process::new(psn, observer.clone()).into();
let timeout = Timeout::new(
Duration::from_secs(PROCESS_READY_TIMEOUT_SEC),
Some(format!(
"{}: Process '{}' did not become ready in {PROCESS_READY_TIMEOUT_SEC}s.",
function_name!(),
process.name()
)),
);
commands.spawn((FreshMarker, timeout, process));
}
}
Event::ApplicationTerminated { psn } => {
if let Some((_, entity)) = find_process(psn) {
commands.entity(entity).despawn();
}
}
_ => (),
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn dispatch_application_messages(
trigger: On<WMEventTrigger>,
windows: Query<(&mut Window, Entity)>,
applications: Query<(&Application, &Children)>,
mut commands: Commands,
) {
let find_window = |window_id| windows.iter().find(|window| window.0.id() == window_id);
match &trigger.event().0 {
Event::WindowMinimized { window_id } => {
if let Some((_, entity)) = find_window(*window_id) {
commands.entity(entity).try_insert(Unmanaged::Minimized);
}
}
Event::WindowDeminimized { window_id } => {
if let Some((_, entity)) = find_window(*window_id) {
commands.entity(entity).try_remove::<Unmanaged>();
}
}
Event::ApplicationHidden { pid } => {
let Some((_, children)) = applications.iter().find(|(app, _)| app.pid() == *pid) else {
warn!("{}: Unable to find with pid {pid}", function_name!());
return;
};
for entity in children {
commands.entity(*entity).try_insert(Unmanaged::Hidden);
}
}
Event::ApplicationVisible { pid } => {
let Some((_, children)) = applications.iter().find(|(app, _)| app.pid() == *pid) else {
warn!(
"{}: Unable to find application with pid {pid}",
function_name!()
);
return;
};
for entity in children {
commands.entity(*entity).try_remove::<Unmanaged>();
}
}
_ => (),
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn window_unmanaged_trigger(
trigger: On<Add, Unmanaged>,
mut windows: Query<(&Window, Option<&Unmanaged>)>,
mut active_display: ActiveDisplayMut,
mut commands: Commands,
) {
let entity = trigger.event().entity;
let Ok((_, marker)) = windows.get(trigger.event().entity) else {
return;
};
if let Some(marker) = marker {
match marker {
Unmanaged::Floating => {
debug!("{}: Entity {entity} is floating.", function_name!(),);
active_display.display().remove_window(entity);
}
Unmanaged::Minimized | Unmanaged::Hidden => {
debug!("{}: Entity {entity} is minimized.", function_name!());
let mut lens = windows.transmute_lens::<&Window>();
let Ok(active_panel) = active_display
.active_panel()
.inspect_err(|err| debug!("{}: {err}", function_name!()))
else {
return;
};
give_away_focus(entity, &lens.query(), active_panel, &mut commands);
active_display.display().remove_window(entity);
}
}
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn window_managed_trigger(
trigger: On<Remove, Unmanaged>,
mut active_display: ActiveDisplayMut,
mut commands: Commands,
) {
let entity = trigger.event().entity;
debug!("{}: Entity {entity} is managed again.", function_name!(),);
let Ok(active_panel) = active_display
.active_panel()
.inspect_err(|err| debug!("{}: {err}", function_name!()))
else {
return;
};
active_panel.append(entity);
reshuffle_around(entity, &mut commands);
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn window_destroyed_trigger(
trigger: On<WMEventTrigger>,
mut windows: Query<(&Window, Entity, &ChildOf)>,
mut apps: Query<&mut Application>,
mut displays: Query<&mut Display>,
window_manager: Res<WindowManager>,
mut commands: Commands,
) {
let Event::WindowDestroyed { window_id } = trigger.event().0 else {
return;
};
let Some((window, entity, child)) = windows
.iter()
.find(|(window, _, _)| window.id() == window_id)
else {
error!(
"{}: Trying to destroy non-existing window {window_id}.",
function_name!()
);
return;
};
let Ok(mut app) = apps.get_mut(child.parent()) else {
error!(
"{}: Window {} has no parent!",
function_name!(),
window.id()
);
return;
};
app.unobserve_window(window);
commands.entity(entity).despawn();
let mut lens = windows.transmute_lens::<&Window>();
for mut display in &mut displays {
let Ok(panel) = window_manager
.0
.active_display_space(display.id())
.and_then(|active_space| display.active_panel(active_space))
else {
continue;
};
give_away_focus(entity, &lens.query(), panel, &mut commands);
display.remove_window(entity);
}
}
fn give_away_focus(
entity: Entity,
windows: &Query<&Window>,
active_pane: &WindowPane,
commands: &mut Commands,
) {
if let Ok(index) = active_pane.index_of(entity)
&& active_pane.len() > 1
{
let neighbour = active_pane.get(index.saturating_sub(1)).ok();
if let Some((window, entity)) = neighbour
.and_then(|pane| pane.top())
.and_then(|entity| windows.get(entity).ok().zip(Some(entity)))
{
let window_id = window.id();
debug!(
"{}: window destroyed, moving focus to {window_id}",
function_name!()
);
commands.trigger(WMEventTrigger(Event::WindowFocused { window_id }));
reshuffle_around(entity, commands);
}
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn spawn_window_trigger(
mut trigger: On<SpawnWindowTrigger>,
windows: Query<(Entity, &Window, Has<FocusedMarker>)>,
mut apps: Query<(Entity, &mut Application)>,
mut active_display: ActiveDisplayMut,
config: Configuration,
mut commands: Commands,
) {
let new_windows = &mut trigger.event_mut().0;
while let Some(mut window) = new_windows.pop() {
let window_id = window.id();
if windows
.iter()
.any(|(_, window, _)| window.id() == window_id)
{
continue;
}
let Ok(pid) = window.pid() else {
trace!(
"{}: Unable to get window pid for {}",
function_name!(),
window_id,
);
continue;
};
let Some((app_entity, mut app)) = apps.iter_mut().find(|(_, app)| app.pid() == pid) else {
trace!(
"{}: unable to find application with pid {pid}.",
function_name!()
);
continue;
};
debug!(
"{}: created {} title: {} role: {} subrole: {} element: {}",
function_name!(),
window_id,
window.title().unwrap_or_default(),
window.role().unwrap_or_default(),
window.subrole().unwrap_or_default(),
window.element(),
);
if app.observe_window(&window).is_err() {
warn!(
"{}: Error observing window {}.",
function_name!(),
window_id
);
}
window.set_psn(app.psn());
let eligible = app.parent_window(active_display.id()).is_err() || window.is_root();
window.set_eligible(eligible);
let bundle_id = app.bundle_id().unwrap_or_default();
debug!(
"{}: window {} isroot {} eligible {} bundle_id {}",
function_name!(),
window_id,
window.is_root(),
window.is_eligible(),
bundle_id,
);
let title = window.title().unwrap_or_default();
let properties = config.find_window_properties(&title, bundle_id);
if !properties.is_empty() {
debug!(
"{}: Applying window properties for '{}'",
function_name!(),
window.id()
);
}
apply_window_properties(
window,
app_entity,
&properties,
&mut active_display,
&windows,
&mut commands,
);
}
}
fn apply_window_properties(
mut window: Window,
app_entity: Entity,
properties: &[WindowParams],
active_display: &mut ActiveDisplayMut,
windows: &Query<(Entity, &Window, Has<FocusedMarker>)>,
commands: &mut Commands,
) {
let floating = properties
.iter()
.find_map(|props| props.floating)
.unwrap_or(false);
let wanted_insertion = properties.iter().find_map(|props| props.index);
if let Some(padding) = properties.iter().find_map(|props| props.vertical_padding)
&& !floating
{
window.set_padding(WindowPadding::Vertical(padding.clamp(0, 50)));
}
if let Some(padding) = properties.iter().find_map(|props| props.horizontal_padding)
&& !floating
{
window.set_padding(WindowPadding::Horizontal(padding.clamp(0, 50)));
}
_ = window
.update_frame(&active_display.bounds())
.inspect_err(|err| error!("{}: {err}", function_name!()));
let entity = commands.spawn((window, ChildOf(app_entity))).id();
if floating {
commands.entity(entity).try_insert(Unmanaged::Floating);
return;
}
let Ok(panel) = active_display.active_panel() else {
return;
};
let insert_at = wanted_insertion.map_or_else(
|| {
let focused_window = windows
.iter()
.find_map(|(entity, _, focused)| focused.then_some(entity));
focused_window
.and_then(|entity| panel.index_of(entity).ok())
.and_then(|insert_at| (insert_at + 1 < panel.len()).then_some(insert_at + 1))
},
Some,
);
debug!("{}: New window adding at {panel}", function_name!());
match insert_at {
Some(after) => {
debug!("{}: New window inserted at {after}", function_name!());
panel.insert_at(after, entity);
}
None => panel.append(entity),
}
reshuffle_around(entity, commands);
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn refresh_configuration_trigger(
trigger: On<WMEventTrigger>,
window_manager: Res<WindowManager>,
mut config: ResMut<Config>,
watcher: Option<NonSendMut<Box<dyn Watcher>>>,
) {
let Event::ConfigRefresh(event) = &trigger.event().0 else {
return;
};
let Some(mut watcher) = watcher else {
return;
};
match &event.kind {
EventKind::Modify(
ModifyKind::Metadata(MetadataKind::WriteTime) | ModifyKind::Data(DataChange::Content),
) => (),
EventKind::Remove(_) => {
for path in &event.paths {
_ = watcher.unwatch(path).inspect_err(|err| {
error!(
"{}: unwatching the config '{}': {err}",
function_name!(),
path.display()
);
});
}
return;
}
_ => return,
}
for path in &event.paths {
if let Some(symlink) = symlink_target(path) {
debug!(
"{}: symlink '{}' changed, replacing the watcher.",
function_name!(),
symlink.display()
);
if let Ok(new_watcher) = window_manager
.setup_config_watcher(path)
.inspect_err(|err| {
error!(
"{}: watching the config '{}': {err}",
function_name!(),
path.display()
);
})
{
*watcher = new_watcher;
}
}
info!(
"{}: Reloading configuration file; {}",
function_name!(),
path.display()
);
_ = config.reload_config(path.as_path()).inspect_err(|err| {
error!(
"{}: loading config '{}': {err}",
function_name!(),
path.display()
);
});
}
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn print_internal_state_trigger(
trigger: On<WMEventTrigger>,
windows: Query<(Entity, &Window, Option<&Unmanaged>, Has<FocusedMarker>)>,
displays: Query<(&Display, Has<ActiveDisplayMarker>)>,
) {
let Event::PrintState = &trigger.event().0 else {
return;
};
let print_window =
|(entity, window, unmanaged, focused): (Entity, &Window, Option<_>, bool)| {
format!(
"id: {}, {entity}, {:.0}:{:.0}, {:.0}x{:.0}{}{}, title: '{:.70}'",
window.id(),
window.frame().origin.x,
window.frame().origin.y,
window.frame().size.width,
window.frame().size.height,
if focused { ", focused" } else { "" },
unmanaged.map(|m| format!(", {m:?}")).unwrap_or_default(),
window.title().unwrap_or_default()
)
};
let mut seen = EntityHashSet::new();
for (display, active) in displays {
for (space, strip) in &display.spaces {
let windows = strip
.all_windows()
.iter()
.flat_map(|entity| windows.get(*entity))
.inspect(|entity| {
seen.insert(entity.0);
})
.map(print_window)
.collect::<Vec<_>>();
debug!(
"{}: Display {}{}, workspace {space}:\n{}",
function_name!(),
display.id(),
if active { ", active" } else { "" },
windows.join("\n")
);
}
}
let remaining = windows
.iter()
.filter(|entity| !seen.contains(&entity.0))
.map(print_window)
.collect::<Vec<_>>();
debug!("{}: Remaining:\n{}", function_name!(), remaining.join("\n"));
if let Some(pool) = bevy::tasks::ComputeTaskPool::try_get() {
debug!(
"{}: Running with {} threads",
function_name!(),
pool.thread_num()
);
}
}