1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-28 07:55:03 +03:00

window: add WindowState concept

WindowState is a bitfield that can represent maximized, full screen
and hidden window states.

WindowState is passed along with resize events, improving on the
prior basic is_full_screen boolean by representing the other states.

Notably, WindowState::MAXIMIZED is used to represent a state where
the window's size is constrained by some window environment function;
it could be due to the window being maximized in either or both the
vertical or horizontal directions, or by the window being in a tiled
state on any edge.

When the window is MAXIMIZED, wezterm will behave as though
`adjust_window_size_when_changing_font_size = false` because it knows
that it cannot adjust the window size in that state.

This potentially helps with #695, depending on whether the window
manager propagates this state information to wezterm.  Gnome/mutter
does a good job at this with both X11 and Wayland, but I couldn't get
sway to report these states and I don't know of any other tiling wm
that I can easily install and use on fedora, so there's a question
mark around that.
This commit is contained in:
Wez Furlong 2021-08-06 18:56:37 -07:00
parent 0158e8e49a
commit 79165617b1
11 changed files with 138 additions and 60 deletions

View File

@ -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)
});

View File

@ -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<String>,
@ -176,7 +176,7 @@ pub struct TermWindow {
fonts: Rc<FontConfiguration>,
/// 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")?;
}

View File

@ -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;

View File

@ -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) => {

View File

@ -225,7 +225,7 @@ impl MyWindow {
}
WindowEvent::Resized {
dimensions,
is_full_screen: _,
window_state: _,
} => {
self.resize(dimensions);
#[cfg(target_os = "macos")]

View File

@ -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

View File

@ -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()
},
});
}
}

View File

@ -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<Mutex<CopyAndPaste>>,
window: Option<toolkit::window::Window<ConceptFrame>>,
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<i32>,
full_screen: Option<bool>,
window_state: Option<WindowState>,
}
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);

View File

@ -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()
},
});
}

View File

@ -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<HashMap<String, String>>,
pub(crate) windows: RefCell<HashMap<xcb::xproto::Window, Arc<Mutex<XWindowInner>>>>,
@ -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,

View File

@ -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<bool> {
fn get_window_state(&self) -> anyhow::Result<WindowState> {
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::<u32>();
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;