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 bevy_kana::ToI32;
use bevy_kana::ToU32;
use super::ManagedWindow;
use super::ManagedWindowPersistence;
use super::Platform;
use super::WindowKey;
use super::constants::DEFAULT_SCALE_FACTOR;
use super::constants::SCALE_FACTOR_EPSILON;
use super::monitors::CurrentMonitor;
use super::monitors::MonitorInfo;
use super::monitors::Monitors;
use super::restore_plan;
use super::state;
use super::types::FullscreenRestoreState;
use super::types::MonitorScaleStrategy;
use super::types::RestoreWindowConfig;
use super::types::SavedWindowMode;
use super::types::SettleSnapshot;
use super::types::SettleState;
use super::types::TargetPosition;
use super::types::WindowDecoration;
use super::types::WindowRestoreMismatch;
use super::types::WindowRestoreState;
use super::types::WindowRestored;
use super::types::WindowState;
use super::types::WinitInfo;
use super::types::X11FrameCompensated;
pub(crate) 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={pos:?} platform={:?}",
Platform::detect()
);
let starting_monitor = winit_window
.current_monitor()
.and_then(|cm| {
let monitor_position = cm.position();
let info = monitors.at(monitor_position.x, monitor_position.y);
debug!(
"[init_winit_info] current_monitor() position=({}, {}) -> index={:?}",
monitor_position.x, monitor_position.y, info.map(|m| m.index)
);
info.copied()
})
.unwrap_or_else(|| {
debug!(
"[init_winit_info] current_monitor() unavailable, falling back to closest_to({}, {})",
pos.x, pos.y
);
*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 {
monitor: starting_monitor,
effective_mode: WindowMode::Windowed,
});
commands.insert_resource(WinitInfo {
starting_monitor_index,
decoration,
});
}
});
}
pub(crate) fn load_target_position(
mut commands: Commands,
window_entity: Single<Entity, With<PrimaryWindow>>,
monitors: Res<Monitors>,
winit_info: Res<WinitInfo>,
mut config: ResMut<RestoreWindowConfig>,
platform: Res<Platform>,
) {
if let Some(all_states) = state::load_all_states(&config.path) {
config.loaded_states = all_states;
}
let Some(state) = config.loaded_states.get(&WindowKey::Primary).cloned() 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={:?} logical_size={}x{} monitor_scale={} monitor_index={} mode={:?}",
state.logical_position,
state.logical_width,
state.logical_height,
state.monitor_scale,
state.monitor_index,
state.mode
);
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(DEFAULT_SCALE_FACTOR, |m| m.scale);
let (target_info, fallback_position, used_fallback) =
restore_plan::resolve_target_monitor_and_position(
state.monitor_index,
state.logical_position,
&monitors,
);
if used_fallback {
warn!(
"[load_target_position] Target monitor {} not found, falling back to monitor 0",
state.monitor_index
);
}
let target = restore_plan::compute_target_position(
&state,
target_info,
fallback_position,
winit_info.decoration(),
starting_scale,
*platform,
);
debug!(
"[load_target_position] Starting monitor={} scale={}, Target monitor={} scale={}, strategy={:?}, position={:?}",
starting_monitor_index,
starting_scale,
target.target_monitor_index,
target.target_scale,
target.scale_strategy,
target.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 entity = *window_entity;
let is_fullscreen = state.mode.is_fullscreen();
commands.entity(entity).insert(target);
if is_fullscreen || !platform.needs_frame_compensation() {
commands.entity(entity).insert(X11FrameCompensated);
}
}
#[cfg(target_os = "linux")]
pub(crate) fn move_to_target_monitor(
mut window: Single<&mut Window, With<PrimaryWindow>>,
targets: Query<&TargetPosition, With<PrimaryWindow>>,
platform: Res<Platform>,
) {
if platform.is_wayland() {
return;
}
let Ok(target) = targets.single() else {
return;
};
if !target.mode.is_fullscreen() {
return;
}
if let Some(pos) = target.position {
debug!("[move_to_target_monitor] X11 fullscreen: setting position={pos:?}");
window.position = WindowPosition::At(pos);
}
}
pub(crate) fn apply_initial_move(target: &TargetPosition, window: &mut Window) {
#[derive(Debug)]
struct MoveParams {
position: IVec2,
width: u32,
height: u32,
}
if target.mode.is_fullscreen() {
if let Some(pos) = target.position {
debug!(
"[apply_initial_move] Moving to target position {:?} for fullscreen mode {:?}",
pos, target.mode
);
window.position = WindowPosition::At(pos);
} else {
debug!(
"[apply_initial_move] No position available (Wayland), fullscreen mode {:?}",
target.mode
);
}
return;
}
let Some(pos) = target.position else {
debug!(
"[apply_initial_move] No position available (Wayland), setting size only: {}x{}",
target.width, target.height
);
debug!(
"[apply_initial_move] 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!(
"[apply_initial_move] 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.scale_strategy {
MonitorScaleStrategy::HigherToLower(_) => {
let ratio = target.starting_scale / target.target_scale;
let compensated_x = (f64::from(pos.x) * ratio).to_i32();
let compensated_y = (f64::from(pos.y) * ratio).to_i32();
debug!(
"[apply_initial_move] HigherToLower: compensating position {:?} -> ({}, {}) (ratio={})",
pos, compensated_x, compensated_y, ratio
);
MoveParams {
position: IVec2::new(compensated_x, compensated_y),
width: target.width,
height: target.height,
}
},
MonitorScaleStrategy::CompensateSizeOnly(_) => {
let compensated = target.compensated_size();
debug!(
"[apply_initial_move] CompensateSizeOnly: position={:?} compensated_size={}x{} (ratio={})",
pos,
compensated.x,
compensated.y,
target.ratio()
);
MoveParams {
position: pos,
width: compensated.x,
height: compensated.y,
}
},
_ => MoveParams {
position: pos,
width: target.width,
height: target.height,
},
};
debug!(
"[apply_initial_move] 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(crate) struct CachedWindowState {
position: Option<IVec2>,
logical_width: u32,
logical_height: u32,
mode: Option<SavedWindowMode>,
monitor_index: Option<usize>,
}
pub(crate) fn save_active_window_state(
config: &RestoreWindowConfig,
monitors: &Monitors,
all_windows: &Query<
(
Entity,
&Window,
Option<&CurrentMonitor>,
Option<&ManagedWindow>,
),
Or<(With<PrimaryWindow>, With<ManagedWindow>)>,
>,
primary_q: &Query<(), With<PrimaryWindow>>,
exclude_entity: Option<Entity>,
) {
if monitors.is_empty() {
return;
}
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 mut states = std::collections::HashMap::new();
for (entity, window, existing_monitor, managed) in all_windows {
if exclude_entity == Some(entity) {
continue;
}
let key = if primary_q.get(entity).is_ok() {
WindowKey::Primary
} else if let Some(m) = managed {
WindowKey::Managed(m.name.clone())
} else {
continue;
};
let pos = get_window_position(entity, window);
let (monitor_index, monitor_scale) = existing_monitor.map_or_else(
|| {
let p = monitors.first();
(p.index, p.scale)
},
|m| (m.index, m.scale),
);
let mode: SavedWindowMode =
existing_monitor.map_or_else(|| (&window.mode).into(), |m| (&m.effective_mode).into());
let logical_position = pos.map(|p| {
let logical_x = (f64::from(p.x) / monitor_scale).round().to_i32();
let logical_y = (f64::from(p.y) / monitor_scale).round().to_i32();
(logical_x, logical_y)
});
states.insert(
key,
WindowState {
logical_position,
logical_width: window.resolution.width().to_u32(),
logical_height: window.resolution.height().to_u32(),
monitor_scale,
monitor_index,
mode,
app_name: app_name.clone(),
},
);
}
state::save_all_states(&config.path, &states);
}
fn persist_remember_all(
config: &RestoreWindowConfig,
monitors: &Monitors,
cached: &std::collections::HashMap<Entity, CachedWindowState>,
all_windows: &Query<
(
Entity,
&Window,
Option<&CurrentMonitor>,
Option<&ManagedWindow>,
),
Or<(With<PrimaryWindow>, With<ManagedWindow>)>,
>,
primary_q: &Query<(), With<PrimaryWindow>>,
) {
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 mut states = state::load_all_states(&config.path).unwrap_or_default();
for (entity, entry) in cached {
let key = if primary_q.get(*entity).is_ok() {
WindowKey::Primary
} else if let Ok((_, _, _, Some(managed))) = all_windows.get(*entity) {
WindowKey::Managed(managed.name.clone())
} else {
continue;
};
if let Some(mode) = &entry.mode {
let monitor_index = entry.monitor_index.unwrap_or(0);
let monitor_scale = monitors
.by_index(monitor_index)
.map_or(DEFAULT_SCALE_FACTOR, |m| m.scale);
let logical_position = entry.position.map(|p| {
let logical_x = (f64::from(p.x) / monitor_scale).round().to_i32();
let logical_y = (f64::from(p.y) / monitor_scale).round().to_i32();
(logical_x, logical_y)
});
states.insert(
key,
WindowState {
logical_position,
logical_width: entry.logical_width,
logical_height: entry.logical_height,
monitor_scale,
monitor_index,
mode: mode.clone(),
app_name: app_name.clone(),
},
);
}
}
state::save_all_states(&config.path, &states);
}
pub(crate) fn save_window_state(
config: Res<RestoreWindowConfig>,
monitors: Res<Monitors>,
persistence: Res<ManagedWindowPersistence>,
windows: Query<
(
Entity,
&Window,
Option<&CurrentMonitor>,
Option<&ManagedWindow>,
),
(
Or<(With<PrimaryWindow>, With<ManagedWindow>)>,
Or<(Changed<Window>, Changed<CurrentMonitor>)>,
),
>,
all_windows: Query<
(
Entity,
&Window,
Option<&CurrentMonitor>,
Option<&ManagedWindow>,
),
Or<(With<PrimaryWindow>, With<ManagedWindow>)>,
>,
primary_q: Query<(), With<PrimaryWindow>>,
mut cached: Local<std::collections::HashMap<Entity, CachedWindowState>>,
_non_send: NonSendMarker,
) {
if monitors.is_empty() {
return;
}
let mut any_changed = false;
for (window_entity, window, existing_monitor, managed) in &windows {
let key = if primary_q.get(window_entity).is_ok() {
WindowKey::Primary
} else if let Some(m) = managed {
WindowKey::Managed(m.name.clone())
} else {
continue;
};
let pos = get_window_position(window_entity, window);
let physical_w = window.resolution.physical_width();
let physical_h = window.resolution.physical_height();
let logical_w = window.resolution.width().to_u32();
let logical_h = window.resolution.height().to_u32();
let res_scale = window.resolution.scale_factor();
let (monitor_index, monitor_scale) = existing_monitor.map_or_else(
|| {
let p = monitors.first();
(p.index, p.scale)
},
|m| (m.index, m.scale),
);
let mode: SavedWindowMode =
existing_monitor.map_or_else(|| (&window.mode).into(), |m| (&m.effective_mode).into());
let entry = cached.entry(window_entity).or_default();
let position_changed = entry.position != pos;
let size_changed = entry.logical_width != logical_w || entry.logical_height != logical_h;
let mode_changed = entry.mode.as_ref() != Some(&mode);
let monitor_changed = entry.monitor_index != Some(monitor_index);
if !position_changed && !size_changed && !mode_changed && !monitor_changed {
continue;
}
debug!(
"[save_window_state] [{key}] SAVE DETAIL: pos={pos:?} physical={physical_w}x{physical_h} logical={logical_w}x{logical_h} res_scale={res_scale} monitor={monitor_index} mode={mode:?}",
);
if monitor_changed {
let prev_scale = entry
.monitor_index
.and_then(|i| monitors.by_index(i))
.map(|m| m.scale);
debug!(
"[save_window_state] [{key}] MONITOR CHANGE: {:?} (scale={:?}) -> {} (scale={})",
entry.monitor_index, prev_scale, monitor_index, monitor_scale
);
}
entry.position = pos;
entry.logical_width = logical_w;
entry.logical_height = logical_h;
entry.mode = Some(mode.clone());
entry.monitor_index = Some(monitor_index);
any_changed = true;
debug!(
"[save_window_state] [{key}] pos={pos:?} logical={logical_w}x{logical_h} physical={physical_w}x{physical_h} monitor={monitor_index} scale={monitor_scale} mode={mode:?}",
);
}
if !any_changed {
return;
}
match *persistence {
ManagedWindowPersistence::ActiveOnly => {
save_active_window_state(&config, &monitors, &all_windows, &primary_q, None);
},
ManagedWindowPersistence::RememberAll => {
persist_remember_all(&config, &monitors, &cached, &all_windows, &primary_q);
},
}
}
pub(crate) fn restore_windows(
mut scale_changed_messages: MessageReader<WindowScaleFactorChanged>,
mut windows: Query<(Entity, &mut TargetPosition, &mut Window), With<X11FrameCompensated>>,
_non_send: NonSendMarker,
platform: Res<Platform>,
) {
let scale_changed = scale_changed_messages.read().last().is_some();
for (entity, mut target, mut window) in &mut windows {
if target.settle_state.is_some() {
continue;
}
let winit_window_exists = WINIT_WINDOWS.with(|ww| ww.borrow().get_window(entity).is_some());
if !winit_window_exists {
debug!("[restore_windows] Skipping entity {entity:?}: winit window not yet created");
continue;
}
if platform.needs_managed_scale_fixup() {
let actual_scale = f64::from(window.resolution.base_scale_factor());
if (actual_scale - target.starting_scale).abs() > SCALE_FACTOR_EPSILON {
let old_strategy = target.scale_strategy;
target.starting_scale = actual_scale;
target.scale_strategy = platform.scale_strategy(actual_scale, target.target_scale);
debug!(
"[restore_windows] Corrected starting_scale for entity {entity:?}: \
strategy: {old_strategy:?} -> {:?} (actual_scale={actual_scale:.2})",
target.scale_strategy
);
}
}
if matches!(
target.scale_strategy,
MonitorScaleStrategy::HigherToLower(WindowRestoreState::NeedInitialMove)
| MonitorScaleStrategy::CompensateSizeOnly(WindowRestoreState::NeedInitialMove)
) && try_cross_dpi_initial_move(&mut target, &mut window)
{
continue;
}
match target.scale_strategy {
MonitorScaleStrategy::HigherToLower(WindowRestoreState::WaitingForScaleChange)
if scale_changed =>
{
debug!(
"[Restore] ScaleChanged received, transitioning to WindowRestoreState::ApplySize"
);
target.scale_strategy =
MonitorScaleStrategy::HigherToLower(WindowRestoreState::ApplySize);
},
MonitorScaleStrategy::CompensateSizeOnly(WindowRestoreState::WaitingForScaleChange) => {
debug!(
"[Restore] CompensateSizeOnly: transitioning to ApplySize (scale_changed={scale_changed})"
);
target.scale_strategy =
MonitorScaleStrategy::CompensateSizeOnly(WindowRestoreState::ApplySize);
},
_ => {},
}
if let Some(fs_state) = target.fullscreen_state {
match fs_state {
FullscreenRestoreState::MoveToMonitor => {
if let Some(pos) = target.position {
debug!("[restore_windows] Fullscreen MoveToMonitor: position={pos:?}");
window.position = WindowPosition::At(pos);
}
target.fullscreen_state = Some(FullscreenRestoreState::WaitForMove);
continue;
},
FullscreenRestoreState::WaitForMove => {
debug!("[restore_windows] Fullscreen WaitForMove: waiting for compositor");
target.fullscreen_state = Some(FullscreenRestoreState::ApplyMode);
continue;
},
FullscreenRestoreState::WaitForSurface => {
debug!("[restore_windows] Fullscreen WaitForSurface: waiting for GPU surface");
target.fullscreen_state = Some(FullscreenRestoreState::ApplyMode);
continue;
},
FullscreenRestoreState::ApplyMode => {
},
}
}
if matches!(
try_apply_restore(&target, &mut window, *platform),
RestoreStatus::Complete
) {
if target.settle_state.is_none() {
info!(
"[restore_windows] Restore applied, starting settle (200ms stability / 1s timeout)"
);
target.settle_state = Some(SettleState::new());
}
}
}
}
fn build_actual_snapshot(
window: &Window,
current_monitor: Option<&CurrentMonitor>,
platform: Platform,
) -> (SettleSnapshot, f64) {
let position = if platform.position_available() {
match window.position {
WindowPosition::At(p) => Some(IVec2::new(p.x, p.y)),
_ => None,
}
} else {
None
};
let size = UVec2::new(
window.resolution.physical_width(),
window.resolution.physical_height(),
);
(
SettleSnapshot {
position,
size,
mode: window.mode,
monitor: current_monitor.map_or(0, |cm| cm.monitor.index),
},
f64::from(window.resolution.scale_factor()),
)
}
fn check_settle_matches(
target: &TargetPosition,
target_position: Option<IVec2>,
target_size: UVec2,
target_mode: WindowMode,
target_monitor: usize,
actual: &SettleSnapshot,
platform: Platform,
) -> (bool, bool, bool, bool) {
let is_fullscreen = target.mode.is_fullscreen();
let position_matches = if is_fullscreen {
true
} else if platform.position_reliable_for_settle() {
target_position == actual.position
} else {
true };
let size_match = is_fullscreen || target_size == actual.size;
let mode_match = platform.modes_match(target_mode, actual.mode);
let monitor_match = target_monitor == actual.monitor;
(position_matches, size_match, mode_match, monitor_match)
}
fn detect_settle_change(
settle: &mut SettleState,
snapshot: SettleSnapshot,
key: &WindowKey,
total_elapsed_ms: f32,
total_timed_out: bool,
) -> bool {
let changed = settle.last_snapshot.as_ref() != Some(&snapshot);
if changed {
if settle.last_snapshot.is_some() {
debug!(
"[check_restore_settling] [{key}] {total_elapsed_ms:.0}ms: values changed, \
resetting stability timer"
);
}
settle.stability_timer.reset();
settle.last_snapshot = Some(snapshot);
!total_timed_out
} else {
false
}
}
fn resolve_window_key(
entity: Entity,
primary_q: &Query<(), With<PrimaryWindow>>,
managed_q: &Query<&ManagedWindow>,
) -> WindowKey {
if primary_q.get(entity).is_ok() {
WindowKey::Primary
} else if let Ok(managed) = managed_q.get(entity) {
WindowKey::Managed(managed.name.clone())
} else {
WindowKey::Primary
}
}
pub(crate) fn check_restore_settling(
mut commands: Commands,
time: Res<Time>,
mut windows: Query<
(
Entity,
&mut TargetPosition,
&Window,
Option<&CurrentMonitor>,
),
With<X11FrameCompensated>,
>,
primary_q: Query<(), With<PrimaryWindow>>,
managed_q: Query<&ManagedWindow>,
platform: Res<Platform>,
) {
for (entity, mut target, window, current_monitor) in &mut windows {
let target_mode = target.mode.to_window_mode(target.target_monitor_index);
let target_size = target.size();
let target_logical_size = target.logical_size();
let target_monitor = target.target_monitor_index;
let expected_scale = target.target_scale;
let target_position = platform
.position_available()
.then_some(target.position)
.flatten();
let key = resolve_window_key(entity, &primary_q, &managed_q);
let (current_snapshot, actual_scale) =
build_actual_snapshot(window, current_monitor, *platform);
let Some(settle) = target.settle_state.as_mut() else {
continue;
};
settle.total_timeout.tick(time.delta());
settle.stability_timer.tick(time.delta());
let total_elapsed_ms = settle.total_timeout.elapsed_secs() * 1000.0;
let stability_elapsed_ms = settle.stability_timer.elapsed_secs() * 1000.0;
let total_timed_out = settle.total_timeout.is_finished();
if detect_settle_change(
settle,
current_snapshot,
&key,
total_elapsed_ms,
total_timed_out,
) {
continue;
}
let stable = settle.stability_timer.is_finished();
let (position_matches, size_match, mode_match, monitor_match) = check_settle_matches(
&target,
target_position,
target_size,
target_mode,
target_monitor,
¤t_snapshot,
*platform,
);
let all_match = position_matches && size_match && mode_match && monitor_match;
debug!(
"[check_restore_settling] [{key}] {total_elapsed_ms:.0}ms (stable: {stability_elapsed_ms:.0}ms): \
pos={position_matches} size={size_match} mode={mode_match} monitor={monitor_match} | \
size: {target_size} vs {}, \
mode: {target_mode:?} vs {:?}, \
monitor: {target_monitor} vs {}, \
scale: {expected_scale} vs {actual_scale}",
current_snapshot.size, current_snapshot.mode, current_snapshot.monitor,
);
let settle_target = SettleTarget {
position: target_position,
size: target_size,
logical_size: target_logical_size,
mode: target_mode,
monitor: target_monitor,
scale: expected_scale,
};
if stable && all_match {
emit_settle_success(
&mut commands,
entity,
key,
&settle_target,
total_elapsed_ms,
stability_elapsed_ms,
);
} else if total_timed_out {
let settle_actual = SettleActual {
snapshot: current_snapshot,
scale: actual_scale,
logical_size: UVec2::new(
window.resolution.width().to_u32(),
window.resolution.height().to_u32(),
),
};
emit_settle_mismatch(
&mut commands,
entity,
key,
&settle_target,
&settle_actual,
total_elapsed_ms,
);
}
}
}
struct SettleActual {
snapshot: SettleSnapshot,
scale: f64,
logical_size: UVec2,
}
struct SettleTarget {
position: Option<IVec2>,
size: UVec2,
logical_size: UVec2,
mode: WindowMode,
monitor: usize,
scale: f64,
}
fn emit_settle_success(
commands: &mut Commands,
entity: Entity,
key: WindowKey,
target: &SettleTarget,
total_elapsed_ms: f32,
stability_elapsed_ms: f32,
) {
info!(
"[check_restore_settling] [{key}] Settled after {total_elapsed_ms:.0}ms \
(stable for {stability_elapsed_ms:.0}ms)"
);
commands
.entity(entity)
.trigger(|entity| WindowRestored {
entity,
window_id: key,
position: target.position,
size: target.size,
logical_size: target.logical_size,
mode: target.mode,
monitor_index: target.monitor,
})
.remove::<TargetPosition>()
.remove::<X11FrameCompensated>();
}
fn emit_settle_mismatch(
commands: &mut Commands,
entity: Entity,
key: WindowKey,
target: &SettleTarget,
actual: &SettleActual,
total_elapsed_ms: f32,
) {
warn!(
"[check_restore_settling] [{key}] Settle timeout after {total_elapsed_ms:.0}ms — \
mismatch remains: \
position: {:?} vs {:?}, \
size: {} vs {}, \
mode: {:?} vs {:?}, \
monitor: {} vs {}, \
scale: {} vs {}",
target.position,
actual.snapshot.position,
target.size,
actual.snapshot.size,
target.mode,
actual.snapshot.mode,
target.monitor,
actual.snapshot.monitor,
target.scale,
actual.scale,
);
commands
.entity(entity)
.trigger(|entity| WindowRestoreMismatch {
entity,
window_id: key,
expected_position: target.position,
actual_position: actual.snapshot.position,
expected_size: target.size,
actual_size: actual.snapshot.size,
expected_logical_size: target.logical_size,
actual_logical_size: actual.logical_size,
expected_mode: target.mode,
actual_mode: actual.snapshot.mode,
expected_monitor: target.monitor,
actual_monitor: actual.snapshot.monitor,
expected_scale: target.scale,
actual_scale: actual.scale,
})
.remove::<TargetPosition>()
.remove::<X11FrameCompensated>();
}
enum RestoreStatus {
Complete,
Waiting,
}
fn get_window_position(entity: Entity, window: &Window) -> Option<IVec2> {
#[cfg(any(
target_os = "macos",
all(target_os = "linux", feature = "workaround-winit-4443")
))]
{
let _ = window;
WINIT_WINDOWS.with(|ww| {
let ww = ww.borrow();
let winit_win = ww.get_window(entity)?;
let outer_pos = winit_win.outer_position().ok()?;
Some(IVec2::new(outer_pos.x, outer_pos.y))
})
}
#[cfg(not(any(
target_os = "macos",
all(target_os = "linux", feature = "workaround-winit-4443")
)))]
{
let _ = entity;
match window.position {
WindowPosition::At(p) => Some(p),
_ => None,
}
}
}
pub(crate) fn update_current_monitor(
mut commands: Commands,
windows: Query<
(Entity, &Window, Option<&CurrentMonitor>),
Or<(With<PrimaryWindow>, With<ManagedWindow>)>,
>,
monitors: Res<Monitors>,
_non_send: NonSendMarker,
) {
if monitors.is_empty() {
return;
}
for (entity, window, existing) in &windows {
let winit_result = winit_detect_monitor(entity, &monitors);
let position_result = if winit_result.is_none() {
position_detect_monitor(window, &monitors)
} else {
None
};
let (monitor_info, source) = match (winit_result, position_result, existing) {
(Some(info), _, _) => (info, "winit"),
(_, Some(info), _) => (info, "position"),
(_, _, Some(cm)) => (cm.monitor, "existing"),
_ => (*monitors.first(), "fallback"),
};
let effective_mode = compute_effective_mode(window, &monitor_info, &monitors);
let new_current = CurrentMonitor {
monitor: monitor_info,
effective_mode,
};
let changed = existing.is_none_or(|cm| {
cm.monitor.index != new_current.monitor.index
|| cm.effective_mode != new_current.effective_mode
});
if changed {
debug!(
"[update_current_monitor] source={} index={} scale={} effective_mode={:?}",
source, monitor_info.index, monitor_info.scale, effective_mode
);
commands.entity(entity).insert(new_current);
}
}
}
fn winit_detect_monitor(entity: Entity, monitors: &Monitors) -> Option<MonitorInfo> {
WINIT_WINDOWS.with(|ww| {
let ww = ww.borrow();
ww.get_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).copied()
})
})
})
}
fn position_detect_monitor(window: &Window, monitors: &Monitors) -> Option<MonitorInfo> {
if let WindowPosition::At(pos) = window.position {
Some(*monitors.monitor_for_window(pos, window.physical_width(), window.physical_height()))
} else {
None
}
}
fn compute_effective_mode(
window: &Window,
monitor_info: &MonitorInfo,
monitors: &Monitors,
) -> WindowMode {
if matches!(window.mode, WindowMode::Fullscreen(_, _)) {
return window.mode;
}
if monitors.is_empty() {
return window.mode;
}
let WindowPosition::At(pos) = window.position else {
return window.mode;
};
let full_width = window.physical_width() == monitor_info.size.x;
let left_aligned = pos.x == monitor_info.position.x;
let reaches_bottom = pos.y + window.physical_height().to_i32()
== monitor_info.position.y + monitor_info.size.y.to_i32();
if full_width && left_aligned && reaches_bottom {
WindowMode::BorderlessFullscreen(MonitorSelection::Index(monitor_info.index))
} else {
WindowMode::Windowed
}
}
fn apply_fullscreen_restore(target: &TargetPosition, window: &mut Window, platform: Platform) {
let monitor_index = target.target_monitor_index;
let window_mode = if platform.exclusive_fullscreen_fallback()
&& 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={:?}",
window.position, window.mode
);
window.mode = window_mode;
}
fn try_cross_dpi_initial_move(target: &mut TargetPosition, window: &mut Window) -> bool {
if target.position.is_none() {
let width = (f64::from(target.logical_width) * target.starting_scale).to_u32();
let height = (f64::from(target.logical_height) * target.starting_scale).to_u32();
debug!(
"[restore_windows] No position for cross-DPI restore, applying logical size \
{}x{} at starting_scale={} (physical {}x{}) instead of two-phase dance",
target.logical_width, target.logical_height, target.starting_scale, width, height
);
window.resolution.set_physical_resolution(width, height);
window.visible = true;
target.settle_state = Some(SettleState::new());
return true;
}
apply_initial_move(target, window);
target.scale_strategy = match target.scale_strategy {
MonitorScaleStrategy::HigherToLower(_) => {
MonitorScaleStrategy::HigherToLower(WindowRestoreState::WaitingForScaleChange)
},
_ => MonitorScaleStrategy::CompensateSizeOnly(WindowRestoreState::WaitingForScaleChange),
};
true
}
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.position = WindowPosition::At(pos);
} 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,
window: &mut Window,
platform: Platform,
) -> RestoreStatus {
if target.mode.is_fullscreen() {
debug!(
"[try_apply_restore] fullscreen: mode={:?} target_monitor={} current_physical={}x{} current_mode={:?} current_pos={:?}",
target.mode,
target.target_monitor_index,
window.physical_width(),
window.physical_height(),
window.mode,
window.position,
);
apply_fullscreen_restore(target, window, platform);
window.visible = true;
return RestoreStatus::Complete;
}
debug!(
"[Restore] target_pos={:?} target_scale={} strategy={:?}",
target.position, target.target_scale, target.scale_strategy
);
match target.scale_strategy {
MonitorScaleStrategy::ApplyUnchanged => {
apply_window_geometry(
window,
target.position(),
target.size(),
"ApplyUnchanged",
None,
);
},
MonitorScaleStrategy::CompensateSizeOnly(WindowRestoreState::ApplySize) => {
let size = target.size();
debug!(
"[try_apply_restore] size={}x{} ONLY (CompensateSizeOnly::ApplySize, position already set)",
size.x, size.y
);
window.resolution.set_physical_resolution(size.x, size.y);
},
MonitorScaleStrategy::CompensateSizeOnly(
WindowRestoreState::NeedInitialMove | WindowRestoreState::WaitingForScaleChange,
) => {
debug!(
"[Restore] CompensateSizeOnly: waiting for initial move or ScaleChanged message"
);
return RestoreStatus::Waiting;
},
MonitorScaleStrategy::LowerToHigher => {
apply_window_geometry(
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
);
window.resolution.set_physical_resolution(size.x, size.y);
},
MonitorScaleStrategy::HigherToLower(
WindowRestoreState::NeedInitialMove | WindowRestoreState::WaitingForScaleChange,
) => {
debug!("[Restore] HigherToLower: waiting for initial move or ScaleChanged message");
return RestoreStatus::Waiting;
},
}
window.visible = true;
RestoreStatus::Complete
}
#[cfg(test)]
mod tests {
use bevy::window::MonitorSelection;
use bevy::window::VideoModeSelection;
use bevy::window::WindowMode;
use bevy::window::WindowPosition;
use super::*;
fn monitor_0() -> MonitorInfo {
MonitorInfo {
index: 0,
scale: 2.0,
position: IVec2::ZERO,
size: UVec2::new(3456, 2234),
}
}
fn monitors_with(info: MonitorInfo) -> Monitors { Monitors { list: vec![info] } }
fn window_at(pos: IVec2, width: u32, height: u32) -> Window {
let mut window = Window {
position: WindowPosition::At(pos),
mode: WindowMode::Windowed,
..Default::default()
};
window.resolution.set_physical_resolution(width, height);
window
}
#[test]
fn effective_mode_fullscreen_when_window_fills_monitor() {
let mon = monitor_0();
let monitors = monitors_with(mon);
let window = window_at(mon.position, mon.size.x, mon.size.y);
let mode = compute_effective_mode(&window, &mon, &monitors);
assert_eq!(
mode,
WindowMode::BorderlessFullscreen(MonitorSelection::Index(0))
);
}
#[test]
fn effective_mode_windowed_when_window_smaller_than_monitor() {
let mon = monitor_0();
let monitors = monitors_with(mon);
let window = window_at(IVec2::new(100, 100), 1600, 1200);
let mode = compute_effective_mode(&window, &mon, &monitors);
assert_eq!(mode, WindowMode::Windowed);
}
#[test]
fn effective_mode_windowed_when_not_left_aligned() {
let mon = monitor_0();
let monitors = monitors_with(mon);
let window = window_at(IVec2::new(1, 0), mon.size.x, mon.size.y);
let mode = compute_effective_mode(&window, &mon, &monitors);
assert_eq!(mode, WindowMode::Windowed);
}
#[test]
fn effective_mode_trusts_exclusive_fullscreen() {
let mon = monitor_0();
let monitors = monitors_with(mon);
let mut window = window_at(IVec2::ZERO, 800, 600);
window.mode =
WindowMode::Fullscreen(MonitorSelection::Index(0), VideoModeSelection::Current);
let mode = compute_effective_mode(&window, &mon, &monitors);
assert!(matches!(mode, WindowMode::Fullscreen(_, _)));
}
#[test]
fn effective_mode_returns_mode_when_no_position() {
let mon = monitor_0();
let monitors = monitors_with(mon);
let mut window = Window::default();
window
.resolution
.set_physical_resolution(mon.size.x, mon.size.y);
let mode = compute_effective_mode(&window, &mon, &monitors);
assert_eq!(mode, WindowMode::Windowed);
}
#[test]
fn effective_mode_returns_mode_when_no_monitors() {
let mon = monitor_0();
let empty = Monitors { list: vec![] };
let window = window_at(IVec2::ZERO, mon.size.x, mon.size.y);
let mode = compute_effective_mode(&window, &mon, &empty);
assert_eq!(mode, WindowMode::Windowed);
}
}