mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Implement explicit unmapped window state tracking
This commit is contained in:
@@ -145,8 +145,7 @@ impl Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_window(&mut self, window: TestWindow, width: Option<ColumnWidth>) {
|
fn add_window(&mut self, window: TestWindow, width: Option<ColumnWidth>) {
|
||||||
self.layout
|
self.layout.add_window(window.clone(), width, false);
|
||||||
.add_window(window.clone(), width.map(Some), false);
|
|
||||||
if window.communicate() {
|
if window.communicate() {
|
||||||
self.layout.update_window(&window);
|
self.layout.update_window(&window);
|
||||||
}
|
}
|
||||||
@@ -160,7 +159,7 @@ impl Layout {
|
|||||||
width: Option<ColumnWidth>,
|
width: Option<ColumnWidth>,
|
||||||
) {
|
) {
|
||||||
self.layout
|
self.layout
|
||||||
.add_window_right_of(right_of, window.clone(), width.map(Some), false);
|
.add_window_right_of(right_of, window.clone(), width, false);
|
||||||
if window.communicate() {
|
if window.communicate() {
|
||||||
self.layout.update_window(&window);
|
self.layout.update_window(&window);
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-26
@@ -16,9 +16,9 @@ use smithay::wayland::dmabuf::get_dmabuf;
|
|||||||
use smithay::wayland::shm::{ShmHandler, ShmState};
|
use smithay::wayland::shm::{ShmHandler, ShmState};
|
||||||
use smithay::{delegate_compositor, delegate_shm};
|
use smithay::{delegate_compositor, delegate_shm};
|
||||||
|
|
||||||
use super::xdg_shell::{initial_configure_sent, resolve_window_rules};
|
|
||||||
use crate::niri::{ClientState, State};
|
use crate::niri::{ClientState, State};
|
||||||
use crate::utils::clone2;
|
use crate::utils::clone2;
|
||||||
|
use crate::window::{InitialConfigureState, Unmapped};
|
||||||
|
|
||||||
impl CompositorHandler for State {
|
impl CompositorHandler for State {
|
||||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||||
@@ -105,29 +105,41 @@ impl CompositorHandler for State {
|
|||||||
|
|
||||||
if is_mapped {
|
if is_mapped {
|
||||||
// The toplevel got mapped.
|
// The toplevel got mapped.
|
||||||
let window = entry.remove();
|
let Unmapped { window, state } = entry.remove();
|
||||||
|
|
||||||
window.on_commit();
|
window.on_commit();
|
||||||
|
|
||||||
|
let (width, output) =
|
||||||
|
if let InitialConfigureState::Configured { width, output, .. } = state {
|
||||||
|
// Check that the output is still connected.
|
||||||
|
let output =
|
||||||
|
output.filter(|o| self.niri.layout.monitor_for_output(o).is_some());
|
||||||
|
|
||||||
|
(width, output)
|
||||||
|
} else {
|
||||||
|
error!("window map must happen after initial configure");
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
let parent = window
|
let parent = window
|
||||||
.toplevel()
|
.toplevel()
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(|parent| self.niri.layout.find_window_and_output(&parent))
|
.and_then(|parent| self.niri.layout.find_window_and_output(&parent))
|
||||||
.map(|(win, _)| win.clone());
|
// Only consider the parent if we configured the window for the same
|
||||||
|
// output.
|
||||||
let (width, output) = {
|
//
|
||||||
let config = self.niri.config.borrow();
|
// Normally when we're following the parent, the configured output will be
|
||||||
let rules = resolve_window_rules(&config.window_rules, window.toplevel());
|
// None. If the configured output is set, that means it was set explicitly
|
||||||
let output = rules
|
// by a window rule or a fullscreen request.
|
||||||
.open_on_output
|
.filter(|(_, parent_output)| {
|
||||||
.and_then(|name| self.niri.output_by_name.get(name))
|
output.is_none() || output.as_ref() == Some(*parent_output)
|
||||||
.cloned();
|
})
|
||||||
(rules.default_width, output)
|
.map(|(window, _)| window.clone());
|
||||||
};
|
|
||||||
|
|
||||||
let win = window.clone();
|
let win = window.clone();
|
||||||
|
|
||||||
// Open dialogs immediately to the right of their parent window.
|
|
||||||
let output = if let Some(p) = parent {
|
let output = if let Some(p) = parent {
|
||||||
|
// Open dialogs immediately to the right of their parent window.
|
||||||
self.niri.layout.add_window_right_of(&p, win, width, false)
|
self.niri.layout.add_window_right_of(&p, win, width, false)
|
||||||
} else if let Some(output) = &output {
|
} else if let Some(output) = &output {
|
||||||
self.niri
|
self.niri
|
||||||
@@ -146,17 +158,10 @@ impl CompositorHandler for State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The toplevel remains unmapped.
|
// The toplevel remains unmapped.
|
||||||
let window = entry.get().clone();
|
let unmapped = entry.get();
|
||||||
|
if unmapped.needs_initial_configure() {
|
||||||
// Send the initial configure in an idle, in case the client sent some more info
|
let toplevel = unmapped.window.toplevel().clone();
|
||||||
// after the initial commit.
|
self.queue_initial_configure(toplevel);
|
||||||
if !initial_configure_sent(window.toplevel()) {
|
|
||||||
self.niri.event_loop.insert_idle(move |state| {
|
|
||||||
if !window.toplevel().alive() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.send_initial_configure_if_needed(&window);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -178,7 +183,12 @@ impl CompositorHandler for State {
|
|||||||
if !is_mapped {
|
if !is_mapped {
|
||||||
// The toplevel got unmapped.
|
// The toplevel got unmapped.
|
||||||
self.niri.layout.remove_window(&window);
|
self.niri.layout.remove_window(&window);
|
||||||
self.niri.unmapped_windows.insert(surface.clone(), window);
|
|
||||||
|
// Newly-unmapped toplevels must perform the initial commit-configure sequence
|
||||||
|
// afresh.
|
||||||
|
let unmapped = Unmapped::new(window);
|
||||||
|
self.niri.unmapped_windows.insert(surface.clone(), unmapped);
|
||||||
|
|
||||||
self.niri.queue_redraw(output);
|
self.niri.queue_redraw(output);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+168
-91
@@ -27,19 +27,7 @@ use smithay::{delegate_kde_decoration, delegate_xdg_decoration, delegate_xdg_she
|
|||||||
use crate::layout::workspace::ColumnWidth;
|
use crate::layout::workspace::ColumnWidth;
|
||||||
use crate::niri::{PopupGrabState, State};
|
use crate::niri::{PopupGrabState, State};
|
||||||
use crate::utils::clone2;
|
use crate::utils::clone2;
|
||||||
|
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped};
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct ResolvedWindowRule<'a> {
|
|
||||||
/// Default width for this window.
|
|
||||||
///
|
|
||||||
/// - `None`: unset.
|
|
||||||
/// - `Some(None)`: set to empty.
|
|
||||||
/// - `Some(Some(width))`: set to a particular width.
|
|
||||||
pub default_width: Option<Option<ColumnWidth>>,
|
|
||||||
|
|
||||||
/// Output to open this window on.
|
|
||||||
pub open_on_output: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_matches(role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
fn window_matches(role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
||||||
if let Some(app_id_re) = &m.app_id {
|
if let Some(app_id_re) = &m.app_id {
|
||||||
@@ -63,13 +51,13 @@ fn window_matches(role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_window_rules<'a>(
|
pub fn resolve_window_rules(
|
||||||
rules: &'a [WindowRule],
|
rules: &[WindowRule],
|
||||||
toplevel: &ToplevelSurface,
|
toplevel: &ToplevelSurface,
|
||||||
) -> ResolvedWindowRule<'a> {
|
) -> ResolvedWindowRules {
|
||||||
let _span = tracy_client::span!("resolve_window_rules");
|
let _span = tracy_client::span!("resolve_window_rules");
|
||||||
|
|
||||||
let mut resolved = ResolvedWindowRule::default();
|
let mut resolved = ResolvedWindowRules::default();
|
||||||
|
|
||||||
with_states(toplevel.wl_surface(), |states| {
|
with_states(toplevel.wl_surface(), |states| {
|
||||||
let role = states
|
let role = states
|
||||||
@@ -79,6 +67,8 @@ pub fn resolve_window_rules<'a>(
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let mut open_on_output = None;
|
||||||
|
|
||||||
for rule in rules {
|
for rule in rules {
|
||||||
if !(rule.matches.is_empty() || rule.matches.iter().any(|m| window_matches(&role, m))) {
|
if !(rule.matches.is_empty() || rule.matches.iter().any(|m| window_matches(&role, m))) {
|
||||||
continue;
|
continue;
|
||||||
@@ -97,9 +87,11 @@ pub fn resolve_window_rules<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(x) = rule.open_on_output.as_deref() {
|
if let Some(x) = rule.open_on_output.as_deref() {
|
||||||
resolved.open_on_output = Some(x);
|
open_on_output = Some(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
||||||
});
|
});
|
||||||
|
|
||||||
resolved
|
resolved
|
||||||
@@ -112,10 +104,8 @@ impl XdgShellHandler for State {
|
|||||||
|
|
||||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||||
let wl_surface = surface.wl_surface().clone();
|
let wl_surface = surface.wl_surface().clone();
|
||||||
let window = Window::new(surface);
|
let unmapped = Unmapped::new(Window::new(surface));
|
||||||
|
let existing = self.niri.unmapped_windows.insert(wl_surface, unmapped);
|
||||||
// At the moment of creation, xdg toplevels must have no buffer.
|
|
||||||
let existing = self.niri.unmapped_windows.insert(wl_surface, window);
|
|
||||||
assert!(existing.is_none());
|
assert!(existing.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,53 +263,42 @@ impl XdgShellHandler for State {
|
|||||||
surface: ToplevelSurface,
|
surface: ToplevelSurface,
|
||||||
wl_output: Option<wl_output::WlOutput>,
|
wl_output: Option<wl_output::WlOutput>,
|
||||||
) {
|
) {
|
||||||
if surface
|
if let Some((window, current_output)) = self
|
||||||
.current_state()
|
.niri
|
||||||
.capabilities
|
.layout
|
||||||
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
|
.find_window_and_output(surface.wl_surface())
|
||||||
{
|
{
|
||||||
if let Some((window, current_output)) = self
|
let window = window.clone();
|
||||||
.niri
|
|
||||||
.layout
|
|
||||||
.find_window_and_output(surface.wl_surface())
|
|
||||||
{
|
|
||||||
let window = window.clone();
|
|
||||||
|
|
||||||
if let Some(requested_output) = wl_output.as_ref().and_then(Output::from_resource) {
|
if let Some(requested_output) = wl_output.as_ref().and_then(Output::from_resource) {
|
||||||
if &requested_output != current_output {
|
if &requested_output != current_output {
|
||||||
self.niri
|
self.niri
|
||||||
.layout
|
.layout
|
||||||
.move_window_to_output(window.clone(), &requested_output);
|
.move_window_to_output(window.clone(), &requested_output);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.niri.layout.set_fullscreen(&window, true);
|
|
||||||
} else if let Some(window) = self.niri.unmapped_windows.get(surface.wl_surface()) {
|
|
||||||
let config = self.niri.config.borrow();
|
|
||||||
let rules = resolve_window_rules(&config.window_rules, window.toplevel());
|
|
||||||
|
|
||||||
// FIXME: take requested output into account (will need to thread this through to
|
|
||||||
// send_initial_configure_if_needed and commit handler).
|
|
||||||
let output = rules
|
|
||||||
.open_on_output
|
|
||||||
.and_then(|name| self.niri.output_by_name.get(name));
|
|
||||||
let mon = output.map(|o| self.niri.layout.monitor_for_output(o).unwrap());
|
|
||||||
let ws = mon
|
|
||||||
.map(|mon| mon.active_workspace_ref())
|
|
||||||
.or_else(|| self.niri.layout.active_workspace());
|
|
||||||
|
|
||||||
if let Some(ws) = ws {
|
|
||||||
window.toplevel().with_pending_state(|state| {
|
|
||||||
state.size = Some(ws.view_size());
|
|
||||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// A configure is required in response to this event. However, if an initial configure
|
self.niri.layout.set_fullscreen(&window, true);
|
||||||
// wasn't sent, then we will send this as part of the initial configure later.
|
|
||||||
if initial_configure_sent(&surface) {
|
// A configure is required in response to this event regardless if there are pending
|
||||||
|
// changes.
|
||||||
|
surface.send_configure();
|
||||||
|
} else if let Some(unmapped) = self.niri.unmapped_windows.get_mut(surface.wl_surface()) {
|
||||||
|
match &mut unmapped.state {
|
||||||
|
InitialConfigureState::NotConfigured { wants_fullscreen } => {
|
||||||
|
*wants_fullscreen = Some(wl_output.as_ref().and_then(Output::from_resource));
|
||||||
|
|
||||||
|
// The required configure will be the initial configure.
|
||||||
|
}
|
||||||
|
InitialConfigureState::Configured { .. } => {
|
||||||
|
// FIXME: implement this once I figure out a good way without code duplication.
|
||||||
|
|
||||||
|
// We already sent the initial configure, so we need to reconfigure.
|
||||||
|
surface.send_configure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("couldn't find the toplevel in fullscreen_request()");
|
||||||
surface.send_configure();
|
surface.send_configure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,24 +311,27 @@ impl XdgShellHandler for State {
|
|||||||
{
|
{
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
self.niri.layout.set_fullscreen(&window, false);
|
self.niri.layout.set_fullscreen(&window, false);
|
||||||
} else if let Some(window) = self.niri.unmapped_windows.get(surface.wl_surface()) {
|
|
||||||
let config = self.niri.config.borrow();
|
|
||||||
let rules = resolve_window_rules(&config.window_rules, window.toplevel());
|
|
||||||
|
|
||||||
let output = rules
|
// A configure is required in response to this event regardless if there are pending
|
||||||
.open_on_output
|
// changes.
|
||||||
.and_then(|name| self.niri.output_by_name.get(name));
|
surface.send_configure();
|
||||||
let mon = output.map(|o| self.niri.layout.monitor_for_output(o).unwrap());
|
} else if let Some(unmapped) = self.niri.unmapped_windows.get_mut(surface.wl_surface()) {
|
||||||
let ws = mon
|
match &mut unmapped.state {
|
||||||
.map(|mon| mon.active_workspace_ref())
|
InitialConfigureState::NotConfigured { wants_fullscreen } => {
|
||||||
.or_else(|| self.niri.layout.active_workspace());
|
*wants_fullscreen = None;
|
||||||
|
|
||||||
if let Some(ws) = ws {
|
// The required configure will be the initial configure.
|
||||||
window.toplevel().with_pending_state(|state| {
|
}
|
||||||
state.size = Some(ws.new_window_size(rules.default_width));
|
InitialConfigureState::Configured { .. } => {
|
||||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
// FIXME: implement this once I figure out a good way without code duplication.
|
||||||
});
|
|
||||||
|
// We already sent the initial configure, so we need to reconfigure.
|
||||||
|
surface.send_configure();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
error!("couldn't find the toplevel in unfullscreen_request()");
|
||||||
|
surface.send_configure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,6 +367,14 @@ impl XdgShellHandler for State {
|
|||||||
self.niri.queue_redraw(output.clone());
|
self.niri.queue_redraw(output.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn app_id_changed(&mut self, toplevel: ToplevelSurface) {
|
||||||
|
self.update_window_rules(&toplevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title_changed(&mut self, toplevel: ToplevelSurface) {
|
||||||
|
self.update_window_rules(&toplevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate_xdg_shell!(State);
|
delegate_xdg_shell!(State);
|
||||||
@@ -440,7 +430,7 @@ impl KdeDecorationHandler for State {
|
|||||||
|
|
||||||
delegate_kde_decoration!(State);
|
delegate_kde_decoration!(State);
|
||||||
|
|
||||||
pub fn initial_configure_sent(toplevel: &ToplevelSurface) -> bool {
|
fn initial_configure_sent(toplevel: &ToplevelSurface) -> bool {
|
||||||
with_states(toplevel.wl_surface(), |states| {
|
with_states(toplevel.wl_surface(), |states| {
|
||||||
states
|
states
|
||||||
.data_map
|
.data_map
|
||||||
@@ -453,28 +443,82 @@ pub fn initial_configure_sent(toplevel: &ToplevelSurface) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn send_initial_configure_if_needed(&mut self, window: &Window) {
|
pub fn send_initial_configure(&mut self, toplevel: &ToplevelSurface) {
|
||||||
let toplevel = window.toplevel();
|
let _span = tracy_client::span!("State::send_initial_configure");
|
||||||
if initial_configure_sent(toplevel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _span = tracy_client::span!("State::send_initial_configure_if_needed");
|
let Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) else {
|
||||||
|
error!("window must be present in unmapped_windows in send_initial_configure()");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Unmapped { window, state } = unmapped;
|
||||||
|
|
||||||
|
let InitialConfigureState::NotConfigured { wants_fullscreen } = state else {
|
||||||
|
error!("window must not be already configured in send_initial_configure()");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let config = self.niri.config.borrow();
|
let config = self.niri.config.borrow();
|
||||||
let rules = resolve_window_rules(&config.window_rules, toplevel);
|
let rules = resolve_window_rules(&config.window_rules, toplevel);
|
||||||
|
|
||||||
let output = rules
|
// Pick the target monitor. First, check if we had an output set in the window rules.
|
||||||
|
let mon = rules
|
||||||
.open_on_output
|
.open_on_output
|
||||||
.and_then(|name| self.niri.output_by_name.get(name));
|
.as_deref()
|
||||||
let mon = output.map(|o| self.niri.layout.monitor_for_output(o).unwrap());
|
.and_then(|name| self.niri.output_by_name.get(name))
|
||||||
|
.and_then(|o| self.niri.layout.monitor_for_output(o));
|
||||||
|
|
||||||
|
// If not, check if the window requested one for fullscreen.
|
||||||
|
let mon = mon.or_else(|| {
|
||||||
|
wants_fullscreen
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.as_ref())
|
||||||
|
// The monitor might not exist if the output was disconnected.
|
||||||
|
.and_then(|o| self.niri.layout.monitor_for_output(o))
|
||||||
|
});
|
||||||
|
|
||||||
|
// If not, check if this is a dialog with a parent, to place it next to the parent.
|
||||||
|
let mon = mon.map(|mon| (mon, false)).or_else(|| {
|
||||||
|
toplevel
|
||||||
|
.parent()
|
||||||
|
.and_then(|parent| self.niri.layout.find_window_and_output(&parent))
|
||||||
|
.map(|(_win, output)| output)
|
||||||
|
.and_then(|o| self.niri.layout.monitor_for_output(o))
|
||||||
|
.map(|mon| (mon, true))
|
||||||
|
});
|
||||||
|
|
||||||
|
// If not, use the active monitor.
|
||||||
|
let mon = mon.or_else(|| {
|
||||||
|
self.niri
|
||||||
|
.layout
|
||||||
|
.active_monitor_ref()
|
||||||
|
.map(|mon| (mon, false))
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we're following the parent, don't set the target output, so that when the window is
|
||||||
|
// mapped, it fetches the possibly changed parent's output again, and shows up there.
|
||||||
|
let output = mon
|
||||||
|
.filter(|(_, parent)| !parent)
|
||||||
|
.map(|(mon, _)| mon.output.clone());
|
||||||
|
let mon = mon.map(|(mon, _)| mon);
|
||||||
|
|
||||||
|
let mut width = None;
|
||||||
|
|
||||||
|
// Tell the surface the preferred size and bounds for its likely output.
|
||||||
let ws = mon
|
let ws = mon
|
||||||
.map(|mon| mon.active_workspace_ref())
|
.map(|mon| mon.active_workspace_ref())
|
||||||
.or_else(|| self.niri.layout.active_workspace());
|
.or_else(|| self.niri.layout.active_workspace());
|
||||||
|
|
||||||
// Tell the surface the preferred size and bounds for its likely output.
|
|
||||||
if let Some(ws) = ws {
|
if let Some(ws) = ws {
|
||||||
ws.configure_new_window(window, rules.default_width);
|
// Set a fullscreen state if requested.
|
||||||
|
if wants_fullscreen.is_some() {
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
width = ws.resolve_default_width(rules.default_width);
|
||||||
|
ws.configure_new_window(window, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user prefers no CSD, it's a reasonable assumption that they would prefer to get
|
// If the user prefers no CSD, it's a reasonable assumption that they would prefer to get
|
||||||
@@ -488,9 +532,32 @@ impl State {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the configured settings.
|
||||||
|
*state = InitialConfigureState::Configured {
|
||||||
|
rules,
|
||||||
|
width,
|
||||||
|
output,
|
||||||
|
};
|
||||||
|
|
||||||
toplevel.send_configure();
|
toplevel.send_configure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queue_initial_configure(&self, toplevel: ToplevelSurface) {
|
||||||
|
// Send the initial configure in an idle, in case the client sent some more info after the
|
||||||
|
// initial commit.
|
||||||
|
self.niri.event_loop.insert_idle(move |state| {
|
||||||
|
if !toplevel.alive() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(unmapped) = state.niri.unmapped_windows.get(toplevel.wl_surface()) {
|
||||||
|
if unmapped.needs_initial_configure() {
|
||||||
|
state.send_initial_configure(&toplevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Should be called on `WlSurface::commit`
|
/// Should be called on `WlSurface::commit`
|
||||||
pub fn popups_handle_commit(&mut self, surface: &WlSurface) {
|
pub fn popups_handle_commit(&mut self, surface: &WlSurface) {
|
||||||
self.niri.popups.commit(surface);
|
self.niri.popups.commit(surface);
|
||||||
@@ -611,6 +678,16 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_window_rules(&mut self, toplevel: &ToplevelSurface) {
|
||||||
|
let resolve = || resolve_window_rules(&self.niri.config.borrow().window_rules, toplevel);
|
||||||
|
|
||||||
|
if let Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) {
|
||||||
|
if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state {
|
||||||
|
*rules = resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unconstrain_with_padding(
|
fn unconstrain_with_padding(
|
||||||
|
|||||||
+19
-21
@@ -531,15 +531,10 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
pub fn add_window(
|
pub fn add_window(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: W,
|
window: W,
|
||||||
width: Option<Option<ColumnWidth>>,
|
width: Option<ColumnWidth>,
|
||||||
is_full_width: bool,
|
is_full_width: bool,
|
||||||
) -> Option<&Output> {
|
) -> Option<&Output> {
|
||||||
let width = match width {
|
let width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
||||||
Some(Some(width)) => Some(width),
|
|
||||||
Some(None) => None,
|
|
||||||
None => self.options.default_width,
|
|
||||||
}
|
|
||||||
.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
|
||||||
|
|
||||||
match &mut self.monitor_set {
|
match &mut self.monitor_set {
|
||||||
MonitorSet::Normal {
|
MonitorSet::Normal {
|
||||||
@@ -587,15 +582,10 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
right_of: &W,
|
right_of: &W,
|
||||||
window: W,
|
window: W,
|
||||||
width: Option<Option<ColumnWidth>>,
|
width: Option<ColumnWidth>,
|
||||||
is_full_width: bool,
|
is_full_width: bool,
|
||||||
) -> Option<&Output> {
|
) -> Option<&Output> {
|
||||||
let width = match width {
|
let width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
||||||
Some(Some(width)) => Some(width),
|
|
||||||
Some(None) => None,
|
|
||||||
None => self.options.default_width,
|
|
||||||
}
|
|
||||||
.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
|
||||||
|
|
||||||
match &mut self.monitor_set {
|
match &mut self.monitor_set {
|
||||||
MonitorSet::Normal { monitors, .. } => {
|
MonitorSet::Normal { monitors, .. } => {
|
||||||
@@ -623,15 +613,10 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
window: W,
|
window: W,
|
||||||
width: Option<Option<ColumnWidth>>,
|
width: Option<ColumnWidth>,
|
||||||
is_full_width: bool,
|
is_full_width: bool,
|
||||||
) {
|
) {
|
||||||
let width = match width {
|
let width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
||||||
Some(Some(width)) => Some(width),
|
|
||||||
Some(None) => None,
|
|
||||||
None => self.options.default_width,
|
|
||||||
}
|
|
||||||
.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
|
||||||
|
|
||||||
let MonitorSet::Normal {
|
let MonitorSet::Normal {
|
||||||
monitors,
|
monitors,
|
||||||
@@ -931,6 +916,19 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
Some(&mut monitors[*active_monitor_idx])
|
Some(&mut monitors[*active_monitor_idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn active_monitor_ref(&self) -> Option<&Monitor<W>> {
|
||||||
|
let MonitorSet::Normal {
|
||||||
|
monitors,
|
||||||
|
active_monitor_idx,
|
||||||
|
..
|
||||||
|
} = &self.monitor_set
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(&monitors[*active_monitor_idx])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn monitor_for_output(&self, output: &Output) -> Option<&Monitor<W>> {
|
pub fn monitor_for_output(&self, output: &Output) -> Option<&Monitor<W>> {
|
||||||
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
+9
-11
@@ -322,17 +322,19 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_window_size(
|
pub fn resolve_default_width(
|
||||||
&self,
|
&self,
|
||||||
default_width: Option<Option<ColumnWidth>>,
|
default_width: Option<Option<ColumnWidth>>,
|
||||||
) -> Size<i32, Logical> {
|
) -> Option<ColumnWidth> {
|
||||||
let default_width = match default_width {
|
match default_width {
|
||||||
Some(Some(width)) => Some(width),
|
Some(Some(width)) => Some(width),
|
||||||
Some(None) => None,
|
Some(None) => None,
|
||||||
None => self.options.default_width,
|
None => self.options.default_width,
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let width = if let Some(width) = default_width {
|
pub fn new_window_size(&self, width: Option<ColumnWidth>) -> Size<i32, Logical> {
|
||||||
|
let width = if let Some(width) = width {
|
||||||
let mut width = width.resolve(&self.options, self.working_area.size.w);
|
let mut width = width.resolve(&self.options, self.working_area.size.w);
|
||||||
if !self.options.border.off {
|
if !self.options.border.off {
|
||||||
width -= self.options.border.width as i32 * 2;
|
width -= self.options.border.width as i32 * 2;
|
||||||
@@ -350,11 +352,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
Size::from((width, max(height, 1)))
|
Size::from((width, max(height, 1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure_new_window(
|
pub fn configure_new_window(&self, window: &Window, width: Option<ColumnWidth>) {
|
||||||
&self,
|
|
||||||
window: &Window,
|
|
||||||
default_width: Option<Option<ColumnWidth>>,
|
|
||||||
) {
|
|
||||||
if let Some(output) = self.output.as_ref() {
|
if let Some(output) = self.output.as_ref() {
|
||||||
set_preferred_scale_transform(window, output);
|
set_preferred_scale_transform(window, output);
|
||||||
}
|
}
|
||||||
@@ -363,7 +361,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
if state.states.contains(xdg_toplevel::State::Fullscreen) {
|
if state.states.contains(xdg_toplevel::State::Fullscreen) {
|
||||||
state.size = Some(self.view_size);
|
state.size = Some(self.view_size);
|
||||||
} else {
|
} else {
|
||||||
state.size = Some(self.new_window_size(default_width));
|
state.size = Some(self.new_window_size(width));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.bounds = Some(self.toplevel_bounds());
|
state.bounds = Some(self.toplevel_bounds());
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub mod protocols;
|
|||||||
pub mod render_helpers;
|
pub mod render_helpers;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
pub mod window;
|
||||||
|
|
||||||
#[cfg(not(feature = "xdp-gnome-screencast"))]
|
#[cfg(not(feature = "xdp-gnome-screencast"))]
|
||||||
pub mod dummy_pw_utils;
|
pub mod dummy_pw_utils;
|
||||||
|
|||||||
+2
-1
@@ -107,6 +107,7 @@ use crate::ui::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
|
|||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
center, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
|
center, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
|
||||||
};
|
};
|
||||||
|
use crate::window::Unmapped;
|
||||||
use crate::{animation, niri_render_elements};
|
use crate::{animation, niri_render_elements};
|
||||||
|
|
||||||
const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.];
|
const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.];
|
||||||
@@ -132,7 +133,7 @@ pub struct Niri {
|
|||||||
pub global_space: Space<Window>,
|
pub global_space: Space<Window>,
|
||||||
|
|
||||||
// Windows which don't have a buffer attached yet.
|
// Windows which don't have a buffer attached yet.
|
||||||
pub unmapped_windows: HashMap<WlSurface, Window>,
|
pub unmapped_windows: HashMap<WlSurface, Unmapped>,
|
||||||
|
|
||||||
pub output_state: HashMap<Output, OutputState>,
|
pub output_state: HashMap<Output, OutputState>,
|
||||||
pub output_by_name: HashMap<String, Output>,
|
pub output_by_name: HashMap<String, Output>,
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
use smithay::desktop::Window;
|
||||||
|
use smithay::output::Output;
|
||||||
|
|
||||||
|
use crate::layout::workspace::ColumnWidth;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Unmapped {
|
||||||
|
pub window: Window,
|
||||||
|
pub state: InitialConfigureState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InitialConfigureState {
|
||||||
|
/// The window has not been initially configured yet.
|
||||||
|
NotConfigured {
|
||||||
|
/// Whether the window requested to be fullscreened, and the requested output, if any.
|
||||||
|
wants_fullscreen: Option<Option<Output>>,
|
||||||
|
},
|
||||||
|
/// The window has been configured.
|
||||||
|
Configured {
|
||||||
|
/// Up-to-date rules.
|
||||||
|
///
|
||||||
|
/// We start tracking window rules when sending the initial configure, since they don't
|
||||||
|
/// affect anything before that.
|
||||||
|
rules: ResolvedWindowRules,
|
||||||
|
|
||||||
|
/// Resolved default width for this window.
|
||||||
|
///
|
||||||
|
/// `None` means that the window will pick its own width.
|
||||||
|
width: Option<ColumnWidth>,
|
||||||
|
|
||||||
|
/// Output to open this window on.
|
||||||
|
///
|
||||||
|
/// This can be `None` in cases like:
|
||||||
|
///
|
||||||
|
/// - There are no outputs connected.
|
||||||
|
/// - This is a dialog with a parent, and there was no explicit output set, so this dialog
|
||||||
|
/// should fetch the parent's current output again upon mapping.
|
||||||
|
output: Option<Output>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rules fully resolved for a window.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ResolvedWindowRules {
|
||||||
|
/// Default width for this window.
|
||||||
|
///
|
||||||
|
/// - `None`: unset (global default should be used).
|
||||||
|
/// - `Some(None)`: set to empty (window picks its own width).
|
||||||
|
/// - `Some(Some(width))`: set to a particular width.
|
||||||
|
pub default_width: Option<Option<ColumnWidth>>,
|
||||||
|
|
||||||
|
/// Output to open this window on.
|
||||||
|
pub open_on_output: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unmapped {
|
||||||
|
/// Wraps a newly created window that hasn't been initially configured yet.
|
||||||
|
pub fn new(window: Window) -> Self {
|
||||||
|
Self {
|
||||||
|
window,
|
||||||
|
state: InitialConfigureState::NotConfigured {
|
||||||
|
wants_fullscreen: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_initial_configure(&self) -> bool {
|
||||||
|
matches!(self.state, InitialConfigureState::NotConfigured { .. })
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user