use std::time::Duration;
use crate::animation::WindowAnimator;
use crate::animation::backend::win32::Win32Backend;
use crate::common::{Rect, WindowId};
use crate::config::dirs::history_rules_path_in;
use crate::config::history::HistoryStore;
use crate::config::types::{FlowConfig, WindowRulesConfig};
use crate::ipc::transport::PipeServer;
use crate::layout::types::MonitorInfo;
use crate::registry::types::{FloatingState, WindowState};
use crate::registry::{WindowRegistry, hooks, win32 as registry_win32};
use crate::workspace::{Monitor, ScrollingSpace, Workspace, WorkspaceId};
use windows::Win32::Foundation::HWND;
use super::animation::animate_layout_raw;
use super::config_derive;
use super::types::FlowWM;
impl FlowWM {
pub fn new(
app_config: FlowConfig,
user_rules: WindowRulesConfig,
default_rules: WindowRulesConfig,
config_dir: std::path::PathBuf,
desktop_name: Option<String>,
) -> Result<Self, String> {
log::debug!(
"effective config: columns_per_screen={}, column_width={:?}, min_column_width_px={}, padding.window_gap={}, padding.up={}, padding.down={}, animation.duration_ms={}",
app_config.columns_per_screen,
app_config.column_width,
app_config.min_column_width_px,
app_config.padding.window_gap,
app_config.padding.up,
app_config.padding.down,
app_config.animation.duration_ms,
);
let mut registry = WindowRegistry::new(&user_rules, &default_rules);
let history = HistoryStore::load(&history_rules_path_in(&config_dir));
if !history.is_empty() {
log::info!("loaded {} learned rules from history", history.len());
registry.set_learned_rules(history.rules().to_vec());
}
registry.scan_existing_windows()?;
let geometry = registry_win32::get_primary_monitor_info()?;
let monitor = MonitorInfo {
work_area: geometry.work_area,
};
let layout_config = config_derive::derive_layout_config(&app_config, &monitor);
let mut scrolling = ScrollingSpace::new(
monitor,
layout_config.column_width,
layout_config.min_column_width_px,
layout_config.min_window_height_px,
layout_config.padding,
app_config.columns_per_screen,
);
let (tiling_ids, widths): (Vec<WindowId>, Vec<u32>) = registry
.tiling_window_ids_with_widths_sorted_by_x()
.into_iter()
.unzip();
log::debug!(
"init: {} tiling windows (sorted by x), widths: {:?}",
tiling_ids.len(),
widths
);
let initial_diff = if !tiling_ids.is_empty() {
let fg_hwnd = registry_win32::get_foreground_window();
if let Some(hwnd) = fg_hwnd {
registry.set_focused(hwnd);
}
let focus_col = fg_hwnd.and_then(|hwnd| {
let wid = WindowId(hwnd);
tiling_ids.iter().position(|&id| id == wid)
});
log::debug!("init: focus_col = {focus_col:?} (foreground window lookup)");
let diff = scrolling.initialize_windows(tiling_ids, focus_col, Some(&widths));
for entry in &diff.actual_layout.entries {
log::trace!(
"init target: {:?} rect ({},{},{},{})",
entry.window_id,
entry.rect.x,
entry.rect.y,
entry.rect.width,
entry.rect.height,
);
}
Some(diff)
} else {
log::warn!("init: no tiling windows found — skipping initial layout");
None
};
let backend = Win32Backend::new();
let init_config = config_derive::derive_animator_config(&app_config, Duration::ZERO);
log::debug!(
"init: animator config duration={:?}ms, animation.enabled={}",
init_config.duration,
app_config.animation.enabled,
);
let mut animator = WindowAnimator::new(backend, init_config);
if let Some(ref diff) = initial_diff {
log::debug!(
"init: submitting {} targets to animator",
diff.actual_layout.entries.len()
);
animate_layout_raw(
&mut animator,
diff,
®istry,
app_config.borders.thickness as i32,
app_config.borders.overlap as i32,
);
registry.update_tiling_slots_from_layout(&diff.virtual_layout);
registry.update_tiled_rects(&diff.actual_layout);
}
let (hook_receiver, _hook_handle, hook_signal) = hooks::start_hook_thread(desktop_name)?;
let server = PipeServer::create()
.map_err(|e| format!("failed to create pipe (is another daemon running?): {e}"))?;
log::info!("flowd: daemon initialized successfully");
const WORKSPACE_COUNT: u32 = 10;
let mut workspaces: Vec<Workspace> = Vec::with_capacity(WORKSPACE_COUNT as usize);
workspaces.push(Workspace::new(WorkspaceId(1), scrolling));
for id in 2..=WORKSPACE_COUNT {
let empty_scrolling = ScrollingSpace::new(
monitor,
layout_config.column_width,
layout_config.min_column_width_px,
layout_config.min_window_height_px,
layout_config.padding,
app_config.columns_per_screen,
);
workspaces.push(Workspace::new(WorkspaceId(id), empty_scrolling));
}
let mut manager = Self {
registry,
history,
monitors: vec![Monitor::new(
geometry.screen_rect,
monitor.work_area,
workspaces,
0,
)],
active_monitor: 0,
animator,
server,
config: app_config,
config_dir,
hook_receiver,
_hook_handle,
hook_signal,
shutting_down: false,
pending_creations: Vec::new(),
float_resume_deadline: None,
last_foreground_sync: std::time::Instant::now(),
};
manager.populate_existing_floats();
manager.refresh_all_border_styles();
Ok(manager)
}
fn populate_existing_floats(&mut self) {
let float_hwnds: Vec<isize> = self
.registry
.windows()
.filter(|w| matches!(w.state, WindowState::Floating(FloatingState::Active { .. })))
.map(|w| w.hwnd.0 as isize)
.collect();
for hwnd in float_hwnds {
let wid = WindowId(hwnd);
let rect = self
.current_visible_rect(hwnd)
.unwrap_or_else(|| self.centered_float_rect(wid));
self.register_float(wid, rect);
}
}
fn current_visible_rect(&self, hwnd: isize) -> Option<Rect> {
let hwnd_handle = HWND(hwnd as *mut _);
let window_rect = registry_win32::get_window_rect(hwnd_handle).ok()?;
let window = self.registry.get_window(hwnd_handle)?;
Some(window.invisible_bounds.window_to_visible(window_rect))
}
}