#![allow(clippy::cast_possible_wrap)]
use std::collections::HashMap;
use bevy::camera::RenderTarget;
use bevy::ecs::system::NonSendMarker;
use bevy::prelude::*;
use bevy::ui::UiTargetCamera;
use bevy::window::Monitor;
use bevy::window::MonitorSelection;
use bevy::window::PrimaryWindow;
use bevy::window::VideoMode;
use bevy::window::VideoModeSelection;
use bevy::window::WindowMode;
use bevy::window::WindowPosition;
use bevy::window::WindowRef;
use bevy::window::WindowScaleFactorChanged;
use bevy::winit::WINIT_WINDOWS;
use bevy_brp_extras::BrpExtrasPlugin;
use bevy_kana::ToU32;
use bevy_window_manager::CurrentMonitor;
use bevy_window_manager::ManagedWindow;
use bevy_window_manager::ManagedWindowPersistence;
use bevy_window_manager::Monitors;
use bevy_window_manager::WindowManagerPlugin;
use bevy_window_manager::WindowRestoreMismatch;
use bevy_window_manager::WindowRestored;
const TEST_MODE_ENV_VAR: &str = "BWM_TEST_MODE";
#[derive(Event, Reflect)]
#[reflect(Event)]
struct SpawnManagedWindow;
#[derive(Event, Reflect)]
#[reflect(Event)]
struct SetBorderlessFullscreen;
#[derive(Event, Reflect)]
#[reflect(Event)]
struct SetWindowed;
#[derive(Event, Reflect)]
#[reflect(Event)]
struct SetExclusiveFullscreen;
#[derive(Event, Reflect)]
#[reflect(Event)]
struct TogglePersistence;
#[derive(Event, Reflect)]
#[reflect(Event)]
struct ClearStateAndQuit;
#[derive(Event, Reflect)]
#[reflect(Event)]
struct QuitApp;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Window Restore - Primary Window".into(),
..default()
}),
..default()
}))
.add_plugins(WindowManagerPlugin)
.add_plugins(BrpExtrasPlugin::default())
.add_observer(on_spawn_managed_window)
.add_observer(on_window_restored)
.add_observer(on_window_restore_mismatch)
.add_observer(on_secondary_window_added)
.add_observer(on_secondary_window_removed)
.add_observer(on_set_borderless_fullscreen)
.add_observer(on_set_windowed)
.add_observer(on_set_exclusive_fullscreen)
.add_observer(on_toggle_persistence)
.add_observer(on_clear_state_and_quit)
.add_observer(on_quit_app)
.insert_resource(TestMode(std::env::var(TEST_MODE_ENV_VAR).is_ok()))
.init_resource::<SelectedVideoModes>()
.init_resource::<WindowCounter>()
.init_resource::<RestoredStates>()
.init_resource::<MismatchStates>()
.init_resource::<WindowsSettledCount>()
.add_systems(Startup, setup)
.add_systems(
Update,
(
update_primary_display,
update_secondary_displays,
handle_global_input.run_if(keyboard_enabled),
handle_window_mode_input.run_if(keyboard_enabled),
debug_winit_monitor,
debug_window_changed,
debug_scale_factor_changed,
),
)
.run();
}
#[derive(Resource)]
struct TestMode(bool);
fn keyboard_enabled(test_mode: Res<TestMode>) -> bool { !test_mode.0 }
#[derive(Resource, Default)]
struct WindowCounter {
next: usize,
}
#[derive(Resource, Default)]
struct SelectedVideoModes {
indices: HashMap<usize, usize>,
last_sync: Option<(UVec2, u32)>,
}
impl SelectedVideoModes {
fn get(&self, monitor_index: usize) -> usize {
self.indices.get(&monitor_index).copied().unwrap_or(0)
}
fn set(&mut self, monitor_index: usize, index: usize) {
self.indices.insert(monitor_index, index);
}
}
#[derive(Component)]
struct PrimaryDisplay;
#[derive(Component)]
struct SecondaryDisplay(Entity);
#[derive(Resource, Debug, Clone, Reflect)]
#[reflect(Resource)]
struct WindowRestoredReceived {
position: Option<IVec2>,
size: UVec2,
mode: WindowMode,
monitor_index: usize,
}
#[derive(Resource, Debug, Clone, Reflect)]
#[reflect(Resource)]
struct WindowRestoreMismatchReceived {
expected_monitor: usize,
actual_monitor: usize,
expected_size: UVec2,
actual_size: UVec2,
expected_mode: WindowMode,
actual_mode: WindowMode,
}
#[derive(Resource, Debug, Default, Reflect)]
#[reflect(Resource)]
struct WindowsSettledCount {
count: usize,
}
#[derive(Clone)]
struct CachedMismatchState {
expected_position: Option<IVec2>,
actual_position: Option<IVec2>,
expected_size: UVec2,
actual_size: UVec2,
expected_logical_size: UVec2,
actual_logical_size: UVec2,
expected_mode: WindowMode,
actual_mode: WindowMode,
expected_monitor: usize,
actual_monitor: usize,
expected_scale: f64,
actual_scale: f64,
}
#[derive(Resource, Default)]
struct MismatchStates {
states: HashMap<Entity, CachedMismatchState>,
}
#[derive(Resource, Default)]
struct RestoredStates {
states: HashMap<Entity, CachedRestoredState>,
}
struct CachedRestoredState {
position: Option<IVec2>,
width: u32,
height: u32,
logical_width: u32,
logical_height: u32,
monitor_index: usize,
mode: WindowMode,
}
const MARGIN: Val = Val::Px(20.0);
const FONT_SIZE: f32 = 14.0;
const SECONDARY_WINDOW_WIDTH: u32 = 600;
const SECONDARY_WINDOW_HEIGHT: u32 = 400;
const MISMATCH_COLOR: Color = Color::linear_rgb(1.0, 0.3, 0.3);
const MISMATCH_WARN_COLOR: Color = Color::linear_rgb(1.0, 0.7, 0.2);
const DEFAULT_COLOR: Color = Color::WHITE;
const LABEL_WIDTH: usize = 18;
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.spawn((
Text::new(""),
TextFont {
font_size: FONT_SIZE,
..default()
},
Node {
position_type: PositionType::Absolute,
top: MARGIN,
left: MARGIN,
..default()
},
PrimaryDisplay,
));
}
fn on_spawn_managed_window(
_trigger: On<SpawnManagedWindow>,
mut commands: Commands,
mut counter: ResMut<WindowCounter>,
) {
counter.next += 1;
let name = format!("window-{}", counter.next);
let title = format!("Managed: {name}");
commands.spawn((
Window {
title,
resolution: bevy::window::WindowResolution::new(
SECONDARY_WINDOW_WIDTH,
SECONDARY_WINDOW_HEIGHT,
),
..default()
},
ManagedWindow {
window_name: name.clone(),
},
));
info!("[restore_window] Spawned managed window \"{name}\"");
}
fn on_secondary_window_added(
add: On<Add, ManagedWindow>,
mut commands: Commands,
primary_q: Query<(), With<PrimaryWindow>>,
) {
let entity = add.entity;
if primary_q.get(entity).is_ok() {
return;
}
let camera = commands
.spawn((Camera2d, RenderTarget::Window(WindowRef::Entity(entity))))
.id();
commands.spawn((
Text::new(""),
TextFont {
font_size: FONT_SIZE,
..default()
},
Node {
position_type: PositionType::Absolute,
top: MARGIN,
left: MARGIN,
..default()
},
UiTargetCamera(camera),
SecondaryDisplay(entity),
));
}
fn on_secondary_window_removed(
remove: On<Remove, ManagedWindow>,
mut commands: Commands,
displays: Query<(Entity, &SecondaryDisplay)>,
) {
let entity = remove.entity;
for (display_entity, display) in &displays {
if display.0 == entity {
commands.entity(display_entity).despawn();
}
}
}
struct CurrentValues {
position: String,
size_phys: String,
size_log: String,
scale: String,
monitor: String,
mode: String,
}
fn build_comparison_spans(
cb: &mut ChildSpawnerCommands,
restored_state: Option<&CachedRestoredState>,
mismatch_state: Option<&CachedMismatchState>,
window: &Window,
monitor: &CurrentMonitor,
font: &TextFont,
) {
let effective_mode = monitor.effective_mode;
let scale = window.resolution.scale_factor();
let current = CurrentValues {
position: match window.position {
WindowPosition::At(pos) => format!("({}, {})", pos.x, pos.y),
_ => "Automatic".to_string(),
},
size_phys: format!("{}x{}", window.physical_width(), window.physical_height()),
size_log: format!(
"{}x{}",
window.resolution.width().to_u32(),
window.resolution.height().to_u32()
),
scale: format!("{scale}"),
monitor: format!("{}", monitor.index),
mode: format!("{effective_mode:?}"),
};
if let Some(state) = restored_state {
build_restored_spans(cb, state, mismatch_state, ¤t, font);
} else {
build_current_only_spans(cb, ¤t, font);
}
add_span(
cb,
font,
&format!("\nEffective Mode: {effective_mode:?}\n"),
DEFAULT_COLOR,
);
}
#[expect(
clippy::too_many_lines,
reason = "UI builder — splitting would scatter tightly-coupled formatting logic"
)]
fn build_restored_spans(
cb: &mut ChildSpawnerCommands,
state: &CachedRestoredState,
mismatch_state: Option<&CachedMismatchState>,
current: &CurrentValues,
font: &TextFont,
) {
let file_pos = state
.position
.map_or_else(|| "None".to_string(), |p| format!("({}, {})", p.x, p.y));
let file_size_phys = format!("{}x{}", state.width, state.height);
let file_size_log = format!("{}x{}", state.logical_width, state.logical_height);
let file_monitor = format!("{}", state.monitor_index);
let file_mode = format!("{:?}", state.mode);
let col1_width = [
file_pos.len(),
file_size_phys.len(),
file_monitor.len(),
file_mode.len(),
]
.into_iter()
.max()
.unwrap_or(0)
+ 2;
let col1_width = col1_width.max(16);
if mismatch_state.is_some() {
let header = format!(
"{:LABEL_WIDTH$}{:<col1_width$}{:<col1_width$}{:<col1_width$}{}\n",
"", "Restored", "Current", "Expected", "Actual"
);
add_span(cb, font, &header, DEFAULT_COLOR);
} else {
let header = format!(
"{:LABEL_WIDTH$}{:<col1_width$}{}\n",
"", "Restored", "Current"
);
add_span(cb, font, &header, DEFAULT_COLOR);
}
let mm = mismatch_state.map(|m| {
let exp = m
.expected_position
.map_or_else(|| "None".to_string(), |p| format!("({}, {})", p.x, p.y));
let act = m
.actual_position
.map_or_else(|| "None".to_string(), |p| format!("({}, {})", p.x, p.y));
(exp, act)
});
add_row(
cb,
font,
"Position:",
&file_pos,
¤t.position,
mm.as_ref(),
col1_width,
);
let mm = mismatch_state.map(|m| {
(
format!("{}x{}", m.expected_size.x, m.expected_size.y),
format!("{}x{}", m.actual_size.x, m.actual_size.y),
)
});
add_row(
cb,
font,
"Size (physical):",
&file_size_phys,
¤t.size_phys,
mm.as_ref(),
col1_width,
);
let mm = mismatch_state.map(|m| {
(
format!(
"{}x{}",
m.expected_logical_size.x, m.expected_logical_size.y
),
format!("{}x{}", m.actual_logical_size.x, m.actual_logical_size.y),
)
});
add_row(
cb,
font,
"Size (logical):",
&file_size_log,
¤t.size_log,
mm.as_ref(),
col1_width,
);
if let Some(m) = mismatch_state {
let exp_scale = format!("{}", m.expected_scale);
let act_scale = format!("{}", m.actual_scale);
add_comparison_row_5(
cb,
font,
"Scale:",
"",
¤t.scale,
&exp_scale,
&act_scale,
col1_width,
);
} else {
add_span(
cb,
font,
&format!(
"{:<LABEL_WIDTH$}{:<col1_width$}{}\n",
"Scale:", "", current.scale
),
DEFAULT_COLOR,
);
}
let mm = mismatch_state.map(|m| {
(
format!("{}", m.expected_monitor),
format!("{}", m.actual_monitor),
)
});
add_row(
cb,
font,
"Monitor:",
&file_monitor,
¤t.monitor,
mm.as_ref(),
col1_width,
);
let mm = mismatch_state.map(|m| {
(
format!("{:?}", m.expected_mode),
format!("{:?}", m.actual_mode),
)
});
add_row(
cb,
font,
"Mode:",
&file_mode,
¤t.mode,
mm.as_ref(),
col1_width,
);
}
fn build_current_only_spans(
cb: &mut ChildSpawnerCommands,
current: &CurrentValues,
font: &TextFont,
) {
add_span(cb, font, "State: No restore data\n\n", MISMATCH_COLOR);
add_span(
cb,
font,
&format!("{:<LABEL_WIDTH$}{}\n", "Position:", current.position),
DEFAULT_COLOR,
);
add_span(
cb,
font,
&format!(
"{:<LABEL_WIDTH$}{}\n",
"Size (physical):", current.size_phys
),
DEFAULT_COLOR,
);
add_span(
cb,
font,
&format!("{:<LABEL_WIDTH$}{}\n", "Size (logical):", current.size_log),
DEFAULT_COLOR,
);
add_span(
cb,
font,
&format!("{:<LABEL_WIDTH$}{}\n", "Scale:", current.scale),
DEFAULT_COLOR,
);
add_span(
cb,
font,
&format!("{:<LABEL_WIDTH$}{}\n", "Monitor:", current.monitor),
DEFAULT_COLOR,
);
add_span(
cb,
font,
&format!("{:<LABEL_WIDTH$}{}\n", "Mode:", current.mode),
DEFAULT_COLOR,
);
}
fn add_row(
cb: &mut ChildSpawnerCommands,
font: &TextFont,
label: &str,
file_val: &str,
current_val: &str,
mismatch: Option<&(String, String)>,
col_width: usize,
) {
if let Some((expected, actual)) = mismatch {
add_comparison_row_5(
cb,
font,
label,
file_val,
current_val,
expected,
actual,
col_width,
);
} else {
add_comparison_row(cb, font, label, file_val, current_val, col_width);
}
}
fn add_comparison_row(
cb: &mut ChildSpawnerCommands,
font: &TextFont,
label: &str,
file_val: &str,
current_val: &str,
col_width: usize,
) {
let color = if file_val == current_val {
DEFAULT_COLOR
} else {
MISMATCH_COLOR
};
add_span(
cb,
font,
&format!("{label:<LABEL_WIDTH$}{file_val:<col_width$}"),
DEFAULT_COLOR,
);
add_span(cb, font, &format!("{current_val}\n"), color);
}
fn add_comparison_row_5(
cb: &mut ChildSpawnerCommands,
font: &TextFont,
label: &str,
file_val: &str,
current_val: &str,
expected_val: &str,
actual_val: &str,
col_width: usize,
) {
let current_color = if file_val == current_val {
DEFAULT_COLOR
} else {
MISMATCH_COLOR
};
let mismatch_color = if expected_val == actual_val {
DEFAULT_COLOR
} else {
MISMATCH_WARN_COLOR
};
add_span(
cb,
font,
&format!("{label:<LABEL_WIDTH$}{file_val:<col_width$}"),
DEFAULT_COLOR,
);
add_span(
cb,
font,
&format!("{current_val:<col_width$}"),
current_color,
);
add_span(
cb,
font,
&format!("{expected_val:<col_width$}"),
DEFAULT_COLOR,
);
add_span(cb, font, &format!("{actual_val}\n"), mismatch_color);
}
fn add_span(cb: &mut ChildSpawnerCommands, font: &TextFont, text: &str, color: Color) {
cb.spawn((TextSpan(text.to_string()), font.clone(), TextColor(color)));
}
#[expect(
clippy::too_many_arguments,
reason = "Bevy system — each param is a distinct system resource"
)]
fn update_primary_display(
primary_display: Single<Entity, With<PrimaryDisplay>>,
window_query: Single<(Entity, &Window, &CurrentMonitor), With<PrimaryWindow>>,
monitors_res: Res<Monitors>,
bevy_monitors: Query<(Entity, &Monitor)>,
mut selected: ResMut<SelectedVideoModes>,
persistence: Res<ManagedWindowPersistence>,
managed_q: Query<(&Window, &ManagedWindow, Option<&CurrentMonitor>)>,
restored_states: Res<RestoredStates>,
mismatch_states: Res<MismatchStates>,
mut commands: Commands,
) {
let display_entity = *primary_display;
let (window_entity, window, monitor) = *window_query;
let restored_state = restored_states.states.get(&window_entity);
let mismatch_state = mismatch_states.states.get(&window_entity);
let (video_modes, refresh_rate) = get_video_modes_for_monitor(&bevy_monitors, monitor);
let refresh_display = format_refresh_rate(window, refresh_rate);
let active_mode_idx = find_active_video_mode_index(window, &video_modes);
sync_selected_to_active(window, monitor, active_mode_idx, &mut selected);
let selected_idx = selected.get(monitor.index);
let video_modes_display =
build_video_modes_display(&video_modes, selected_idx, active_mode_idx);
let font = TextFont {
font_size: FONT_SIZE,
..default()
};
commands.entity(display_entity).despawn_children();
commands.entity(display_entity).with_children(|cb| {
let monitor_row = format_monitor_row(monitor, &refresh_display);
add_span(cb, &font, &format!("{monitor_row}\n\n"), DEFAULT_COLOR);
build_comparison_spans(cb, restored_state, mismatch_state, window, monitor, &font);
add_span(
cb,
&font,
&format!("\nVideo Modes (Up/Down to select):\n{video_modes_display}\n"),
DEFAULT_COLOR,
);
add_span(
cb,
&font,
&format!(
"\nControls:\n\
[Enter] Exclusive Fullscreen\n\
[B] Borderless Fullscreen\n\
[W] Windowed\n\
[Space] Spawn managed window\n\
[P] Toggle persistence ({persistence:?})\n\
[Ctrl+Shift+Backspace] Clear state and quit\n\
[Q] Quit\n"
),
DEFAULT_COLOR,
);
let mut managed_lines = Vec::new();
for (mw, managed, current_monitor) in &managed_q {
let mon = current_monitor.map_or_else(|| *monitors_res.first(), |cm| cm.monitor);
let pos = match mw.position {
WindowPosition::At(p) => format!("({}, {})", p.x, p.y),
_ => "Automatic".to_string(),
};
managed_lines.push(format!(
" {}: pos={pos} phys={}x{} log={}x{} scale={} monitor={}\n",
managed.window_name,
mw.physical_width(),
mw.physical_height(),
mw.resolution.width().to_u32(),
mw.resolution.height().to_u32(),
mw.resolution.scale_factor(),
mon.index,
));
}
let managed_header = "\nManaged Windows:\n";
add_span(cb, &font, managed_header, DEFAULT_COLOR);
if managed_lines.is_empty() {
add_span(cb, &font, " (none)\n", DEFAULT_COLOR);
} else {
for line in &managed_lines {
add_span(cb, &font, line, DEFAULT_COLOR);
}
}
});
}
#[expect(
clippy::too_many_arguments,
reason = "Bevy system — each param is a distinct system resource"
)]
fn update_secondary_displays(
mut displays: Query<(Entity, &SecondaryDisplay)>,
windows: Query<(&Window, Option<&CurrentMonitor>)>,
managed_q: Query<&ManagedWindow>,
monitors_res: Res<Monitors>,
bevy_monitors: Query<(Entity, &Monitor)>,
mut selected: ResMut<SelectedVideoModes>,
restored_states: Res<RestoredStates>,
mismatch_states: Res<MismatchStates>,
mut commands: Commands,
) {
for (display_entity, display) in &mut displays {
let Ok((window, current_monitor)) = windows.get(display.0) else {
continue;
};
let monitor_info = current_monitor.copied().unwrap_or_else(|| CurrentMonitor {
monitor: *monitors_res.first(),
effective_mode: window.mode,
});
let name = managed_q
.get(display.0)
.map_or("unknown", |m| &m.window_name);
let restored_state = restored_states.states.get(&display.0);
let mismatch_state = mismatch_states.states.get(&display.0);
let (video_modes, refresh_rate) =
get_video_modes_for_monitor(&bevy_monitors, &monitor_info);
let refresh_display = format_refresh_rate(window, refresh_rate);
let active_mode_idx = find_active_video_mode_index(window, &video_modes);
sync_selected_to_active(window, &monitor_info, active_mode_idx, &mut selected);
let selected_idx = selected.get(monitor_info.index);
let video_modes_display =
build_video_modes_display(&video_modes, selected_idx, active_mode_idx);
let font = TextFont {
font_size: FONT_SIZE,
..default()
};
commands.entity(display_entity).despawn_children();
commands.entity(display_entity).with_children(|cb| {
let monitor_row = format_monitor_row(&monitor_info, &refresh_display);
add_span(
cb,
&font,
&format!("Window: {name}\n{monitor_row}\n\n"),
DEFAULT_COLOR,
);
build_comparison_spans(
cb,
restored_state,
mismatch_state,
window,
&monitor_info,
&font,
);
add_span(
cb,
&font,
&format!("\nVideo Modes (Up/Down to select):\n{video_modes_display}\n"),
DEFAULT_COLOR,
);
add_span(
cb,
&font,
"\nControls:\n\
[Enter] Exclusive Fullscreen\n\
[B] Borderless Fullscreen\n\
[W] Windowed\n\
[Space] Spawn managed window\n\
[P] Toggle persistence\n\
[Ctrl+Shift+Backspace] Clear state and quit\n\
[Q] Quit\n",
DEFAULT_COLOR,
);
});
}
}
fn handle_global_input(
keys: Res<ButtonInput<KeyCode>>,
windows: Query<&Window>,
mut commands: Commands,
) {
if !windows.iter().any(|w| w.focused) {
return;
}
if keys.just_pressed(KeyCode::Space) {
commands.trigger(SpawnManagedWindow);
}
if keys.just_pressed(KeyCode::KeyP) {
commands.trigger(TogglePersistence);
}
if keys.just_pressed(KeyCode::Backspace)
&& keys.pressed(KeyCode::ShiftLeft)
&& keys.pressed(KeyCode::ControlLeft)
{
commands.trigger(ClearStateAndQuit);
}
if keys.just_pressed(KeyCode::KeyQ) {
commands.trigger(QuitApp);
}
}
fn despawn_managed_and_exit(
managed_entities: &Query<Entity, With<ManagedWindow>>,
commands: &mut Commands,
app_exit: &mut MessageWriter<AppExit>,
) {
for entity in managed_entities.iter() {
commands.entity(entity).despawn();
}
app_exit.write(AppExit::Success);
}
fn get_state_file_path() -> Option<std::path::PathBuf> {
let exe_name = std::env::current_exe()
.ok()?
.file_stem()?
.to_str()?
.to_string();
dirs::config_dir().map(|d| d.join(exe_name).join("windows.ron"))
}
fn handle_window_mode_input(
keys: Res<ButtonInput<KeyCode>>,
mut windows: Query<(Entity, &mut Window, Option<&CurrentMonitor>)>,
monitors_res: Res<Monitors>,
bevy_monitors: Query<(Entity, &Monitor)>,
mut selected: ResMut<SelectedVideoModes>,
restored_states: Res<RestoredStates>,
mut commands: Commands,
) {
let Some((entity, mut window, current_monitor)) =
windows.iter_mut().find(|(_, w, _)| w.focused)
else {
return;
};
let monitor = current_monitor.copied().unwrap_or_else(|| CurrentMonitor {
monitor: *monitors_res.first(),
effective_mode: window.mode,
});
let is_fullscreen = !matches!(window.mode, WindowMode::Windowed);
let restore_complete = restored_states.states.contains_key(&entity);
#[allow(clippy::suspicious_operation_groupings)]
if !is_fullscreen && restore_complete && window.mode != monitor.effective_mode {
window.mode = monitor.effective_mode;
}
let video_modes: Vec<VideoMode> = bevy_monitors
.iter()
.find(|(_, m)| m.physical_position == monitor.position)
.map(|(_, m)| m.video_modes.clone())
.unwrap_or_default();
let current_idx = selected.get(monitor.index);
if keys.just_pressed(KeyCode::ArrowUp) && current_idx > 0 {
selected.set(monitor.index, current_idx - 1);
}
if keys.just_pressed(KeyCode::ArrowDown) && current_idx < video_modes.len().saturating_sub(1) {
selected.set(monitor.index, current_idx + 1);
}
if keys.just_pressed(KeyCode::Enter) {
commands.trigger(SetExclusiveFullscreen);
}
if keys.just_pressed(KeyCode::KeyB) {
commands.trigger(SetBorderlessFullscreen);
}
if keys.just_pressed(KeyCode::KeyW) {
commands.trigger(SetWindowed);
}
}
fn get_video_modes_for_monitor<'a>(
bevy_monitors: &'a Query<(Entity, &Monitor)>,
monitor: &CurrentMonitor,
) -> (Vec<&'a VideoMode>, Option<u32>) {
bevy_monitors
.iter()
.find(|(_, m)| m.physical_position == monitor.position)
.map(|(_, m)| {
(
m.video_modes.iter().collect(),
m.refresh_rate_millihertz.map(|r| r / 1000),
)
})
.unwrap_or_default()
}
fn format_refresh_rate(window: &Window, monitor_refresh: Option<u32>) -> String {
let active_refresh = match &window.mode {
WindowMode::Fullscreen(_, VideoModeSelection::Specific(mode)) => {
Some(mode.refresh_rate_millihertz / 1000)
},
_ => monitor_refresh,
};
active_refresh.map_or_else(|| "N/A".into(), |hz| format!("{hz}Hz"))
}
fn find_active_video_mode_index(window: &Window, video_modes: &[&VideoMode]) -> Option<usize> {
match &window.mode {
WindowMode::Fullscreen(_, VideoModeSelection::Specific(active)) => {
video_modes.iter().position(|m| {
m.physical_size == active.physical_size
&& m.refresh_rate_millihertz == active.refresh_rate_millihertz
})
},
_ => None,
}
}
fn sync_selected_to_active(
window: &Window,
monitor: &CurrentMonitor,
active_mode_idx: Option<usize>,
selected: &mut SelectedVideoModes,
) {
if let WindowMode::Fullscreen(_, VideoModeSelection::Specific(active)) = &window.mode {
let current_mode = (active.physical_size, active.refresh_rate_millihertz);
if selected.last_sync != Some(current_mode)
&& let Some(active_idx) = active_mode_idx
{
selected.set(monitor.index, active_idx);
selected.last_sync = Some(current_mode);
}
} else {
selected.last_sync = None;
}
}
#[cfg_attr(not(target_os = "linux"), allow(clippy::missing_const_for_fn))]
fn platform_suffix() -> &'static str {
#[cfg(target_os = "linux")]
{
if std::env::var("WAYLAND_DISPLAY")
.map(|v| !v.is_empty())
.unwrap_or(false)
{
" (Wayland)"
} else {
" (X11)"
}
}
#[cfg(not(target_os = "linux"))]
{
""
}
}
fn format_monitor_row(monitor: &CurrentMonitor, refresh_display: &str) -> String {
let primary_marker = if monitor.index == 0 {
" Primary Monitor -"
} else {
" -"
};
format!(
"Monitor: {}{primary_marker} Scale: {} - Refresh Rate: {refresh_display}{}",
monitor.index,
monitor.scale,
platform_suffix()
)
}
fn build_video_modes_display(
video_modes: &[&VideoMode],
selected_idx: usize,
active_mode_idx: Option<usize>,
) -> String {
if video_modes.is_empty() {
return " (no video modes available)".into();
}
let selected_idx = selected_idx.min(video_modes.len().saturating_sub(1));
let len = video_modes.len();
let start = if len <= 5 {
0
} else {
let center_target = active_mode_idx.unwrap_or(selected_idx);
let ideal_start = center_target.saturating_sub(2);
let ideal_end = ideal_start + 5;
if selected_idx < ideal_start {
selected_idx.saturating_sub(2)
} else if selected_idx >= ideal_end {
(selected_idx + 3).saturating_sub(5)
} else {
ideal_start
}
.min(len.saturating_sub(5))
};
let end = (start + 5).min(len);
video_modes[start..end]
.iter()
.enumerate()
.map(|(i, mode)| {
let actual_idx = start + i;
let left_marker = if actual_idx == selected_idx { ">" } else { " " };
let right_marker = if Some(actual_idx) == active_mode_idx {
" <- active"
} else {
""
};
format!(
" {left_marker} {}x{} @ {}Hz{right_marker}",
mode.physical_size.x,
mode.physical_size.y,
mode.refresh_rate_millihertz / 1000
)
})
.collect::<Vec<_>>()
.join("\n")
}
fn debug_winit_monitor(
window: Single<Entity, With<PrimaryWindow>>,
monitors: Res<Monitors>,
mut cached_monitor: Local<Option<usize>>,
_non_send: NonSendMarker,
) {
let window_entity = *window;
let winit_monitor_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_monitor != winit_monitor_index {
info!(
"[debug_winit_monitor] Monitor changed: {:?} -> {:?}",
*cached_monitor, winit_monitor_index
);
*cached_monitor = winit_monitor_index;
}
}
#[derive(Default)]
struct CachedWindowDebug {
position: Option<WindowPosition>,
width: u32,
height: u32,
mode: Option<WindowMode>,
focused: bool,
}
fn debug_window_changed(
window: Single<&Window, (With<PrimaryWindow>, Changed<Window>)>,
mut cached: Local<CachedWindowDebug>,
) {
let w = *window;
let position_changed = cached.position.as_ref() != Some(&w.position);
let size_changed = cached.width != w.physical_width() || cached.height != w.physical_height();
let mode_changed = cached.mode.as_ref() != Some(&w.mode);
let focused_changed = cached.focused != w.focused;
let mut changes = Vec::new();
if position_changed {
changes.push(format!(
"position: {:?} -> {:?}",
cached.position, w.position
));
}
if size_changed {
changes.push(format!(
"size: {}x{} -> {}x{}",
cached.width,
cached.height,
w.physical_width(),
w.physical_height()
));
}
if mode_changed {
changes.push(format!("mode: {:?} -> {:?}", cached.mode, w.mode));
}
if focused_changed {
changes.push(format!("focused: {} -> {}", cached.focused, w.focused));
}
if !changes.is_empty() {
info!("[debug_window_changed] {}", changes.join(", "));
}
cached.position = Some(w.position);
cached.width = w.physical_width();
cached.height = w.physical_height();
cached.mode = Some(w.mode);
cached.focused = w.focused;
}
fn debug_scale_factor_changed(mut messages: MessageReader<WindowScaleFactorChanged>) {
for msg in messages.read() {
info!(
"[debug_scale_factor_changed] WindowScaleFactorChanged received: scale_factor={}",
msg.scale_factor
);
}
}
fn on_set_borderless_fullscreen(
_trigger: On<SetBorderlessFullscreen>,
mut windows: Query<(&mut Window, Option<&CurrentMonitor>)>,
monitors_res: Res<Monitors>,
) {
let Some((mut window, current_monitor)) = windows.iter_mut().find(|(w, _)| w.focused) else {
return;
};
let monitor = current_monitor.copied().unwrap_or_else(|| CurrentMonitor {
monitor: *monitors_res.first(),
effective_mode: window.mode,
});
window.mode = WindowMode::BorderlessFullscreen(MonitorSelection::Index(monitor.monitor.index));
}
fn on_set_windowed(_trigger: On<SetWindowed>, mut windows: Query<&mut Window>) {
let Some(mut window) = windows.iter_mut().find(|w| w.focused) else {
return;
};
window.mode = WindowMode::Windowed;
}
fn on_set_exclusive_fullscreen(
_trigger: On<SetExclusiveFullscreen>,
mut windows: Query<(&mut Window, Option<&CurrentMonitor>)>,
monitors_res: Res<Monitors>,
bevy_monitors: Query<(Entity, &Monitor)>,
selected: Res<SelectedVideoModes>,
) {
let Some((mut window, current_monitor)) = windows.iter_mut().find(|(w, _)| w.focused) else {
return;
};
let monitor = current_monitor.copied().unwrap_or_else(|| CurrentMonitor {
monitor: *monitors_res.first(),
effective_mode: window.mode,
});
let video_modes: Vec<VideoMode> = bevy_monitors
.iter()
.find(|(_, m)| m.physical_position == monitor.monitor.position)
.map(|(_, m)| m.video_modes.clone())
.unwrap_or_default();
let selected_idx = selected
.get(monitor.monitor.index)
.min(video_modes.len().saturating_sub(1));
let video_mode_selection = video_modes
.get(selected_idx)
.map_or(VideoModeSelection::Current, |mode| {
VideoModeSelection::Specific(*mode)
});
window.mode = WindowMode::Fullscreen(
MonitorSelection::Index(monitor.monitor.index),
video_mode_selection,
);
}
fn on_toggle_persistence(
_trigger: On<TogglePersistence>,
mut persistence: ResMut<ManagedWindowPersistence>,
) {
*persistence = match *persistence {
ManagedWindowPersistence::RememberAll => ManagedWindowPersistence::ActiveOnly,
ManagedWindowPersistence::ActiveOnly => ManagedWindowPersistence::RememberAll,
};
info!("[restore_window] Persistence mode: {:?}", *persistence);
}
fn on_clear_state_and_quit(
_trigger: On<ClearStateAndQuit>,
managed_entities: Query<Entity, With<ManagedWindow>>,
mut commands: Commands,
mut app_exit: MessageWriter<AppExit>,
) {
if let Some(state_path) = get_state_file_path() {
if let Err(e) = std::fs::remove_file(&state_path) {
warn!("[restore_window] Failed to remove state file: {e}");
} else {
info!("[restore_window] Cleared state file: {state_path:?}");
}
}
despawn_managed_and_exit(&managed_entities, &mut commands, &mut app_exit);
}
fn on_quit_app(
_trigger: On<QuitApp>,
managed_entities: Query<Entity, With<ManagedWindow>>,
mut commands: Commands,
mut app_exit: MessageWriter<AppExit>,
) {
despawn_managed_and_exit(&managed_entities, &mut commands, &mut app_exit);
}
fn on_window_restored(
trigger: On<WindowRestored>,
mut commands: Commands,
mut restored_states: ResMut<RestoredStates>,
mut settled_count: ResMut<WindowsSettledCount>,
) {
let event = trigger.event();
info!(
"[on_window_restored] Restore complete: window_id={} entity={:?} position={:?} physical={} logical={} mode={:?} monitor={}",
event.window_id,
event.entity,
event.position,
event.size,
event.logical_size,
event.mode,
event.monitor_index
);
restored_states.states.insert(
event.entity,
CachedRestoredState {
position: event.position,
width: event.size.x,
height: event.size.y,
logical_width: event.logical_size.x,
logical_height: event.logical_size.y,
monitor_index: event.monitor_index,
mode: event.mode,
},
);
commands.insert_resource(WindowRestoredReceived {
position: event.position,
size: event.size,
mode: event.mode,
monitor_index: event.monitor_index,
});
settled_count.count += 1;
}
fn on_window_restore_mismatch(
trigger: On<WindowRestoreMismatch>,
mut commands: Commands,
mut restored_states: ResMut<RestoredStates>,
mut mismatch_states: ResMut<MismatchStates>,
mut settled_count: ResMut<WindowsSettledCount>,
) {
let event = trigger.event();
warn!(
"[on_window_restore_mismatch] window_id={} entity={:?} \
monitor: {} vs {}, size: {} vs {}, mode: {:?} vs {:?}",
event.window_id,
event.entity,
event.expected_monitor,
event.actual_monitor,
event.expected_size,
event.actual_size,
event.expected_mode,
event.actual_mode,
);
restored_states.states.insert(
event.entity,
CachedRestoredState {
position: event.expected_position,
width: event.expected_size.x,
height: event.expected_size.y,
logical_width: event.expected_logical_size.x,
logical_height: event.expected_logical_size.y,
monitor_index: event.expected_monitor,
mode: event.expected_mode,
},
);
mismatch_states.states.insert(
event.entity,
CachedMismatchState {
expected_position: event.expected_position,
actual_position: event.actual_position,
expected_size: event.expected_size,
actual_size: event.actual_size,
expected_logical_size: event.expected_logical_size,
actual_logical_size: event.actual_logical_size,
expected_mode: event.expected_mode,
actual_mode: event.actual_mode,
expected_monitor: event.expected_monitor,
actual_monitor: event.actual_monitor,
expected_scale: event.expected_scale,
actual_scale: event.actual_scale,
},
);
commands.insert_resource(WindowRestoreMismatchReceived {
expected_monitor: event.expected_monitor,
actual_monitor: event.actual_monitor,
expected_size: event.expected_size,
actual_size: event.actual_size,
expected_mode: event.expected_mode,
actual_mode: event.actual_mode,
});
settled_count.count += 1;
}