use bevy::ecs::system::NonSendMarker;
use bevy::prelude::*;
use bevy::window::MonitorSelection;
use bevy::window::PrimaryWindow;
use bevy::window::WindowMode;
use bevy::window::WindowScaleFactorChanged;
use bevy::winit::WINIT_WINDOWS;
use super::state;
use super::types::RestoreWindowConfig;
use super::types::WindowState;
#[cfg(all(target_os = "macos", feature = "workaround-winit-4441"))]
use crate::macos_drag_back_fix::DragBackSizeProtection;
use crate::monitors::CurrentMonitor;
use crate::monitors::Monitors;
#[cfg(all(target_os = "windows", feature = "workaround-winit-3124"))]
use crate::types::FullscreenRestoreState;
use crate::types::MonitorScaleStrategy;
use crate::types::SCALE_FACTOR_EPSILON;
use crate::types::SavedWindowMode;
use crate::types::TargetPosition;
use crate::types::WindowDecoration;
use crate::types::WindowRestoreState;
use crate::types::WindowTargetLoaded;
use crate::types::WinitInfo;
use crate::types::X11FrameCompensated;
use crate::window_ext::WindowExt;
pub fn init_winit_info(
mut commands: Commands,
window_entity: Single<Entity, With<PrimaryWindow>>,
monitors: Res<Monitors>,
_non_send: NonSendMarker,
) {
assert!(
!monitors.is_empty(),
"No monitors available - cannot initialize window manager without a display"
);
WINIT_WINDOWS.with(|ww| {
let ww = ww.borrow();
if let Some(winit_window) = ww.get_window(*window_entity) {
let outer = winit_window.outer_size();
let inner = winit_window.inner_size();
let decoration = WindowDecoration {
width: outer.width.saturating_sub(inner.width),
height: outer.height.saturating_sub(inner.height),
};
let pos = winit_window
.outer_position()
.map(|p| IVec2::new(p.x, p.y))
.unwrap_or(IVec2::ZERO);
debug!(
"[init_winit_info] outer_position={:?} is_wayland={}",
pos,
is_wayland()
);
let current_monitor_index = winit_window.current_monitor().and_then(|cm| {
let cm_pos = cm.position();
let idx = monitors.at(cm_pos.x, cm_pos.y).map(|m| m.index);
debug!(
"[init_winit_info] current_monitor() position=({}, {}) -> index={:?}",
cm_pos.x, cm_pos.y, idx
);
idx
});
debug!(
"[init_winit_info] current_monitor_index={:?}",
current_monitor_index
);
let starting_monitor = monitors.closest_to(pos.x, pos.y);
let starting_monitor_index = starting_monitor.index;
debug!(
"[init_winit_info] decoration={}x{} pos=({}, {}) starting_monitor={}",
decoration.width, decoration.height, pos.x, pos.y, starting_monitor_index
);
commands
.entity(*window_entity)
.insert(CurrentMonitor(*starting_monitor));
commands.insert_resource(WinitInfo {
starting_monitor_index,
window_decoration: decoration,
});
}
});
}
fn clamp_position_to_monitor(
saved_x: i32,
saved_y: i32,
target_info: &crate::monitors::MonitorInfo,
outer_width: u32,
outer_height: u32,
) -> IVec2 {
if cfg!(target_os = "macos") {
let mon_right = target_info.position.x + target_info.size.x as i32;
let mon_bottom = target_info.position.y + target_info.size.y as i32;
let mut x = saved_x;
let mut y = saved_y;
if x + outer_width as i32 > mon_right {
x = mon_right - outer_width as i32;
}
if y + outer_height as i32 > mon_bottom {
y = mon_bottom - outer_height as i32;
}
x = x.max(target_info.position.x);
y = y.max(target_info.position.y);
if x != saved_x || y != saved_y {
debug!(
"[clamp_position_to_monitor] Clamped: ({saved_x}, {saved_y}) -> ({x}, {y}) for outer size {outer_width}x{outer_height}"
);
}
IVec2::new(x, y)
} else {
IVec2::new(saved_x, saved_y)
}
}
pub fn load_target_position(
mut commands: Commands,
window_entity: Single<Entity, With<PrimaryWindow>>,
monitors: Res<Monitors>,
winit_info: Res<WinitInfo>,
config: Res<RestoreWindowConfig>,
) {
let Some(state) = state::load_state(&config.path) else {
debug!("[load_target_position] No saved bevy_window_manager state, showing window");
commands.queue(|world: &mut World| {
let mut query = world.query_filtered::<&mut Window, With<PrimaryWindow>>();
if let Some(mut window) = query.iter_mut(world).next() {
window.visible = true;
}
});
return;
};
debug!(
"[load_target_position] Loaded state: position={:?} size={}x{} monitor_index={} mode={:?}",
state.position, state.width, state.height, state.monitor_index, state.mode
);
let saved_pos = state.position;
let starting_monitor_index = winit_info.starting_monitor_index;
let starting_info = monitors.by_index(starting_monitor_index);
let starting_scale = starting_info.map_or(1.0, |m| m.scale);
let Some(target_info) = monitors.by_index(state.monitor_index) else {
debug!(
"[load_target_position] Target monitor index {} not found",
state.monitor_index
);
return;
};
let target_scale = target_info.scale;
let width = state.width;
let height = state.height;
let decoration = winit_info.decoration();
let outer_width = width + decoration.x;
let outer_height = height + decoration.y;
let strategy = determine_scale_strategy(starting_scale, target_scale);
let position = saved_pos
.map(|(x, y)| clamp_position_to_monitor(x, y, target_info, outer_width, outer_height));
debug!(
"[load_target_position] Starting monitor={} scale={}, Target monitor={} scale={}, strategy={:?}, position={:?}",
starting_monitor_index,
starting_scale,
state.monitor_index,
target_scale,
strategy,
position
);
#[cfg(all(target_os = "windows", feature = "workaround-winit-3124"))]
if matches!(state.mode, SavedWindowMode::Fullscreen { .. }) {
debug!(
"[load_target_position] Windows exclusive fullscreen: showing window for surface creation"
);
commands.queue(|world: &mut World| {
let mut query = world.query_filtered::<&mut Window, With<PrimaryWindow>>();
if let Some(mut window) = query.iter_mut(world).next() {
window.visible = true;
}
});
}
let window_mode = state.mode.to_window_mode(state.monitor_index);
commands.insert_resource(TargetPosition {
position,
width,
height,
target_scale,
starting_scale,
monitor_scale_strategy: strategy,
mode: state.mode,
target_monitor_index: state.monitor_index,
#[cfg(all(target_os = "windows", feature = "workaround-winit-3124"))]
fullscreen_restore_state: FullscreenRestoreState::WaitingForSurface,
});
let size = UVec2::new(width, height);
commands
.entity(*window_entity)
.trigger(|entity| WindowTargetLoaded {
entity,
position,
size,
mode: window_mode,
});
#[cfg(not(all(target_os = "linux", feature = "workaround-winit-4445")))]
commands.insert_resource(X11FrameCompensated);
#[cfg(all(target_os = "linux", feature = "workaround-winit-4445"))]
if is_wayland() {
commands.insert_resource(X11FrameCompensated);
}
}
pub fn move_to_target_monitor(
mut window: Single<&mut Window, With<PrimaryWindow>>,
target: Res<TargetPosition>,
) {
#[derive(Debug)]
struct MoveParams {
position: IVec2,
width: u32,
height: u32,
}
if target.mode.is_fullscreen() {
if let Some(pos) = target.position {
debug!(
"[move_to_target_monitor] Moving to target position {:?} for fullscreen mode {:?}",
pos, target.mode
);
window.position = WindowPosition::At(pos);
} else {
debug!(
"[move_to_target_monitor] No position available (Wayland), fullscreen mode {:?}",
target.mode
);
}
return;
}
let Some(pos) = target.position else {
debug!(
"[move_to_target_monitor] No position available (Wayland), setting size only: {}x{}",
target.width, target.height
);
debug!(
"[move_to_target_monitor] BEFORE set_physical_resolution: physical={}x{} logical={}x{} scale={}",
window.resolution.physical_width(),
window.resolution.physical_height(),
window.resolution.width(),
window.resolution.height(),
window.resolution.scale_factor()
);
window
.resolution
.set_physical_resolution(target.width, target.height);
debug!(
"[move_to_target_monitor] AFTER set_physical_resolution: physical={}x{} logical={}x{} scale={}",
window.resolution.physical_width(),
window.resolution.physical_height(),
window.resolution.width(),
window.resolution.height(),
window.resolution.scale_factor()
);
return;
};
let params = match target.monitor_scale_strategy {
MonitorScaleStrategy::HigherToLower(_) => {
let ratio = target.starting_scale / target.target_scale;
let comp_x = (f64::from(pos.x) * ratio) as i32;
let comp_y = (f64::from(pos.y) * ratio) as i32;
debug!(
"[move_to_target_monitor] HigherToLower: compensating position {:?} -> ({}, {}) (ratio={})",
pos, comp_x, comp_y, ratio
);
MoveParams {
position: IVec2::new(comp_x, comp_y),
width: target.width,
height: target.height,
}
},
_ => MoveParams {
position: pos,
width: target.width,
height: target.height,
},
};
debug!(
"[move_to_target_monitor] position={:?} size={}x{} visible={}",
params.position, params.width, params.height, window.visible
);
window.position = WindowPosition::At(params.position);
window
.resolution
.set_physical_resolution(params.width, params.height);
}
#[derive(Default)]
pub struct CachedWindowState {
position: Option<IVec2>,
width: u32,
height: u32,
mode: Option<SavedWindowMode>,
monitor_index: Option<usize>,
}
#[allow(
clippy::type_complexity,
clippy::too_many_lines,
clippy::option_if_let_else
)]
pub fn save_window_state(
mut commands: Commands,
config: Res<RestoreWindowConfig>,
monitors: Res<Monitors>,
window: Single<
(Entity, &Window, Option<&CurrentMonitor>),
(With<PrimaryWindow>, Changed<Window>),
>,
mut cached: Local<CachedWindowState>,
_non_send: NonSendMarker,
) {
let (window_entity, window, existing_monitor) = *window;
if monitors.is_empty() {
return;
}
#[cfg(all(target_os = "linux", feature = "workaround-winit-4443"))]
let pos = WINIT_WINDOWS.with(|ww| {
let ww = ww.borrow();
let winit_win = ww.get_window(window_entity)?;
let outer_pos = winit_win.outer_position().ok()?;
Some(IVec2::new(outer_pos.x, outer_pos.y))
});
#[cfg(not(all(target_os = "linux", feature = "workaround-winit-4443")))]
let pos = match window.position {
bevy::window::WindowPosition::At(p) => Some(p),
_ => None,
};
let width = window.resolution.physical_width();
let height = window.resolution.physical_height();
let mode: SavedWindowMode = (&window.effective_mode(&monitors)).into();
let (monitor_index, monitor_scale) = if is_wayland() {
existing_monitor.map_or_else(
|| {
let p = monitors.first();
(p.index, p.scale)
},
|m| (m.index, m.scale),
)
} else {
let info = window.monitor(&monitors);
commands.entity(window_entity).insert(CurrentMonitor(*info));
(info.index, info.scale)
};
let monitor_changed = cached.monitor_index != Some(monitor_index);
if monitor_changed {
let prev_scale = cached
.monitor_index
.and_then(|i| monitors.by_index(i))
.map(|m| m.scale);
debug!(
"[save_window_state] MONITOR CHANGE: {:?} (scale={:?}) -> {} (scale={})",
cached.monitor_index, prev_scale, monitor_index, monitor_scale
);
debug!(
"[save_window_state] physical: {}x{}, logical: {}x{}, scale_factor: {}",
width,
height,
window.resolution.width(),
window.resolution.height(),
window.resolution.scale_factor()
);
debug!(
"[save_window_state] cached size was: {}x{}",
cached.width, cached.height
);
}
let position_changed = cached.position != pos;
let size_changed = cached.width != width || cached.height != height;
let mode_changed = cached.mode.as_ref() != Some(&mode);
if !position_changed && !size_changed && !mode_changed {
cached.monitor_index = Some(monitor_index);
return;
}
cached.position = pos;
cached.width = width;
cached.height = height;
cached.mode = Some(mode.clone());
cached.monitor_index = Some(monitor_index);
debug!(
"[save_window_state] pos={:?} size={}x{} monitor={} scale={} mode={:?}",
pos, width, height, monitor_index, monitor_scale, mode
);
let app_name = std::env::current_exe()
.ok()
.and_then(|p| p.file_stem().and_then(|s| s.to_str()).map(String::from))
.unwrap_or_default();
let state = WindowState {
position: pos.map(|p| (p.x, p.y)),
width,
height,
monitor_index,
mode,
app_name,
};
state::save_state(&config.path, &state);
}
pub fn restore_primary_window(
mut commands: Commands,
mut scale_changed_messages: MessageReader<WindowScaleFactorChanged>,
mut target: ResMut<TargetPosition>,
mut primary_window: Single<&mut Window, With<PrimaryWindow>>,
) {
let scale_changed = scale_changed_messages.read().last().is_some();
if target.monitor_scale_strategy
== MonitorScaleStrategy::HigherToLower(WindowRestoreState::WaitingForScaleChange)
&& scale_changed
{
debug!("[Restore] ScaleChanged received, transitioning to WindowRestoreState::ApplySize");
target.monitor_scale_strategy =
MonitorScaleStrategy::HigherToLower(WindowRestoreState::ApplySize);
}
#[cfg(all(target_os = "windows", feature = "workaround-winit-3124"))]
if target.mode.is_fullscreen()
&& target.fullscreen_restore_state == FullscreenRestoreState::WaitingForSurface
{
debug!("[Restore] First frame passed, transitioning to ApplyFullscreen");
target.fullscreen_restore_state = FullscreenRestoreState::ApplyFullscreen;
return; }
#[cfg(all(target_os = "macos", feature = "workaround-winit-4441"))]
let was_higher_to_lower = matches!(
target.monitor_scale_strategy,
MonitorScaleStrategy::HigherToLower(WindowRestoreState::ApplySize)
);
if matches!(
try_apply_restore(&target, &mut primary_window),
RestoreStatus::Complete
) {
#[cfg(all(target_os = "macos", feature = "workaround-winit-4441"))]
if was_higher_to_lower {
debug!(
"[Restore] Inserting DragBackSizeProtection: size={}x{} launch_scale={} restored_scale={}",
target.width, target.height, target.starting_scale, target.target_scale
);
let phase1_cached_size = UVec2::new(target.width, target.height);
commands.insert_resource(DragBackSizeProtection {
expected_physical_size: UVec2::new(target.width, target.height),
launch_scale: target.starting_scale,
restored_scale: target.target_scale,
phase1_cached_size,
state: crate::macos_drag_back_fix::CorrectionState::WaitingForDragBack,
});
}
commands.remove_resource::<TargetPosition>();
}
}
enum RestoreStatus {
Complete,
Waiting,
}
pub fn is_wayland() -> bool {
cfg!(target_os = "linux")
&& std::env::var("WAYLAND_DISPLAY")
.map(|v| !v.is_empty())
.unwrap_or(false)
}
#[cfg(target_os = "linux")]
pub fn update_wayland_monitor(
mut commands: Commands,
window: Single<(Entity, &Window), With<PrimaryWindow>>,
monitors: Res<Monitors>,
mut cached_index: Local<Option<usize>>,
_non_send: NonSendMarker,
) {
let (window_entity, window) = *window;
if !window.focused {
return;
}
let detected_index: Option<usize> = WINIT_WINDOWS.with(|ww| {
let ww = ww.borrow();
ww.get_window(window_entity).and_then(|winit_window| {
winit_window.current_monitor().and_then(|current_monitor| {
let pos = current_monitor.position();
monitors.at(pos.x, pos.y).map(|mon| mon.index)
})
})
});
if *cached_index != detected_index {
if let Some(idx) = detected_index
&& let Some(info) = monitors.by_index(idx)
{
debug!(
"[update_wayland_monitor] Monitor changed: {:?} -> {}",
*cached_index, idx
);
commands.entity(window_entity).insert(CurrentMonitor(*info));
}
*cached_index = detected_index;
}
}
fn apply_fullscreen_restore(
target: &TargetPosition,
primary_window: &mut Window,
monitor_index: usize,
) {
let window_mode = if is_wayland() && matches!(target.mode, SavedWindowMode::Fullscreen { .. }) {
warn!(
"Exclusive fullscreen is not supported on Wayland, restoring as BorderlessFullscreen"
);
WindowMode::BorderlessFullscreen(MonitorSelection::Index(monitor_index))
} else {
target.mode.to_window_mode(monitor_index)
};
debug!(
"[Restore] Applying fullscreen mode {:?} on monitor {} -> WindowMode::{:?}",
target.mode, monitor_index, window_mode
);
debug!(
"[Restore] Current window state: position={:?} mode={:?}",
primary_window.position, primary_window.mode
);
primary_window.mode = window_mode;
}
fn apply_window_geometry(
window: &mut Window,
position: Option<IVec2>,
size: UVec2,
strategy: &str,
ratio: Option<f64>,
) {
if let Some(pos) = position {
if let Some(r) = ratio {
debug!(
"[try_apply_restore] position={:?} size={}x{} ({strategy}, ratio={r})",
pos, size.x, size.y
);
} else {
debug!(
"[try_apply_restore] position={:?} size={}x{} ({strategy})",
pos, size.x, size.y
);
}
window.set_position_and_size(pos, size);
} else {
if let Some(r) = ratio {
debug!(
"[try_apply_restore] size={}x{} only ({strategy}, ratio={r}, no position)",
size.x, size.y
);
} else {
debug!(
"[try_apply_restore] size={}x{} only ({strategy}, no position)",
size.x, size.y
);
}
window.resolution.set_physical_resolution(size.x, size.y);
}
}
fn try_apply_restore(target: &TargetPosition, primary_window: &mut Window) -> RestoreStatus {
if target.mode.is_fullscreen() {
apply_fullscreen_restore(target, primary_window, target.target_monitor_index);
primary_window.visible = true;
return RestoreStatus::Complete;
}
debug!(
"[Restore] target_pos={:?} target_scale={} strategy={:?}",
target.position, target.target_scale, target.monitor_scale_strategy
);
match target.monitor_scale_strategy {
MonitorScaleStrategy::ApplyUnchanged => {
apply_window_geometry(
primary_window,
target.position(),
target.size(),
"ApplyUnchanged",
None,
);
},
#[cfg(all(target_os = "windows", feature = "workaround-winit-4440"))]
MonitorScaleStrategy::CompensateSizeOnly => {
apply_window_geometry(
primary_window,
target.position(),
target.compensated_size(),
"CompensateSizeOnly",
Some(target.ratio()),
);
},
#[cfg(all(not(target_os = "windows"), feature = "workaround-winit-4440"))]
MonitorScaleStrategy::LowerToHigher => {
apply_window_geometry(
primary_window,
target.compensated_position(),
target.compensated_size(),
"LowerToHigher",
Some(target.ratio()),
);
},
MonitorScaleStrategy::HigherToLower(WindowRestoreState::ApplySize) => {
let size = target.size();
debug!(
"[try_apply_restore] size={}x{} ONLY (HigherToLower::ApplySize, position already set)",
size.x, size.y
);
primary_window
.resolution
.set_physical_resolution(size.x, size.y);
},
MonitorScaleStrategy::HigherToLower(WindowRestoreState::WaitingForScaleChange) => {
debug!("[Restore] HigherToLower: waiting for ScaleChanged message");
return RestoreStatus::Waiting;
},
}
primary_window.visible = true;
RestoreStatus::Complete
}
#[cfg(all(target_os = "windows", feature = "workaround-winit-4440"))]
fn determine_scale_strategy(starting_scale: f64, target_scale: f64) -> MonitorScaleStrategy {
if (starting_scale - target_scale).abs() < SCALE_FACTOR_EPSILON {
MonitorScaleStrategy::ApplyUnchanged
} else {
MonitorScaleStrategy::CompensateSizeOnly
}
}
#[cfg(all(target_os = "windows", not(feature = "workaround-winit-4440")))]
fn determine_scale_strategy(_starting_scale: f64, _target_scale: f64) -> MonitorScaleStrategy {
MonitorScaleStrategy::ApplyUnchanged
}
#[cfg(all(not(target_os = "windows"), feature = "workaround-winit-4440"))]
fn determine_scale_strategy(starting_scale: f64, target_scale: f64) -> MonitorScaleStrategy {
if is_wayland() {
return MonitorScaleStrategy::ApplyUnchanged;
}
if (starting_scale - target_scale).abs() < SCALE_FACTOR_EPSILON {
MonitorScaleStrategy::ApplyUnchanged
} else if starting_scale < target_scale {
MonitorScaleStrategy::LowerToHigher
} else {
MonitorScaleStrategy::HigherToLower(WindowRestoreState::WaitingForScaleChange)
}
}
#[cfg(all(not(target_os = "windows"), not(feature = "workaround-winit-4440")))]
fn determine_scale_strategy(_starting_scale: f64, _target_scale: f64) -> MonitorScaleStrategy {
MonitorScaleStrategy::ApplyUnchanged
}