diff --git a/wezterm-gui/src/scripting/guiwin.rs b/wezterm-gui/src/scripting/guiwin.rs index e365825a7..55a7ede35 100644 --- a/wezterm-gui/src/scripting/guiwin.rs +++ b/wezterm-gui/src/scripting/guiwin.rs @@ -9,7 +9,7 @@ use mlua::{UserData, UserDataMethods}; use mux::window::WindowId as MuxWindowId; use serde::*; use wezterm_toast_notification::ToastNotification; -use window::{Connection, ConnectionOps, WindowOps}; +use window::{Connection, ConnectionOps, WindowOps, WindowState}; #[derive(Clone)] pub struct GuiWin { @@ -53,7 +53,7 @@ impl UserData for GuiWin { methods.add_async_method("get_dimensions", |_, this, _: ()| async move { let (tx, rx) = smol::channel::bounded(1); this.window.notify(TermWindowNotif::GetDimensions(tx)); - let (dims, is_full_screen) = rx + let (dims, window_state) = rx .recv() .await .map_err(|e| anyhow::anyhow!("{:#}", e)) @@ -72,7 +72,8 @@ impl UserData for GuiWin { pixel_width: dims.pixel_width, pixel_height: dims.pixel_height, dpi: dims.dpi, - is_full_screen, + is_full_screen: window_state.contains(WindowState::FULL_SCREEN), + // FIXME: expose other states here }; Ok(dims) }); diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index d738e6880..2204c6b5b 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -81,7 +81,7 @@ pub enum TermWindowNotif { assignment: KeyAssignment, }, SetRightStatus(String), - GetDimensions(Sender<(Dimensions, bool)>), + GetDimensions(Sender<(Dimensions, WindowState)>), GetSelectionForPane { pane_id: PaneId, tx: Sender, @@ -176,7 +176,7 @@ pub struct TermWindow { fonts: Rc, /// Window dimensions and dpi pub dimensions: Dimensions, - pub is_full_screen: bool, + pub window_state: WindowState, /// Terminal dimensions terminal_size: PtySize, pub mux_window_id: MuxWindowId, @@ -457,7 +457,7 @@ impl TermWindow { fonts: Rc::clone(&fontconfig), render_metrics, dimensions, - is_full_screen: false, + window_state: WindowState::default(), terminal_size, render_state, input_map: InputMap::new(&config), @@ -568,9 +568,9 @@ impl TermWindow { } WindowEvent::Resized { dimensions, - is_full_screen, + window_state, } => { - self.resize(dimensions, is_full_screen); + self.resize(dimensions, window_state); Ok(true) } WindowEvent::KeyEvent(event) => { @@ -642,7 +642,7 @@ impl TermWindow { } } TermWindowNotif::GetDimensions(tx) => { - tx.try_send((self.dimensions, self.is_full_screen)) + tx.try_send((self.dimensions, self.window_state)) .map_err(chan_err) .context("send GetDimensions response")?; } diff --git a/wezterm-gui/src/termwindow/resize.rs b/wezterm-gui/src/termwindow/resize.rs index cb75da386..bae40266a 100644 --- a/wezterm-gui/src/termwindow/resize.rs +++ b/wezterm-gui/src/termwindow/resize.rs @@ -1,5 +1,5 @@ use crate::utilsprites::RenderMetrics; -use ::window::{Dimensions, WindowOps}; +use ::window::{Dimensions, WindowOps, WindowState}; use config::ConfigHandle; use mux::Mux; use portable_pty::PtySize; @@ -13,12 +13,12 @@ pub struct RowsAndCols { } impl super::TermWindow { - pub fn resize(&mut self, dimensions: Dimensions, is_full_screen: bool) { + pub fn resize(&mut self, dimensions: Dimensions, window_state: WindowState) { log::trace!( - "resize event, current cells: {:?}, new dims: {:?} is_full_screen:{}", + "resize event, current cells: {:?}, new dims: {:?} window_state:{:?}", self.current_cell_dimensions(), dimensions, - is_full_screen, + window_state, ); if dimensions.pixel_width == 0 || dimensions.pixel_height == 0 { // on windows, this can happen when minimizing the window. @@ -26,12 +26,12 @@ impl super::TermWindow { log::trace!("new dimensions are zero: NOP!"); return; } - if self.dimensions == dimensions && self.is_full_screen == is_full_screen { + if self.dimensions == dimensions && self.window_state == window_state { // It didn't really change log::trace!("dimensions didn't change NOP!"); return; } - self.is_full_screen = is_full_screen; + self.window_state = window_state; self.scaling_changed(dimensions, self.fonts.get_font_scale()); self.emit_window_event("window-resized"); } @@ -198,7 +198,8 @@ impl super::TermWindow { /// the `adjust_window_size_when_changing_font_size` configuration and /// revises the scaling/resize change accordingly pub fn adjust_font_scale(&mut self, font_scale: f64) { - if !self.is_full_screen && self.config.adjust_window_size_when_changing_font_size { + if self.window_state.can_resize() && self.config.adjust_window_size_when_changing_font_size + { self.scaling_changed(self.dimensions, font_scale); } else { let dimensions = self.dimensions; diff --git a/window/examples/async.rs b/window/examples/async.rs index 1f78935fc..3738cfc95 100644 --- a/window/examples/async.rs +++ b/window/examples/async.rs @@ -34,9 +34,9 @@ impl MyWindow { } WindowEvent::Resized { dimensions, - is_full_screen, + window_state, } => { - eprintln!("resize {:?} is_full_screen={}", dimensions, is_full_screen); + eprintln!("resize {:?} state={:?}", dimensions, window_state); self.dims = dimensions; } WindowEvent::MouseEvent(event) => { diff --git a/window/examples/wgpu.rs b/window/examples/wgpu.rs index edbb39e8e..d7dd2031a 100644 --- a/window/examples/wgpu.rs +++ b/window/examples/wgpu.rs @@ -225,7 +225,7 @@ impl MyWindow { } WindowEvent::Resized { dimensions, - is_full_screen: _, + window_state: _, } => { self.resize(dimensions); #[cfg(target_os = "macos")] diff --git a/window/src/lib.rs b/window/src/lib.rs index 1937a0f67..bec2ab5f1 100644 --- a/window/src/lib.rs +++ b/window/src/lib.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use bitflags::bitflags; use promise::Future; use std::any::Any; use std::rc::Rc; @@ -89,6 +90,30 @@ impl std::string::ToString for Appearance { } } +bitflags! { + #[derive(Default)] + pub struct WindowState: u8 { + /// Occupies the whole screen; cannot be resized while in this state. + const FULL_SCREEN = 1<<1; + /// Maximized along either or both of horizontal or vertical dimensions; + /// cannot be resized while in this state. + const MAXIMIZED = 1<<2; + /// Minimized or in some kind of off-screen state. Cannot be repainted + /// while in this state. + const HIDDEN = 1<<3; + } +} + +impl WindowState { + pub fn can_resize(self) -> bool { + !self.intersects(Self::FULL_SCREEN | Self::MAXIMIZED) + } + + pub fn can_paint(self) -> bool { + !self.contains(Self::HIDDEN) + } +} + #[derive(Debug)] pub enum WindowEvent { /// Called when the window close button is clicked. @@ -103,7 +128,7 @@ pub enum WindowEvent { /// Called when the window has been resized Resized { dimensions: Dimensions, - is_full_screen: bool, + window_state: WindowState, }, /// Called when the window has been invalidated and needs to diff --git a/window/src/os/macos/window.rs b/window/src/os/macos/window.rs index 35fba6b68..cfe46b657 100644 --- a/window/src/os/macos/window.rs +++ b/window/src/os/macos/window.rs @@ -507,7 +507,7 @@ impl Window { dpi: (crate::DEFAULT_DPI * (backing_frame.size.width / frame.size.width)) as usize, }, - is_full_screen: false, + window_state: WindowState::default(), }); Ok(window_handle) @@ -1929,7 +1929,11 @@ impl WindowView { dpi: (crate::DEFAULT_DPI * (backing_frame.size.width / frame.size.width)) as usize, }, - is_full_screen, + window_state: if is_full_screen { + WindowState::FULL_SCREEN + } else { + WindowState::default() + }, }); } } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 7d6a87684..6f7810c3e 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -6,7 +6,7 @@ use crate::os::wayland::connection::WaylandConnection; use crate::os::x11::keyboard::Keyboard; use crate::{ Clipboard, Connection, Dimensions, MouseCursor, Point, ScreenPoint, Window, WindowEvent, - WindowEventSender, WindowOps, + WindowEventSender, WindowOps, WindowState, }; use anyhow::{anyhow, bail, Context}; use async_io::Timer; @@ -112,7 +112,7 @@ pub struct WaylandWindowInner { copy_and_paste: Arc>, window: Option>, dimensions: Dimensions, - full_screen: bool, + window_state: WindowState, last_mouse_coords: Point, mouse_buttons: MouseButtons, modifiers: Modifiers, @@ -134,7 +134,7 @@ struct PendingEvent { refresh_decorations: bool, configure: Option<(u32, u32)>, dpi: Option, - full_screen: Option, + window_state: Option, } impl PendingEvent { @@ -165,17 +165,32 @@ impl PendingEvent { } else { changed = true; } - let full_screen = states.contains(&State::Fullscreen); + let mut state = WindowState::default(); + for s in &states { + match s { + State::Fullscreen => { + state |= WindowState::FULL_SCREEN; + } + State::Maximized + | State::TiledLeft + | State::TiledRight + | State::TiledTop + | State::TiledBottom => { + state |= WindowState::MAXIMIZED; + } + _ => {} + } + } log::debug!( - "Config: self.full_screen={:?}, states:{:?} {:?}", - self.full_screen, - full_screen, + "Config: self.window_state={:?}, states:{:?} {:?}", + self.window_state, + state, states ); - match (self.full_screen, full_screen) { - (None, false) => {} + match (self.window_state, state) { + (None, s) if s == WindowState::default() => {} _ => { - self.full_screen.replace(full_screen); + self.window_state.replace(state); changed = true; } } @@ -292,7 +307,7 @@ impl WaylandWindow { surface: surface.detach(), window: Some(window), dimensions, - full_screen: false, + window_state: WindowState::default(), last_mouse_coords: Point::new(0, 0), mouse_buttons: MouseButtons::NONE, modifiers: Modifiers::NONE, @@ -507,13 +522,13 @@ impl WaylandWindowInner { self.events.dispatch(WindowEvent::CloseRequested); } - if let Some(full_screen) = pending.full_screen.take() { + if let Some(window_state) = pending.window_state.take() { log::debug!( - "dispatch_pending_event self.full_screen={} pending:{}", - self.full_screen, - full_screen + "dispatch_pending_event self.window_state={:?} pending:{:?}", + self.window_state, + window_state ); - self.full_screen = full_screen; + self.window_state = window_state; } if pending.configure.is_none() && pending.dpi.is_some() { @@ -554,7 +569,7 @@ impl WaylandWindowInner { self.events.dispatch(WindowEvent::Resized { dimensions: self.dimensions, - is_full_screen: self.full_screen, + window_state: self.window_state, }); if let Some(wegl_surface) = self.wegl_surface.as_mut() { wegl_surface.resize(pixel_width, pixel_height, 0, 0); @@ -874,7 +889,7 @@ impl WaylandWindowInner { fn toggle_fullscreen(&mut self) { if let Some(window) = self.window.as_ref() { - if self.full_screen { + if self.window_state.contains(WindowState::FULL_SCREEN) { window.unset_fullscreen(); } else { window.set_fullscreen(None); diff --git a/window/src/os/windows/window.rs b/window/src/os/windows/window.rs index 3f72ce6d5..5d4610f0a 100644 --- a/window/src/os/windows/window.rs +++ b/window/src/os/windows/window.rs @@ -4,7 +4,7 @@ use crate::Appearance; use crate::{ Clipboard, Dimensions, KeyCode, KeyEvent, Modifiers, MouseButtons, MouseCursor, MouseEvent, MouseEventKind, MousePress, Point, Rect, ScreenPoint, WindowDecorations, WindowEvent, - WindowEventSender, WindowOps, + WindowEventSender, WindowOps, WindowState, }; use anyhow::{bail, Context}; use async_trait::async_trait; @@ -221,7 +221,11 @@ impl WindowInner { self.events.dispatch(WindowEvent::Resized { dimensions: current_dims, - is_full_screen: self.saved_placement.is_some(), + window_state: if self.saved_placement.is_some() { + WindowState::FULL_SCREEN + } else { + WindowState::default() + }, }); } diff --git a/window/src/os/x11/connection.rs b/window/src/os/x11/connection.rs index 5b4d23cee..c621df9ef 100644 --- a/window/src/os/x11/connection.rs +++ b/window/src/os/x11/connection.rs @@ -34,6 +34,11 @@ pub struct XConnection { pub atom_xsettings_selection: xcb::Atom, pub atom_xsettings_settings: xcb::Atom, pub atom_manager: xcb::Atom, + pub atom_state_maximized_vert: xcb::Atom, + pub atom_state_maximized_horz: xcb::Atom, + pub atom_state_hidden: xcb::Atom, + pub atom_state_fullscreen: xcb::Atom, + pub atom_net_wm_state: xcb::Atom, keysyms: *mut xcb_key_symbols_t, pub(crate) xrm: RefCell>, pub(crate) windows: RefCell>>>, @@ -357,6 +362,23 @@ impl XConnection { let atom_manager = xcb::intern_atom(&conn, false, "MANAGER") .get_reply()? .atom(); + let atom_state_maximized_vert = + xcb::intern_atom(&conn, false, "_NET_WM_STATE_MAXIMIZED_VERT") + .get_reply()? + .atom(); + let atom_state_maximized_horz = + xcb::intern_atom(&conn, false, "_NET_WM_STATE_MAXIMIZED_HORZ") + .get_reply()? + .atom(); + let atom_state_hidden = xcb::intern_atom(&conn, false, "_NET_WM_STATE_HIDDEN") + .get_reply()? + .atom(); + let atom_state_fullscreen = xcb::intern_atom(&conn, false, "_NET_WM_STATE_FULLSCREEN") + .get_reply()? + .atom(); + let atom_net_wm_state = xcb::intern_atom(&conn, false, "_NET_WM_STATE") + .get_reply()? + .atom(); let keysyms = unsafe { xcb_key_symbols_alloc((*conn).get_raw_conn()) }; @@ -434,6 +456,11 @@ impl XConnection { atom_xsettings_settings, atom_manager, atom_delete, + atom_state_maximized_vert, + atom_state_maximized_horz, + atom_state_hidden, + atom_state_fullscreen, + atom_net_wm_state, keysyms, keyboard, kbd_ev, diff --git a/window/src/os/x11/window.rs b/window/src/os/x11/window.rs index 0038cc05a..b00aaabd0 100644 --- a/window/src/os/x11/window.rs +++ b/window/src/os/x11/window.rs @@ -6,6 +6,7 @@ use crate::os::{Connection, Window}; use crate::{ Appearance, Clipboard, Dimensions, MouseButtons, MouseCursor, MouseEvent, MouseEventKind, MousePress, Point, ScreenPoint, WindowDecorations, WindowEvent, WindowEventSender, WindowOps, + WindowState, }; use anyhow::{anyhow, Context as _}; use async_trait::async_trait; @@ -152,7 +153,7 @@ impl XWindowInner { pixel_height: self.height as usize, dpi: self.dpi as usize, }, - is_full_screen: self.is_fullscreen().unwrap_or(false), + window_state: self.get_window_state().unwrap_or(WindowState::default()), }); } } @@ -189,7 +190,7 @@ impl XWindowInner { self.events.dispatch(WindowEvent::Resized { dimensions, - is_full_screen: self.is_fullscreen().unwrap_or(false), + window_state: self.get_window_state().unwrap_or(WindowState::default()), }); } xcb::KEY_PRESS | xcb::KEY_RELEASE => { @@ -515,22 +516,14 @@ impl XWindowInner { Ok(()) } - fn is_fullscreen(&self) -> anyhow::Result { + fn get_window_state(&self) -> anyhow::Result { let conn = self.conn(); - let net_wm_state = xcb::intern_atom(conn.conn(), false, "_NET_WM_STATE") - .get_reply()? - .atom(); - let net_wm_state_fullscreen = - xcb::intern_atom(conn.conn(), false, "_NET_WM_STATE_FULLSCREEN") - .get_reply()? - .atom(); - let reply = xcb::xproto::get_property( &conn, false, self.window_id, - net_wm_state, + conn.atom_net_wm_state, xcb::xproto::ATOM_ATOM, 0, 1024, @@ -538,11 +531,19 @@ impl XWindowInner { .get_reply()?; let state = reply.value::(); + let mut window_state = WindowState::default(); - Ok(state - .iter() - .position(|&x| x == net_wm_state_fullscreen) - .is_some()) + for &s in state { + if s == conn.atom_state_fullscreen { + window_state |= WindowState::FULL_SCREEN; + } else if s == conn.atom_state_maximized_vert || s == conn.atom_state_maximized_horz { + window_state |= WindowState::MAXIMIZED; + } else if s == conn.atom_state_hidden { + window_state |= WindowState::HIDDEN; + } + } + + Ok(window_state) } fn set_fullscreen_hint(&mut self, enable: bool) -> anyhow::Result<()> { @@ -804,8 +805,8 @@ impl XWindowInner { } fn toggle_fullscreen(&mut self) { - let fullscreen = match self.is_fullscreen() { - Ok(f) => f, + let fullscreen = match self.get_window_state() { + Ok(f) => f.contains(WindowState::FULL_SCREEN), Err(err) => { log::error!("Failed to determine fullscreen state: {}", err); return;