use std::sync::mpsc::Receiver;
use std::time::{Duration, Instant};
use bevy::MinimalPlugins;
use bevy::app::App as BevyApp;
use bevy::app::{PostUpdate, PreUpdate, Startup};
use bevy::ecs::message::Messages;
use bevy::ecs::resource::Resource;
use bevy::ecs::schedule::common_conditions::resource_exists;
use bevy::ecs::system::{Commands, Res};
use bevy::prelude::Event as BevyEvent;
use bevy::tasks::Task;
use bevy::time::Timer;
use bevy::time::common_conditions::on_timer;
use bevy::time::{Time, Virtual};
use bevy::{
app::Update,
ecs::{component::Component, entity::Entity, schedule::IntoScheduleConfigs},
};
use derive_more::{Deref, DerefMut};
use objc2_core_graphics::CGDirectDisplayID;
use tracing::{Level, instrument};
use crate::commands::register_commands;
use crate::config::{CONFIGURATION_FILE, Config};
use crate::errors::Result;
use crate::events::{Event, EventSender};
use crate::manager::{
Origin, ProcessApi, Size, Window, WindowManager, WindowManagerApi, WindowManagerOS,
};
use crate::overlay::OverlayManager;
use crate::platform::{PlatformCallbacks, WinID};
pub mod params;
mod systems;
mod triggers;
pub fn register_systems(app: &mut bevy::app::App) {
const DISPLAY_CHANGE_CHECK_FREQ_MS: u64 = 1000;
app.add_systems(
Startup,
(systems::gather_displays, systems::gather_initial_processes).chain(),
);
app.add_systems(
PreUpdate,
(systems::dispatch_toplevel_triggers, systems::pump_events),
);
app.add_systems(
Update,
(
(
systems::add_existing_process,
systems::add_existing_application,
systems::finish_setup,
)
.chain()
.run_if(resource_exists::<Initializing>),
systems::apply_scroll_physics,
systems::add_launched_process,
systems::add_launched_application,
systems::fresh_marker_cleanup,
systems::timeout_ticker,
systems::window_update_frame,
systems::displays_rearranged,
systems::reposition_dragged_window,
systems::reshuffle_layout_strip,
systems::find_orphaned_workspaces
.after(systems::displays_rearranged)
.run_if(on_timer(Duration::from_millis(
DISPLAY_CHANGE_CHECK_FREQ_MS,
))),
systems::cleanup_on_exit,
),
);
app.add_systems(
Update,
(
systems::display_changes_watcher,
systems::workspace_change_watcher,
)
.run_if(resource_exists::<PollForNotifications>)
.run_if(on_timer(Duration::from_millis(
DISPLAY_CHANGE_CHECK_FREQ_MS,
))),
);
app.add_systems(
PostUpdate,
(
systems::position_layout_strip,
systems::animate_entities.after(systems::position_layout_strip),
systems::animate_resize_entities.after(systems::position_layout_strip),
systems::update_overlays
.after(systems::animate_entities)
.after(systems::animate_resize_entities)
.run_if(|config: Option<Res<Config>>| {
config.is_some_and(|config| {
config.options().dim_inactive_color.is_some()
|| config.border_active_window()
})
}),
systems::commit_window_position.after(systems::animate_entities),
systems::commit_window_size.after(systems::animate_resize_entities),
),
);
}
pub fn register_triggers(app: &mut bevy::app::App) {
app.add_observer(triggers::mouse_moved_trigger)
.add_observer(triggers::mouse_down_trigger)
.add_observer(triggers::mouse_dragged_trigger)
.add_observer(triggers::workspace_change_trigger)
.add_observer(triggers::active_workspace_trigger)
.add_observer(triggers::display_change_trigger)
.add_observer(triggers::front_switched_trigger)
.add_observer(triggers::center_mouse_trigger)
.add_observer(triggers::window_focused_trigger)
.add_observer(triggers::swipe_gesture_trigger)
.add_observer(triggers::mission_control_trigger)
.add_observer(triggers::application_event_trigger)
.add_observer(triggers::dispatch_application_messages)
.add_observer(triggers::window_destroyed_trigger)
.add_observer(triggers::window_unmanaged_trigger)
.add_observer(triggers::window_managed_trigger)
.add_observer(triggers::spawn_window_trigger)
.add_observer(triggers::refresh_configuration_trigger)
.add_observer(triggers::stray_focus_observer)
.add_observer(triggers::locate_dock_trigger)
.add_observer(triggers::send_message_trigger)
.add_observer(triggers::window_removal_trigger)
.add_observer(triggers::dim_window_trigger)
.add_observer(triggers::dim_remove_window_trigger);
}
#[derive(Component)]
pub struct FocusedMarker;
#[derive(Component)]
pub struct ActiveWorkspaceMarker;
#[derive(Component)]
pub struct ActiveDisplayMarker;
#[derive(Component)]
pub struct FreshMarker;
#[derive(Component)]
pub struct ExistingMarker;
#[derive(Component)]
pub struct RepositionMarker {
pub origin: Origin,
pub display_id: CGDirectDisplayID,
}
#[derive(Component)]
pub struct ResizeMarker {
pub size: Size,
pub display_id: CGDirectDisplayID,
}
#[derive(Component)]
pub struct WindowDraggedMarker {
pub entity: Entity,
pub display_id: CGDirectDisplayID,
}
#[derive(Component)]
pub struct ReshuffleAroundMarker;
#[derive(Component)]
pub struct StackAdjustedResize;
#[derive(Component, Debug)]
pub struct Scrolling {
pub velocity: f64,
pub is_user_swiping: bool,
pub last_event: Instant,
}
impl Default for Scrolling {
fn default() -> Self {
Self {
velocity: 0.0,
is_user_swiping: false,
last_event: Instant::now(),
}
}
}
#[derive(Component, Clone, Debug, Deref, DerefMut)]
pub struct Position(pub Origin);
#[derive(Component, Clone, Debug, Deref, DerefMut)]
pub struct Bounds(pub Size);
#[derive(Component)]
pub struct NativeFullscreenMarker {
pub order: u32,
}
#[derive(Component)]
pub struct FullWidthMarker {
pub width_ratio: f64,
pub was_stacked: bool,
}
#[derive(Component, Debug)]
pub enum Unmanaged {
Floating,
Minimized,
Hidden,
}
#[derive(Component, Deref, DerefMut)]
pub struct BProcess(pub Box<dyn ProcessApi>);
#[derive(Component)]
pub struct Timeout {
pub timer: Timer,
pub message: Option<String>,
}
impl Timeout {
pub fn new(duration: Duration, message: Option<String>) -> Self {
let timer = Timer::from_seconds(duration.as_secs_f32(), bevy::time::TimerMode::Once);
Self { timer, message }
}
}
#[derive(Component)]
pub struct StrayFocusEvent(pub WinID);
#[derive(Component)]
pub struct BruteforceWindows(Task<Vec<Window>>);
#[derive(Component, Debug)]
pub enum DockPosition {
Bottom(i32),
Left(i32),
Right(i32),
Hidden,
}
#[derive(Resource)]
pub struct SkipReshuffle(pub bool);
#[derive(Resource)]
pub struct MissionControlActive(pub bool);
#[derive(Resource)]
pub struct FocusFollowsMouse(pub Option<WinID>);
#[derive(PartialEq, Resource)]
pub struct PollForNotifications;
#[derive(PartialEq, Resource)]
pub struct Initializing;
#[derive(BevyEvent)]
pub struct WMEventTrigger(pub Event);
#[derive(BevyEvent)]
pub struct SpawnWindowTrigger(pub Vec<Window>);
#[derive(BevyEvent)]
pub struct LocateDockTrigger(pub Entity);
#[derive(BevyEvent)]
pub struct SendMessageTrigger(pub Event);
#[instrument(level = Level::TRACE, skip(commands))]
pub fn reposition_entity(
entity: Entity,
origin: Origin,
display_id: CGDirectDisplayID,
commands: &mut Commands,
) {
if let Ok(mut entity_cmmands) = commands.get_entity(entity) {
entity_cmmands.try_insert(RepositionMarker { origin, display_id });
}
}
#[instrument(level = Level::TRACE, skip(commands))]
pub fn resize_entity(
entity: Entity,
size: Size,
display_id: CGDirectDisplayID,
commands: &mut Commands,
) {
if size.x <= 0 || size.y <= 0 {
return;
}
if let Ok(mut entity_cmmands) = commands.get_entity(entity) {
entity_cmmands.try_insert(ResizeMarker { size, display_id });
}
}
#[instrument(level = Level::TRACE, skip(commands))]
pub fn reshuffle_around(entity: Entity, commands: &mut Commands) {
if let Ok(mut entity_commands) = commands.get_entity(entity) {
entity_commands.try_insert(ReshuffleAroundMarker);
}
}
pub fn setup_bevy_app(sender: EventSender, receiver: Receiver<Event>) -> Result<BevyApp> {
let window_manager: Box<dyn WindowManagerApi> = Box::new(WindowManagerOS::new(sender.clone()));
let watcher = window_manager.setup_config_watcher(CONFIGURATION_FILE.as_path())?;
let mut app = BevyApp::new();
app.add_plugins(MinimalPlugins)
.init_resource::<Messages<Event>>()
.insert_resource(Time::<Virtual>::from_max_delta(Duration::from_secs(10)))
.insert_resource(WindowManager(window_manager))
.insert_resource(SkipReshuffle(false))
.insert_resource(MissionControlActive(false))
.insert_resource(FocusFollowsMouse(None))
.insert_resource(PollForNotifications)
.insert_resource(Initializing)
.insert_non_send_resource(watcher)
.add_plugins((register_triggers, register_systems, register_commands));
let mut platform_callbacks = PlatformCallbacks::new(sender);
platform_callbacks.setup_handlers()?;
let overlay_manager = OverlayManager::new(platform_callbacks.main_thread_marker);
app.insert_non_send_resource(platform_callbacks);
app.insert_non_send_resource(overlay_manager);
app.insert_non_send_resource(receiver);
Ok(app)
}