diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 166a38c28..59ab42222 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1781,6 +1781,16 @@ fn process_viewport_command( ViewportCommand::Fullscreen(v) => { window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None))); } + ViewportCommand::SetMonitor(idx) => { + if let Some(monitor) = window.available_monitors().nth(idx) { + window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(Some(monitor)))); + } else { + log::warn!( + "ViewportCommand::SetMonitor({idx}): index out of range ({} monitors available)", + window.available_monitors().count() + ); + } + } ViewportCommand::Decorations(v) => { window.set_decorations(v); #[cfg(target_os = "windows")] @@ -1886,7 +1896,24 @@ pub fn create_window( ) -> Result { profiling::function_scope!(); - let window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone()); + let mut window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone()); + + // Resolve target monitor index → MonitorHandle, so the window is created + // directly in borderless fullscreen on the requested output. This is the + // only reliable way to target a specific monitor under Wayland, and also + // avoids the Mutter race where OuterPosition is ignored pre-mapping. + if let Some(idx) = viewport_builder.monitor { + if let Some(monitor) = event_loop.available_monitors().nth(idx) { + window_attributes = window_attributes + .with_fullscreen(Some(winit::window::Fullscreen::Borderless(Some(monitor)))); + } else { + log::warn!( + "ViewportBuilder::with_monitor({idx}): index out of range ({} monitors available)", + event_loop.available_monitors().count() + ); + } + } + let window = event_loop.create_window(window_attributes)?; apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder); Ok(window) @@ -1938,6 +1965,7 @@ pub fn create_winit_window_attributes( mouse_passthrough: _, // handled in `apply_viewport_builder_to_window` clamp_size_to_monitor_size: _, // Handled in `viewport_builder` in `epi_integration.rs` + monitor: _, // Handled in `create_window` (needs ActiveEventLoop for monitor handle) } = viewport_builder; let mut window_attributes = winit::window::WindowAttributes::default() diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index a94f1f637..1b1e64fe1 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -333,6 +333,19 @@ pub struct ViewportBuilder { // X11 pub window_type: Option, pub override_redirect: Option, + + /// Target monitor index for borderless fullscreen. + /// + /// When set, the window is placed in borderless fullscreen on the monitor at + /// the given index in `available_monitors()` order (same order returned by + /// winit). Works on Windows, macOS, and Linux (X11 + Wayland). + /// + /// If the index is out of range, it is ignored and a warning is logged. + /// + /// Takes precedence over [`Self::with_position`] / [`Self::with_fullscreen`] + /// for monitor selection: if both are set, the window will be fullscreen on + /// the chosen monitor. + pub monitor: Option, } impl ViewportBuilder { @@ -680,6 +693,20 @@ impl ViewportBuilder { self } + /// Place the window in borderless fullscreen on the monitor at `index`. + /// + /// The index refers to the order returned by winit's `available_monitors()`. + /// Works cross-platform (Windows, macOS, Linux X11 + Wayland). On Wayland + /// this is the only reliable way to target a specific output, since + /// absolute window positions are not exposed. + /// + /// If the index is out of range, the flag is ignored at window creation time. + #[inline] + pub fn with_monitor(mut self, index: usize) -> Self { + self.monitor = Some(index); + self + } + /// Update this `ViewportBuilder` with a delta, /// returning a list of commands and a bool indicating if the window needs to be recreated. #[must_use] @@ -717,6 +744,7 @@ impl ViewportBuilder { taskbar: new_taskbar, window_type: new_window_type, override_redirect: new_override_redirect, + monitor: new_monitor, } = new_vp_builder; let mut commands = Vec::new(); @@ -919,6 +947,13 @@ impl ViewportBuilder { recreate_window = true; } + if let Some(new_monitor) = new_monitor + && Some(new_monitor) != self.monitor + { + self.monitor = Some(new_monitor); + commands.push(ViewportCommand::SetMonitor(new_monitor)); + } + (commands, recreate_window) } } @@ -1105,6 +1140,12 @@ pub enum ViewportCommand { /// Turn borderless fullscreen on/off. Fullscreen(bool), + /// Move the window to borderless fullscreen on the monitor at the given index. + /// + /// Index refers to winit's `available_monitors()` order. If out of range, the + /// command is ignored (logged as a warning). + SetMonitor(usize), + /// Show window decorations, i.e. the chrome around the content /// with the title bar, close buttons, resize handles, etc. Decorations(bool),