mirror of
https://github.com/YaLTeR/niri.git
synced 2024-09-11 20:37:25 +03:00
Compare commits
7 Commits
c3327d36da
...
db49deb7fd
Author | SHA1 | Date | |
---|---|---|---|
|
db49deb7fd | ||
|
c61361de3c | ||
|
3963f537a4 | ||
|
f31e105043 | ||
|
bbb4caeb8c | ||
|
d421e1fbf8 | ||
|
23ac3d7323 |
@ -73,6 +73,8 @@ pub struct Input {
|
||||
pub warp_mouse_to_focus: bool,
|
||||
#[knuffel(child)]
|
||||
pub focus_follows_mouse: bool,
|
||||
#[knuffel(child)]
|
||||
pub workspace_auto_back_and_forth: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
|
||||
@ -672,6 +674,7 @@ pub struct WindowRule {
|
||||
#[knuffel(children(name = "exclude"))]
|
||||
pub excludes: Vec<Match>,
|
||||
|
||||
// Rules applied at initial configure.
|
||||
#[knuffel(child)]
|
||||
pub default_column_width: Option<DefaultColumnWidth>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
@ -680,6 +683,19 @@ pub struct WindowRule {
|
||||
pub open_maximized: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_fullscreen: Option<bool>,
|
||||
|
||||
// Rules applied dynamically.
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub min_width: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub min_height: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_width: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_height: Option<u16>,
|
||||
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub draw_border_with_background: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone)]
|
||||
@ -766,6 +782,7 @@ pub enum Action {
|
||||
FocusWorkspaceDown,
|
||||
FocusWorkspaceUp,
|
||||
FocusWorkspace(#[knuffel(argument)] u8),
|
||||
FocusWorkspacePrevious,
|
||||
MoveWindowToWorkspaceDown,
|
||||
MoveWindowToWorkspaceUp,
|
||||
MoveWindowToWorkspace(#[knuffel(argument)] u8),
|
||||
@ -835,6 +852,7 @@ impl From<niri_ipc::Action> for Action {
|
||||
niri_ipc::Action::FocusWorkspaceDown => Self::FocusWorkspaceDown,
|
||||
niri_ipc::Action::FocusWorkspaceUp => Self::FocusWorkspaceUp,
|
||||
niri_ipc::Action::FocusWorkspace { index } => Self::FocusWorkspace(index),
|
||||
niri_ipc::Action::FocusWorkspacePrevious => Self::FocusWorkspacePrevious,
|
||||
niri_ipc::Action::MoveWindowToWorkspaceDown => Self::MoveWindowToWorkspaceDown,
|
||||
niri_ipc::Action::MoveWindowToWorkspaceUp => Self::MoveWindowToWorkspaceUp,
|
||||
niri_ipc::Action::MoveWindowToWorkspace { index } => Self::MoveWindowToWorkspace(index),
|
||||
@ -1599,6 +1617,7 @@ mod tests {
|
||||
|
||||
warp-mouse-to-focus
|
||||
focus-follows-mouse
|
||||
workspace-auto-back-and-forth
|
||||
}
|
||||
|
||||
output "eDP-1" {
|
||||
@ -1740,6 +1759,7 @@ mod tests {
|
||||
disable_power_key_handling: true,
|
||||
warp_mouse_to_focus: true,
|
||||
focus_follows_mouse: true,
|
||||
workspace_auto_back_and_forth: true,
|
||||
},
|
||||
outputs: vec![Output {
|
||||
off: false,
|
||||
|
@ -123,6 +123,8 @@ pub enum Action {
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
index: u8,
|
||||
},
|
||||
/// Focus the previous workspace.
|
||||
FocusWorkspacePrevious,
|
||||
/// Move the focused window to the workspace below.
|
||||
MoveWindowToWorkspaceDown,
|
||||
/// Move the focused window to the workspace above.
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri::layout::workspace::ColumnWidth;
|
||||
use niri::layout::Options;
|
||||
use niri::layout::{LayoutElement as _, Options};
|
||||
use niri::utils::get_monotonic_time;
|
||||
use niri_config::Color;
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
@ -73,12 +73,12 @@ impl Layout {
|
||||
|
||||
rv.add_window(TestWindow::freeform(0), Some(ColumnWidth::Proportion(0.3)));
|
||||
rv.add_window(TestWindow::freeform(1), Some(ColumnWidth::Proportion(0.3)));
|
||||
rv.layout.activate_window(&rv.windows[0]);
|
||||
rv.layout.activate_window(&0);
|
||||
|
||||
rv.add_step(500, |l| {
|
||||
let win = TestWindow::freeform(2);
|
||||
l.add_window(win.clone(), Some(ColumnWidth::Proportion(0.3)));
|
||||
l.layout.start_open_animation_for_window(&win);
|
||||
l.layout.start_open_animation_for_window(win.id());
|
||||
});
|
||||
|
||||
rv
|
||||
@ -91,7 +91,7 @@ impl Layout {
|
||||
rv.add_step(delay, move |l| {
|
||||
let win = TestWindow::freeform(delay as usize);
|
||||
l.add_window(win.clone(), Some(ColumnWidth::Proportion(0.3)));
|
||||
l.layout.start_open_animation_for_window(&win);
|
||||
l.layout.start_open_animation_for_window(win.id());
|
||||
});
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ impl Layout {
|
||||
rv.add_step(delay, move |l| {
|
||||
let win = TestWindow::freeform(delay as usize);
|
||||
l.add_window(win.clone(), Some(ColumnWidth::Proportion(0.5)));
|
||||
l.layout.start_open_animation_for_window(&win);
|
||||
l.layout.start_open_animation_for_window(win.id());
|
||||
});
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ impl Layout {
|
||||
let win = TestWindow::freeform(2);
|
||||
let right_of = l.windows[0].clone();
|
||||
l.add_window_right_of(&right_of, win.clone(), Some(ColumnWidth::Proportion(0.3)));
|
||||
l.layout.start_open_animation_for_window(&win);
|
||||
l.layout.start_open_animation_for_window(win.id());
|
||||
});
|
||||
|
||||
rv
|
||||
@ -138,7 +138,7 @@ impl Layout {
|
||||
let win = TestWindow::freeform(2);
|
||||
let right_of = l.windows[0].clone();
|
||||
l.add_window_right_of(&right_of, win.clone(), Some(ColumnWidth::Proportion(0.5)));
|
||||
l.layout.start_open_animation_for_window(&win);
|
||||
l.layout.start_open_animation_for_window(win.id());
|
||||
});
|
||||
|
||||
rv
|
||||
@ -147,7 +147,7 @@ impl Layout {
|
||||
fn add_window(&mut self, window: TestWindow, width: Option<ColumnWidth>) {
|
||||
self.layout.add_window(window.clone(), width, false);
|
||||
if window.communicate() {
|
||||
self.layout.update_window(&window);
|
||||
self.layout.update_window(window.id());
|
||||
}
|
||||
self.windows.push(window);
|
||||
}
|
||||
@ -159,9 +159,9 @@ impl Layout {
|
||||
width: Option<ColumnWidth>,
|
||||
) {
|
||||
self.layout
|
||||
.add_window_right_of(right_of, window.clone(), width, false);
|
||||
.add_window_right_of(right_of.id(), window.clone(), width, false);
|
||||
if window.communicate() {
|
||||
self.layout.update_window(&window);
|
||||
self.layout.update_window(window.id());
|
||||
}
|
||||
self.windows.push(window);
|
||||
}
|
||||
@ -183,7 +183,7 @@ impl TestCase for Layout {
|
||||
self.layout.update_output_size(&self.output);
|
||||
for win in &self.windows {
|
||||
if win.communicate() {
|
||||
self.layout.update_window(win);
|
||||
self.layout.update_window(win.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use std::rc::Rc;
|
||||
|
||||
use niri::layout::{LayoutElement, LayoutElementRenderElement};
|
||||
use niri::render_helpers::renderer::NiriRenderer;
|
||||
use niri::window::ResolvedWindowRules;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::{Id, Kind};
|
||||
use smithay::output::Output;
|
||||
@ -12,7 +13,6 @@ use smithay::utils::{Logical, Point, Scale, Size, Transform};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestWindowInner {
|
||||
id: usize,
|
||||
size: Size<i32, Logical>,
|
||||
requested_size: Option<Size<i32, Logical>>,
|
||||
min_size: Size<i32, Logical>,
|
||||
@ -24,7 +24,10 @@ struct TestWindowInner {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestWindow(Rc<RefCell<TestWindowInner>>);
|
||||
pub struct TestWindow {
|
||||
id: usize,
|
||||
inner: Rc<RefCell<TestWindowInner>>,
|
||||
}
|
||||
|
||||
impl TestWindow {
|
||||
pub fn freeform(id: usize) -> Self {
|
||||
@ -33,17 +36,19 @@ impl TestWindow {
|
||||
let max_size = Size::from((0, 0));
|
||||
let buffer = SolidColorBuffer::new(size, [0.15, 0.64, 0.41, 1.]);
|
||||
|
||||
Self(Rc::new(RefCell::new(TestWindowInner {
|
||||
Self {
|
||||
id,
|
||||
size,
|
||||
requested_size: None,
|
||||
min_size,
|
||||
max_size,
|
||||
buffer,
|
||||
pending_fullscreen: false,
|
||||
csd_shadow_width: 0,
|
||||
csd_shadow_buffer: SolidColorBuffer::new((0, 0), [0., 0., 0., 0.3]),
|
||||
})))
|
||||
inner: Rc::new(RefCell::new(TestWindowInner {
|
||||
size,
|
||||
requested_size: None,
|
||||
min_size,
|
||||
max_size,
|
||||
buffer,
|
||||
pending_fullscreen: false,
|
||||
csd_shadow_width: 0,
|
||||
csd_shadow_buffer: SolidColorBuffer::new((0, 0), [0., 0., 0., 0.3]),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fixed_size(id: usize) -> Self {
|
||||
@ -56,24 +61,24 @@ impl TestWindow {
|
||||
}
|
||||
|
||||
pub fn set_min_size(&self, size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().min_size = size;
|
||||
self.inner.borrow_mut().min_size = size;
|
||||
}
|
||||
|
||||
pub fn set_max_size(&self, size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().max_size = size;
|
||||
self.inner.borrow_mut().max_size = size;
|
||||
}
|
||||
|
||||
pub fn set_color(&self, color: [f32; 4]) {
|
||||
self.0.borrow_mut().buffer.set_color(color);
|
||||
self.inner.borrow_mut().buffer.set_color(color);
|
||||
}
|
||||
|
||||
pub fn set_csd_shadow_width(&self, width: i32) {
|
||||
self.0.borrow_mut().csd_shadow_width = width;
|
||||
self.inner.borrow_mut().csd_shadow_width = width;
|
||||
}
|
||||
|
||||
pub fn communicate(&self) -> bool {
|
||||
let mut rv = false;
|
||||
let mut inner = self.0.borrow_mut();
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
||||
let mut new_size = inner.size;
|
||||
|
||||
@ -117,15 +122,15 @@ impl TestWindow {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TestWindow {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.borrow().id == other.0.borrow().id
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutElement for TestWindow {
|
||||
type Id = usize;
|
||||
|
||||
fn id(&self) -> &Self::Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<i32, Logical> {
|
||||
self.0.borrow().size
|
||||
self.inner.borrow().size
|
||||
}
|
||||
|
||||
fn buf_loc(&self) -> Point<i32, Logical> {
|
||||
@ -142,7 +147,7 @@ impl LayoutElement for TestWindow {
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
) -> Vec<LayoutElementRenderElement<R>> {
|
||||
let inner = self.0.borrow();
|
||||
let inner = self.inner.borrow();
|
||||
|
||||
vec![
|
||||
SolidColorRenderElement::from_buffer(
|
||||
@ -166,20 +171,20 @@ impl LayoutElement for TestWindow {
|
||||
}
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().requested_size = Some(size);
|
||||
self.0.borrow_mut().pending_fullscreen = false;
|
||||
self.inner.borrow_mut().requested_size = Some(size);
|
||||
self.inner.borrow_mut().pending_fullscreen = false;
|
||||
}
|
||||
|
||||
fn request_fullscreen(&self, _size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().pending_fullscreen = true;
|
||||
self.inner.borrow_mut().pending_fullscreen = true;
|
||||
}
|
||||
|
||||
fn min_size(&self) -> Size<i32, Logical> {
|
||||
self.0.borrow().min_size
|
||||
self.inner.borrow().min_size
|
||||
}
|
||||
|
||||
fn max_size(&self) -> Size<i32, Logical> {
|
||||
self.0.borrow().max_size
|
||||
self.inner.borrow().max_size
|
||||
}
|
||||
|
||||
fn is_wl_surface(&self, _wl_surface: &WlSurface) -> bool {
|
||||
@ -198,11 +203,24 @@ impl LayoutElement for TestWindow {
|
||||
|
||||
fn set_offscreen_element_id(&self, _id: Option<Id>) {}
|
||||
|
||||
fn set_activated(&self, _active: bool) {}
|
||||
|
||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||
|
||||
fn send_pending_configure(&self) {}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.0.borrow().pending_fullscreen
|
||||
self.inner.borrow().pending_fullscreen
|
||||
}
|
||||
|
||||
fn refresh(&self) {}
|
||||
|
||||
fn rules(&self) -> &ResolvedWindowRules {
|
||||
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
|
||||
&EMPTY
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,11 @@ input {
|
||||
|
||||
// Focus windows and outputs automatically when moving the mouse into them.
|
||||
// focus-follows-mouse
|
||||
|
||||
// Uncomment this to enable workspace auto-back-and-forth.
|
||||
// If enabled, switching to the same workspace by index twice will switch back to the
|
||||
// previous workspace.
|
||||
// workspace-auto-back-and-forth
|
||||
}
|
||||
|
||||
// You can configure outputs by their name, which you can find
|
||||
@ -118,6 +123,9 @@ layout {
|
||||
// If you don't like that, you should uncomment `prefer-no-csd` below.
|
||||
// Niri will draw focus ring and border *around* windows that agree to omit their
|
||||
// client-side decorations.
|
||||
//
|
||||
// Alternatively, you can override it with a window rule called
|
||||
// `draw-border-with-background`.
|
||||
|
||||
// You can change how the focus ring looks.
|
||||
focus-ring {
|
||||
@ -357,6 +365,8 @@ animations {
|
||||
exclude app-id=r#"\.unwanted\."#
|
||||
|
||||
// Here are the properties that you can set on a window rule.
|
||||
// These properties apply once, when a window first opens.
|
||||
|
||||
// You can override the default column width.
|
||||
default-column-width { proportion 0.75; }
|
||||
|
||||
@ -372,6 +382,27 @@ animations {
|
||||
open-fullscreen true
|
||||
// You can also set this to false to prevent a window from opening fullscreen.
|
||||
// open-fullscreen false
|
||||
|
||||
// The following properties apply dynamically while a window is open.
|
||||
|
||||
// You can amend the window's minimum and maximum size in logical pixels.
|
||||
// Keep in mind that the window itself always has a final say in its size.
|
||||
// These values instruct niri to never ask the window to be smaller than
|
||||
// the minimum you set, or to be bigger than the maximum you set.
|
||||
min-width 100
|
||||
max-width 200
|
||||
min-height 300
|
||||
// Caveat: max-height will only apply to automatically-sized windows
|
||||
// if it is equal to min-height. Either set this equal to min-height,
|
||||
// or change the window height manually for this to apply.
|
||||
max-height 300
|
||||
|
||||
// Override whether the border and the focus ring draw with a background.
|
||||
// Set this to `true` to draw them as solid colored rectangles even for
|
||||
// windows which agreed to omit their client-side decorations.
|
||||
// Set this to `false` to draw them as borders around the window even for
|
||||
// windows which use client-side decorations.
|
||||
draw-border-with-background false
|
||||
}
|
||||
|
||||
// Here's a useful example. Work around WezTerm's initial configure bug
|
||||
@ -518,6 +549,9 @@ binds {
|
||||
// Alternatively, there are commands to move just a single window:
|
||||
// Mod+Ctrl+1 { move-window-to-workspace 1; }
|
||||
|
||||
// Switches focus between the current and the previous workspace.
|
||||
// Mod+Tab { focus-workspace-previous; }
|
||||
|
||||
Mod+Comma { consume-window-into-column; }
|
||||
Mod+Period { expel-window-from-column; }
|
||||
|
||||
|
@ -17,8 +17,7 @@ use smithay::wayland::shm::{ShmHandler, ShmState};
|
||||
use smithay::{delegate_compositor, delegate_shm};
|
||||
|
||||
use crate::niri::{ClientState, State};
|
||||
use crate::utils::clone2;
|
||||
use crate::window::{InitialConfigureState, Unmapped};
|
||||
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped};
|
||||
|
||||
impl CompositorHandler for State {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
@ -109,22 +108,22 @@ impl CompositorHandler for State {
|
||||
|
||||
window.on_commit();
|
||||
|
||||
let (width, is_full_width, output) =
|
||||
let (rules, width, is_full_width, output) =
|
||||
if let InitialConfigureState::Configured {
|
||||
rules,
|
||||
width,
|
||||
is_full_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, is_full_width, output)
|
||||
(rules, width, is_full_width, output)
|
||||
} else {
|
||||
error!("window map must happen after initial configure");
|
||||
(None, false, None)
|
||||
(ResolvedWindowRules::empty(), None, false, None)
|
||||
};
|
||||
|
||||
let parent = window
|
||||
@ -141,29 +140,30 @@ impl CompositorHandler for State {
|
||||
.filter(|(_, parent_output)| {
|
||||
output.is_none() || output.as_ref() == Some(*parent_output)
|
||||
})
|
||||
.map(|(window, _)| window.clone());
|
||||
.map(|(mapped, _)| mapped.window.clone());
|
||||
|
||||
let window = window.clone();
|
||||
let win = window.clone();
|
||||
let mapped = Mapped::new(window, rules);
|
||||
let window = mapped.window.clone();
|
||||
|
||||
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, is_full_width)
|
||||
.add_window_right_of(&p, mapped, width, is_full_width)
|
||||
} else if let Some(output) = &output {
|
||||
self.niri
|
||||
.layout
|
||||
.add_window_on_output(output, win, width, is_full_width);
|
||||
.add_window_on_output(output, mapped, width, is_full_width);
|
||||
Some(output)
|
||||
} else {
|
||||
self.niri.layout.add_window(win, width, is_full_width)
|
||||
self.niri.layout.add_window(mapped, width, is_full_width)
|
||||
};
|
||||
|
||||
if let Some(output) = output.cloned() {
|
||||
self.niri.layout.start_open_animation_for_window(&window);
|
||||
|
||||
let new_active_window = self.niri.layout.active_window().map(|(w, _)| w);
|
||||
let new_active_window =
|
||||
self.niri.layout.active_window().map(|(m, _)| &m.window);
|
||||
if new_active_window == Some(&window) {
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
@ -183,8 +183,9 @@ impl CompositorHandler for State {
|
||||
}
|
||||
|
||||
// This is a commit of a previously-mapped root or a non-toplevel root.
|
||||
if let Some(win_out) = self.niri.layout.find_window_and_output(surface) {
|
||||
let (window, output) = clone2(win_out);
|
||||
if let Some((mapped, output)) = self.niri.layout.find_window_and_output(surface) {
|
||||
let window = mapped.window.clone();
|
||||
let output = output.clone();
|
||||
|
||||
window.on_commit();
|
||||
|
||||
@ -224,7 +225,9 @@ impl CompositorHandler for State {
|
||||
|
||||
// This is a commit of a non-root or a non-toplevel root.
|
||||
let root_window_output = self.niri.layout.find_window_and_output(&root_surface);
|
||||
if let Some((window, output)) = root_window_output.map(clone2) {
|
||||
if let Some((mapped, output)) = root_window_output {
|
||||
let window = mapped.window.clone();
|
||||
let output = output.clone();
|
||||
window.on_commit();
|
||||
self.niri.layout.update_window(&window);
|
||||
self.niri.queue_redraw(output);
|
||||
|
@ -144,7 +144,7 @@ impl InputMethodHandler for State {
|
||||
self.niri
|
||||
.layout
|
||||
.find_window_and_output(parent)
|
||||
.map(|(window, _)| window.geometry())
|
||||
.map(|(mapped, _)| mapped.window.geometry())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@ -333,25 +333,24 @@ impl ForeignToplevelHandler for State {
|
||||
}
|
||||
|
||||
fn activate(&mut self, wl_surface: WlSurface) {
|
||||
if let Some((window, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
let window = window.clone();
|
||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.activate_window(&window);
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self, wl_surface: WlSurface) {
|
||||
if let Some((window, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
window.toplevel().expect("no x11 support").send_close();
|
||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
mapped.toplevel().send_close();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_fullscreen(&mut self, wl_surface: WlSurface, wl_output: Option<WlOutput>) {
|
||||
if let Some((window, current_output)) = self.niri.layout.find_window_and_output(&wl_surface)
|
||||
if let Some((mapped, current_output)) = self.niri.layout.find_window_and_output(&wl_surface)
|
||||
{
|
||||
if !window
|
||||
if !mapped
|
||||
.toplevel()
|
||||
.expect("no x11 support")
|
||||
.current_state()
|
||||
.capabilities
|
||||
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
|
||||
@ -359,13 +358,13 @@ impl ForeignToplevelHandler for State {
|
||||
return;
|
||||
}
|
||||
|
||||
let window = window.clone();
|
||||
let window = mapped.window.clone();
|
||||
|
||||
if let Some(requested_output) = wl_output.as_ref().and_then(Output::from_resource) {
|
||||
if &requested_output != current_output {
|
||||
self.niri
|
||||
.layout
|
||||
.move_window_to_output(window.clone(), &requested_output);
|
||||
.move_window_to_output(&window, &requested_output);
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,8 +373,8 @@ impl ForeignToplevelHandler for State {
|
||||
}
|
||||
|
||||
fn unset_fullscreen(&mut self, wl_surface: WlSurface) {
|
||||
if let Some((window, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
let window = window.clone();
|
||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.set_fullscreen(&window, false);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use niri_config::{Match, WindowRule};
|
||||
use smithay::desktop::{
|
||||
find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, LayerSurface,
|
||||
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy, Window,
|
||||
@ -20,7 +19,7 @@ use smithay::wayland::shell::wlr_layer::Layer;
|
||||
use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
|
||||
use smithay::wayland::shell::xdg::{
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
|
||||
XdgShellState, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
||||
XdgShellState, XdgToplevelSurfaceData,
|
||||
};
|
||||
use smithay::wayland::xdg_foreign::{XdgForeignHandler, XdgForeignState};
|
||||
use smithay::{
|
||||
@ -29,85 +28,8 @@ use smithay::{
|
||||
|
||||
use crate::layout::workspace::ColumnWidth;
|
||||
use crate::niri::{PopupGrabState, State};
|
||||
use crate::utils::clone2;
|
||||
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped};
|
||||
|
||||
fn window_matches(role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
||||
if let Some(app_id_re) = &m.app_id {
|
||||
let Some(app_id) = &role.app_id else {
|
||||
return false;
|
||||
};
|
||||
if !app_id_re.is_match(app_id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(title_re) = &m.title {
|
||||
let Some(title) = &role.title else {
|
||||
return false;
|
||||
};
|
||||
if !title_re.is_match(title) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn resolve_window_rules(
|
||||
rules: &[WindowRule],
|
||||
toplevel: &ToplevelSurface,
|
||||
) -> ResolvedWindowRules {
|
||||
let _span = tracy_client::span!("resolve_window_rules");
|
||||
|
||||
let mut resolved = ResolvedWindowRules::default();
|
||||
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
let role = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
let mut open_on_output = None;
|
||||
|
||||
for rule in rules {
|
||||
if !(rule.matches.is_empty() || rule.matches.iter().any(|m| window_matches(&role, m))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if rule.excludes.iter().any(|m| window_matches(&role, m)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(x) = rule
|
||||
.default_column_width
|
||||
.as_ref()
|
||||
.map(|d| d.0.map(ColumnWidth::from))
|
||||
{
|
||||
resolved.default_width = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.open_on_output.as_deref() {
|
||||
open_on_output = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.open_maximized {
|
||||
resolved.open_maximized = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.open_fullscreen {
|
||||
resolved.open_fullscreen = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
||||
});
|
||||
|
||||
resolved
|
||||
}
|
||||
|
||||
impl XdgShellHandler for State {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.niri.xdg_shell_state
|
||||
@ -213,9 +135,7 @@ impl XdgShellHandler for State {
|
||||
}
|
||||
|
||||
let layout_focus = self.niri.layout.focus();
|
||||
if Some(&root)
|
||||
!= layout_focus.map(|win| win.toplevel().expect("no x11 support").wl_surface())
|
||||
{
|
||||
if Some(&root) != layout_focus.map(|win| win.toplevel().wl_surface()) {
|
||||
let _ = PopupManager::dismiss_popup(&root, &popup);
|
||||
return;
|
||||
}
|
||||
@ -278,18 +198,18 @@ impl XdgShellHandler for State {
|
||||
) {
|
||||
let requested_output = wl_output.as_ref().and_then(Output::from_resource);
|
||||
|
||||
if let Some((window, current_output)) = self
|
||||
if let Some((mapped, current_output)) = self
|
||||
.niri
|
||||
.layout
|
||||
.find_window_and_output(toplevel.wl_surface())
|
||||
{
|
||||
let window = window.clone();
|
||||
let window = mapped.window.clone();
|
||||
|
||||
if let Some(requested_output) = requested_output {
|
||||
if &requested_output != current_output {
|
||||
self.niri
|
||||
.layout
|
||||
.move_window_to_output(window.clone(), &requested_output);
|
||||
.move_window_to_output(&window, &requested_output);
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,12 +278,12 @@ impl XdgShellHandler for State {
|
||||
}
|
||||
|
||||
fn unfullscreen_request(&mut self, toplevel: ToplevelSurface) {
|
||||
if let Some((window, _)) = self
|
||||
if let Some((mapped, _)) = self
|
||||
.niri
|
||||
.layout
|
||||
.find_window_and_output(toplevel.wl_surface())
|
||||
{
|
||||
let window = window.clone();
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.set_fullscreen(&window, false);
|
||||
|
||||
// A configure is required in response to this event regardless if there are pending
|
||||
@ -453,14 +373,16 @@ impl XdgShellHandler for State {
|
||||
.layout
|
||||
.find_window_and_output(surface.wl_surface());
|
||||
|
||||
let Some((window, output)) = win_out.map(clone2) else {
|
||||
let Some((mapped, output)) = win_out else {
|
||||
// I have no idea how this can happen, but I saw it happen once, in a weird interaction
|
||||
// involving laptop going to sleep and resuming.
|
||||
error!("toplevel missing from both unmapped_windows and layout");
|
||||
return;
|
||||
};
|
||||
let window = mapped.window.clone();
|
||||
let output = output.clone();
|
||||
|
||||
let active_window = self.niri.layout.active_window().map(|(w, _)| w);
|
||||
let active_window = self.niri.layout.active_window().map(|(m, _)| &m.window);
|
||||
let was_active = active_window == Some(&window);
|
||||
|
||||
self.niri.layout.remove_window(&window);
|
||||
@ -575,7 +497,7 @@ impl State {
|
||||
};
|
||||
|
||||
let config = self.niri.config.borrow();
|
||||
let rules = resolve_window_rules(&config.window_rules, toplevel);
|
||||
let rules = ResolvedWindowRules::compute(&config.window_rules, toplevel);
|
||||
|
||||
// Pick the target monitor. First, check if we had an output set in the window rules.
|
||||
let mon = rules
|
||||
@ -733,8 +655,8 @@ impl State {
|
||||
};
|
||||
|
||||
// Figure out if the root is a window or a layer surface.
|
||||
if let Some((window, output)) = self.niri.layout.find_window_and_output(&root) {
|
||||
self.unconstrain_window_popup(popup, window, output);
|
||||
if let Some((mapped, output)) = self.niri.layout.find_window_and_output(&root) {
|
||||
self.unconstrain_window_popup(popup, &mapped.window, output);
|
||||
} else if let Some((layer_surface, output)) = self.niri.layout.outputs().find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
let layer_surface = map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?;
|
||||
@ -808,12 +730,30 @@ impl State {
|
||||
}
|
||||
|
||||
pub fn update_window_rules(&mut self, toplevel: &ToplevelSurface) {
|
||||
let resolve = || resolve_window_rules(&self.niri.config.borrow().window_rules, toplevel);
|
||||
let resolve =
|
||||
|| ResolvedWindowRules::compute(&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();
|
||||
}
|
||||
} else if let Some((mapped, output)) = self
|
||||
.niri
|
||||
.layout
|
||||
.find_window_and_output_mut(toplevel.wl_surface())
|
||||
{
|
||||
let new_rules = resolve();
|
||||
if mapped.rules != new_rules {
|
||||
mapped.rules = new_rules;
|
||||
|
||||
let output = output.cloned();
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.update_window(&window);
|
||||
|
||||
if let Some(output) = output {
|
||||
self.niri.queue_redraw(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
src/input.rs
39
src/input.rs
@ -379,21 +379,24 @@ impl State {
|
||||
}
|
||||
Action::ScreenshotWindow => {
|
||||
let active = self.niri.layout.active_window();
|
||||
if let Some((window, output)) = active {
|
||||
if let Some((mapped, output)) = active {
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
if let Err(err) = self.niri.screenshot_window(renderer, output, window) {
|
||||
if let Err(err) =
|
||||
self.niri
|
||||
.screenshot_window(renderer, output, &mapped.window)
|
||||
{
|
||||
warn!("error taking screenshot: {err:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Action::CloseWindow => {
|
||||
if let Some(window) = self.niri.layout.focus() {
|
||||
window.toplevel().expect("no x11 support").send_close();
|
||||
if let Some(mapped) = self.niri.layout.focus() {
|
||||
mapped.toplevel().send_close();
|
||||
}
|
||||
}
|
||||
Action::FullscreenWindow => {
|
||||
let focus = self.niri.layout.focus().cloned();
|
||||
let focus = self.niri.layout.focus().map(|m| m.window.clone());
|
||||
if let Some(window) = focus {
|
||||
self.niri.layout.toggle_fullscreen(&window);
|
||||
// FIXME: granular
|
||||
@ -569,11 +572,23 @@ impl State {
|
||||
}
|
||||
Action::FocusWorkspace(idx) => {
|
||||
let idx = idx.saturating_sub(1) as usize;
|
||||
self.niri.layout.switch_workspace(idx);
|
||||
|
||||
let config = &self.niri.config;
|
||||
if config.borrow().input.workspace_auto_back_and_forth {
|
||||
self.niri.layout.switch_workspace_auto_back_and_forth(idx);
|
||||
} else {
|
||||
self.niri.layout.switch_workspace(idx);
|
||||
}
|
||||
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWorkspacePrevious => {
|
||||
self.niri.layout.switch_workspace_previous();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::MoveWorkspaceDown => {
|
||||
self.niri.layout.move_workspace_down();
|
||||
// FIXME: granular
|
||||
@ -1005,8 +1020,8 @@ impl State {
|
||||
let button_state = event.state();
|
||||
|
||||
if ButtonState::Pressed == button_state {
|
||||
if let Some(window) = self.niri.window_under_cursor() {
|
||||
let window = window.clone();
|
||||
if let Some(mapped) = self.niri.window_under_cursor() {
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.activate_window(&window);
|
||||
|
||||
// FIXME: granular.
|
||||
@ -1165,8 +1180,8 @@ impl State {
|
||||
tool.tip_down(serial, event.time_msec());
|
||||
|
||||
if let Some(pos) = self.niri.tablet_cursor_location {
|
||||
if let Some(window) = self.niri.window_under(pos) {
|
||||
let window = window.clone();
|
||||
if let Some(mapped) = self.niri.window_under(pos) {
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.activate_window(&window);
|
||||
|
||||
// FIXME: granular.
|
||||
@ -1523,8 +1538,8 @@ impl State {
|
||||
.output_under(touch_location)
|
||||
.next()
|
||||
.cloned();
|
||||
if let Some(window) = self.niri.window_under(touch_location) {
|
||||
let window = window.clone();
|
||||
if let Some(mapped) = self.niri.window_under(touch_location) {
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.activate_window(&window);
|
||||
|
||||
// FIXME: granular.
|
||||
|
@ -38,24 +38,18 @@ use niri_config::{CenterFocusedColumn, Config, Struts};
|
||||
use niri_ipc::SizeChange;
|
||||
use smithay::backend::renderer::element::solid::SolidColorRenderElement;
|
||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
||||
use smithay::backend::renderer::element::{AsRenderElements, Id};
|
||||
use smithay::desktop::space::SpaceElement;
|
||||
use smithay::desktop::Window;
|
||||
use smithay::backend::renderer::element::Id;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||
use smithay::wayland::shell::xdg::SurfaceCachedState;
|
||||
use smithay::utils::{Logical, Point, Scale, Size, Transform};
|
||||
|
||||
use self::monitor::Monitor;
|
||||
pub use self::monitor::MonitorRenderElement;
|
||||
use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace};
|
||||
use crate::niri::WindowOffscreenId;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::utils::output_size;
|
||||
use crate::window::ResolvedWindowRules;
|
||||
|
||||
pub mod focus_ring;
|
||||
pub mod monitor;
|
||||
@ -69,7 +63,13 @@ niri_render_elements! {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayoutElement: PartialEq {
|
||||
pub trait LayoutElement {
|
||||
/// Type that can be used as a unique ID of this element.
|
||||
type Id: PartialEq;
|
||||
|
||||
/// Unique ID of this element.
|
||||
fn id(&self) -> &Self::Id;
|
||||
|
||||
/// Visual size of the element.
|
||||
///
|
||||
/// This is what the user would consider the size, i.e. excluding CSD shadows and whatnot.
|
||||
@ -107,6 +107,10 @@ pub trait LayoutElement: PartialEq {
|
||||
fn output_enter(&self, output: &Output);
|
||||
fn output_leave(&self, output: &Output);
|
||||
fn set_offscreen_element_id(&self, id: Option<Id>);
|
||||
fn set_activated(&self, active: bool);
|
||||
fn set_bounds(&self, bounds: Size<i32, Logical>);
|
||||
|
||||
fn send_pending_configure(&self);
|
||||
|
||||
/// Whether the element is currently fullscreen.
|
||||
///
|
||||
@ -117,6 +121,11 @@ pub trait LayoutElement: PartialEq {
|
||||
///
|
||||
/// This *will* switch immediately after a [`LayoutElement::request_fullscreen()`] call.
|
||||
fn is_pending_fullscreen(&self) -> bool;
|
||||
|
||||
fn rules(&self) -> &ResolvedWindowRules;
|
||||
|
||||
/// Runs periodic clean-up tasks.
|
||||
fn refresh(&self);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -216,120 +225,6 @@ impl Options {
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutElement for Window {
|
||||
fn size(&self) -> Size<i32, Logical> {
|
||||
self.geometry().size
|
||||
}
|
||||
|
||||
fn buf_loc(&self) -> Point<i32, Logical> {
|
||||
Point::from((0, 0)) - self.geometry().loc
|
||||
}
|
||||
|
||||
fn is_in_input_region(&self, point: Point<f64, Logical>) -> bool {
|
||||
let surface_local = point + self.geometry().loc.to_f64();
|
||||
SpaceElement::is_in_input_region(self, &surface_local)
|
||||
}
|
||||
|
||||
fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
) -> Vec<LayoutElementRenderElement<R>> {
|
||||
let buf_pos = location - self.geometry().loc;
|
||||
self.render_elements(
|
||||
renderer,
|
||||
buf_pos.to_physical_precise_round(scale),
|
||||
scale,
|
||||
1.,
|
||||
)
|
||||
}
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
self.toplevel()
|
||||
.expect("no x11 support")
|
||||
.with_pending_state(|state| {
|
||||
state.size = Some(size);
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
});
|
||||
}
|
||||
|
||||
fn request_fullscreen(&self, size: Size<i32, Logical>) {
|
||||
self.toplevel()
|
||||
.expect("no x11 support")
|
||||
.with_pending_state(|state| {
|
||||
state.size = Some(size);
|
||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||
});
|
||||
}
|
||||
|
||||
fn min_size(&self) -> Size<i32, Logical> {
|
||||
with_states(
|
||||
self.toplevel().expect("no x11 support").wl_surface(),
|
||||
|state| {
|
||||
let curr = state.cached_state.current::<SurfaceCachedState>();
|
||||
curr.min_size
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn max_size(&self) -> Size<i32, Logical> {
|
||||
with_states(
|
||||
self.toplevel().expect("no x11 support").wl_surface(),
|
||||
|state| {
|
||||
let curr = state.cached_state.current::<SurfaceCachedState>();
|
||||
curr.max_size
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool {
|
||||
self.toplevel().expect("no x11 support").wl_surface() == wl_surface
|
||||
}
|
||||
|
||||
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform) {
|
||||
self.with_surfaces(|surface, data| {
|
||||
send_surface_state(surface, data, scale, transform);
|
||||
});
|
||||
}
|
||||
|
||||
fn has_ssd(&self) -> bool {
|
||||
self.toplevel()
|
||||
.expect("no x11 support")
|
||||
.current_state()
|
||||
.decoration_mode
|
||||
== Some(zxdg_toplevel_decoration_v1::Mode::ServerSide)
|
||||
}
|
||||
|
||||
fn output_enter(&self, output: &Output) {
|
||||
let overlap = Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX));
|
||||
SpaceElement::output_enter(self, output, overlap)
|
||||
}
|
||||
|
||||
fn output_leave(&self, output: &Output) {
|
||||
SpaceElement::output_leave(self, output)
|
||||
}
|
||||
|
||||
fn set_offscreen_element_id(&self, id: Option<Id>) {
|
||||
let data = self.user_data().get_or_insert(WindowOffscreenId::default);
|
||||
data.0.replace(id);
|
||||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
self.toplevel()
|
||||
.expect("no x11 support")
|
||||
.current_state()
|
||||
.states
|
||||
.contains(xdg_toplevel::State::Fullscreen)
|
||||
}
|
||||
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.toplevel()
|
||||
.expect("no x11 support")
|
||||
.with_pending_state(|state| state.states.contains(xdg_toplevel::State::Fullscreen))
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Layout<W> {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
Self::with_options(Options::from_config(config))
|
||||
@ -599,7 +494,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
/// Returns an output that the window was added to, if there were any outputs.
|
||||
pub fn add_window_right_of(
|
||||
&mut self,
|
||||
right_of: &W,
|
||||
right_of: &W::Id,
|
||||
window: W,
|
||||
width: Option<ColumnWidth>,
|
||||
is_full_width: bool,
|
||||
@ -681,13 +576,15 @@ impl<W: LayoutElement> Layout<W> {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn remove_window(&mut self, window: &W) {
|
||||
pub fn remove_window(&mut self, window: &W::Id) -> Option<W> {
|
||||
let mut rv = None;
|
||||
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for (idx, ws) in mon.workspaces.iter_mut().enumerate() {
|
||||
if ws.has_window(window) {
|
||||
ws.remove_window(window);
|
||||
rv = Some(ws.remove_window(window));
|
||||
|
||||
// Clean up empty workspaces that are not active and not last.
|
||||
if !ws.has_windows()
|
||||
@ -710,7 +607,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||
for (idx, ws) in workspaces.iter_mut().enumerate() {
|
||||
if ws.has_window(window) {
|
||||
ws.remove_window(window);
|
||||
rv = Some(ws.remove_window(window));
|
||||
|
||||
// Clean up empty workspaces.
|
||||
if !ws.has_windows() {
|
||||
@ -722,9 +619,11 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rv
|
||||
}
|
||||
|
||||
pub fn update_window(&mut self, window: &W) {
|
||||
pub fn update_window(&mut self, window: &W::Id) {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
@ -761,7 +660,33 @@ impl<W: LayoutElement> Layout<W> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn window_y(&self, window: &W) -> Option<i32> {
|
||||
pub fn find_window_and_output_mut(
|
||||
&mut self,
|
||||
wl_surface: &WlSurface,
|
||||
) -> Option<(&mut W, Option<&Output>)> {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mut mon.workspaces {
|
||||
if let Some(window) = ws.find_wl_surface_mut(wl_surface) {
|
||||
return Some((window, Some(&mon.output)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorSet::NoOutputs { workspaces } => {
|
||||
for ws in workspaces {
|
||||
if let Some(window) = ws.find_wl_surface_mut(wl_surface) {
|
||||
return Some((window, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn window_y(&self, window: &W::Id) -> Option<i32> {
|
||||
match &self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
@ -810,7 +735,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_window(&mut self, window: &W) {
|
||||
pub fn activate_window(&mut self, window: &W::Id) {
|
||||
let MonitorSet::Normal {
|
||||
monitors,
|
||||
active_monitor_idx,
|
||||
@ -932,6 +857,27 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_windows_mut(&mut self, mut f: impl FnMut(&mut W, Option<&Output>)) {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mut mon.workspaces {
|
||||
for win in ws.windows_mut() {
|
||||
f(win, Some(&mon.output));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorSet::NoOutputs { workspaces } => {
|
||||
for ws in workspaces {
|
||||
for win in ws.windows_mut() {
|
||||
f(win, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn active_monitor(&mut self) -> Option<&mut Monitor<W>> {
|
||||
let MonitorSet::Normal {
|
||||
monitors,
|
||||
@ -1165,6 +1111,20 @@ impl<W: LayoutElement> Layout<W> {
|
||||
monitor.switch_workspace(idx);
|
||||
}
|
||||
|
||||
pub fn switch_workspace_auto_back_and_forth(&mut self, idx: usize) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.switch_workspace_auto_back_and_forth(idx);
|
||||
}
|
||||
|
||||
pub fn switch_workspace_previous(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.switch_workspace_previous();
|
||||
}
|
||||
|
||||
pub fn consume_into_column(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
@ -1221,8 +1181,12 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
#[cfg(test)]
|
||||
fn verify_invariants(&self) {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::layout::monitor::WorkspaceSwitch;
|
||||
|
||||
let mut seen_workspace_id = HashSet::new();
|
||||
|
||||
let (monitors, &primary_idx, &active_monitor_idx) = match &self.monitor_set {
|
||||
MonitorSet::Normal {
|
||||
monitors,
|
||||
@ -1241,6 +1205,11 @@ impl<W: LayoutElement> Layout<W> {
|
||||
"workspace options must be synchronized with layout"
|
||||
);
|
||||
|
||||
assert!(
|
||||
seen_workspace_id.insert(workspace.id()),
|
||||
"workspace id must be unique"
|
||||
);
|
||||
|
||||
workspace.verify_invariants();
|
||||
}
|
||||
|
||||
@ -1325,6 +1294,11 @@ impl<W: LayoutElement> Layout<W> {
|
||||
"workspace options must be synchronized with layout"
|
||||
);
|
||||
|
||||
assert!(
|
||||
seen_workspace_id.insert(workspace.id()),
|
||||
"workspace id must be unique"
|
||||
);
|
||||
|
||||
workspace.verify_invariants();
|
||||
}
|
||||
}
|
||||
@ -1465,7 +1439,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_window_to_output(&mut self, window: W, output: &Output) {
|
||||
pub fn move_window_to_output(&mut self, window: &W::Id, output: &Output) {
|
||||
let mut width = None;
|
||||
let mut is_full_width = false;
|
||||
|
||||
@ -1473,7 +1447,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
for mon in &*monitors {
|
||||
for ws in &mon.workspaces {
|
||||
for col in &ws.columns {
|
||||
if col.contains(&window) {
|
||||
if col.contains(window) {
|
||||
width = Some(col.width);
|
||||
is_full_width = col.is_full_width;
|
||||
break;
|
||||
@ -1485,7 +1459,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
let Some(width) = width else { return };
|
||||
|
||||
self.remove_window(&window);
|
||||
let window = self.remove_window(window).unwrap();
|
||||
|
||||
if let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set {
|
||||
let new_idx = monitors
|
||||
@ -1529,6 +1503,8 @@ impl<W: LayoutElement> Layout<W> {
|
||||
.unwrap();
|
||||
let target = &mut monitors[target_idx];
|
||||
|
||||
target.previous_workspace_id = Some(target.workspaces[target.active_workspace_idx].id());
|
||||
|
||||
// Insert the workspace after the currently active one. Unless the currently active one is
|
||||
// the last empty workspace, then insert before.
|
||||
let target_ws_idx = min(target.active_workspace_idx + 1, target.workspaces.len() - 1);
|
||||
@ -1540,7 +1516,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
*active_monitor_idx = target_idx;
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) {
|
||||
pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
@ -1563,7 +1539,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_fullscreen(&mut self, window: &W) {
|
||||
pub fn toggle_fullscreen(&mut self, window: &W::Id) {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
@ -1716,14 +1692,14 @@ impl<W: LayoutElement> Layout<W> {
|
||||
monitor.move_workspace_up();
|
||||
}
|
||||
|
||||
pub fn start_open_animation_for_window(&mut self, window: &W) {
|
||||
pub fn start_open_animation_for_window(&mut self, window: &W::Id) {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mut mon.workspaces {
|
||||
for col in &mut ws.columns {
|
||||
for tile in &mut col.tiles {
|
||||
if tile.window() == window {
|
||||
if tile.window().id() == window {
|
||||
tile.start_open_animation();
|
||||
return;
|
||||
}
|
||||
@ -1734,13 +1710,11 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||
for ws in workspaces {
|
||||
if ws.has_window(window) {
|
||||
for col in &mut ws.columns {
|
||||
for tile in &mut col.tiles {
|
||||
if tile.window() == window {
|
||||
tile.start_open_animation();
|
||||
return;
|
||||
}
|
||||
for col in &mut ws.columns {
|
||||
for tile in &mut col.tiles {
|
||||
if tile.window().id() == window {
|
||||
tile.start_open_animation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1748,11 +1722,9 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout<Window> {
|
||||
pub fn refresh(&mut self) {
|
||||
let _span = tracy_client::span!("MonitorSet::refresh");
|
||||
let _span = tracy_client::span!("Layout::refresh");
|
||||
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal {
|
||||
@ -1795,6 +1767,7 @@ mod tests {
|
||||
use proptest::prelude::*;
|
||||
use proptest_derive::Arbitrary;
|
||||
use smithay::output::{Mode, PhysicalProperties, Subpixel};
|
||||
use smithay::utils::Rectangle;
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -1859,13 +1832,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TestWindow {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.id == other.0.id
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutElement for TestWindow {
|
||||
type Id = usize;
|
||||
|
||||
fn id(&self) -> &Self::Id {
|
||||
&self.0.id
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<i32, Logical> {
|
||||
self.0.bbox.get().size
|
||||
}
|
||||
@ -1920,6 +1893,12 @@ mod tests {
|
||||
|
||||
fn set_offscreen_element_id(&self, _id: Option<Id>) {}
|
||||
|
||||
fn set_activated(&self, _active: bool) {}
|
||||
|
||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||
|
||||
fn send_pending_configure(&self) {}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@ -1927,6 +1906,13 @@ mod tests {
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.0.pending_fullscreen.get()
|
||||
}
|
||||
|
||||
fn refresh(&self) {}
|
||||
|
||||
fn rules(&self) -> &ResolvedWindowRules {
|
||||
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
|
||||
&EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
|
||||
@ -2017,6 +2003,8 @@ mod tests {
|
||||
FocusWorkspaceDown,
|
||||
FocusWorkspaceUp,
|
||||
FocusWorkspace(#[proptest(strategy = "0..=4usize")] usize),
|
||||
FocusWorkspaceAutoBackAndForth(#[proptest(strategy = "0..=4usize")] usize),
|
||||
FocusWorkspacePrevious,
|
||||
MoveWindowToWorkspaceDown,
|
||||
MoveWindowToWorkspaceUp,
|
||||
MoveWindowToWorkspace(#[proptest(strategy = "0..=4usize")] usize),
|
||||
@ -2176,24 +2164,14 @@ mod tests {
|
||||
return;
|
||||
}
|
||||
|
||||
let right_of_win = TestWindow::new(
|
||||
right_of_id,
|
||||
Rectangle::default(),
|
||||
Size::default(),
|
||||
Size::default(),
|
||||
);
|
||||
let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1);
|
||||
layout.add_window_right_of(&right_of_win, win, None, false);
|
||||
layout.add_window_right_of(&right_of_id, win, None, false);
|
||||
}
|
||||
Op::CloseWindow(id) => {
|
||||
let dummy =
|
||||
TestWindow::new(id, Rectangle::default(), Size::default(), Size::default());
|
||||
layout.remove_window(&dummy);
|
||||
layout.remove_window(&id);
|
||||
}
|
||||
Op::FullscreenWindow(id) => {
|
||||
let dummy =
|
||||
TestWindow::new(id, Rectangle::default(), Size::default(), Size::default());
|
||||
layout.toggle_fullscreen(&dummy);
|
||||
layout.toggle_fullscreen(&id);
|
||||
}
|
||||
Op::FocusColumnLeft => layout.focus_left(),
|
||||
Op::FocusColumnRight => layout.focus_right(),
|
||||
@ -2219,6 +2197,10 @@ mod tests {
|
||||
Op::FocusWorkspaceDown => layout.switch_workspace_down(),
|
||||
Op::FocusWorkspaceUp => layout.switch_workspace_up(),
|
||||
Op::FocusWorkspace(idx) => layout.switch_workspace(idx),
|
||||
Op::FocusWorkspaceAutoBackAndForth(idx) => {
|
||||
layout.switch_workspace_auto_back_and_forth(idx)
|
||||
}
|
||||
Op::FocusWorkspacePrevious => layout.switch_workspace_previous(),
|
||||
Op::MoveWindowToWorkspaceDown => layout.move_to_workspace_down(),
|
||||
Op::MoveWindowToWorkspaceUp => layout.move_to_workspace_up(),
|
||||
Op::MoveWindowToWorkspace(idx) => layout.move_to_workspace(idx),
|
||||
@ -2248,7 +2230,7 @@ mod tests {
|
||||
Op::SetColumnWidth(change) => layout.set_column_width(change),
|
||||
Op::SetWindowHeight(change) => layout.set_window_height(change),
|
||||
Op::Communicate(id) => {
|
||||
let mut window = None;
|
||||
let mut update = false;
|
||||
match &mut layout.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
'outer: for mon in monitors {
|
||||
@ -2256,7 +2238,7 @@ mod tests {
|
||||
for win in ws.windows() {
|
||||
if win.0.id == id {
|
||||
if win.communicate() {
|
||||
window = Some(win.clone());
|
||||
update = true;
|
||||
}
|
||||
break 'outer;
|
||||
}
|
||||
@ -2269,7 +2251,7 @@ mod tests {
|
||||
for win in ws.windows() {
|
||||
if win.0.id == id {
|
||||
if win.communicate() {
|
||||
window = Some(win.clone());
|
||||
update = true;
|
||||
}
|
||||
break 'outer;
|
||||
}
|
||||
@ -2278,8 +2260,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(win) = window {
|
||||
layout.update_window(&win);
|
||||
if update {
|
||||
layout.update_window(&id);
|
||||
}
|
||||
}
|
||||
Op::MoveWorkspaceToOutput(id) => {
|
||||
|
@ -10,7 +10,8 @@ use smithay::output::Output;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale};
|
||||
|
||||
use super::workspace::{
|
||||
compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceRenderElement,
|
||||
compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceId,
|
||||
WorkspaceRenderElement,
|
||||
};
|
||||
use super::{LayoutElement, Options};
|
||||
use crate::animation::Animation;
|
||||
@ -35,6 +36,8 @@ pub struct Monitor<W: LayoutElement> {
|
||||
pub workspaces: Vec<Workspace<W>>,
|
||||
/// Index of the currently active workspace.
|
||||
pub active_workspace_idx: usize,
|
||||
/// ID of the previously active workspace.
|
||||
pub previous_workspace_id: Option<WorkspaceId>,
|
||||
/// In-progress switch between workspaces.
|
||||
pub workspace_switch: Option<WorkspaceSwitch>,
|
||||
/// Configurable properties of the layout.
|
||||
@ -89,6 +92,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
output,
|
||||
workspaces,
|
||||
active_workspace_idx: 0,
|
||||
previous_workspace_id: None,
|
||||
workspace_switch: None,
|
||||
options,
|
||||
}
|
||||
@ -114,6 +118,8 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
.map(|s| s.current_idx())
|
||||
.unwrap_or(self.active_workspace_idx as f64);
|
||||
|
||||
self.previous_workspace_id = Some(self.workspaces[self.active_workspace_idx].id());
|
||||
|
||||
self.active_workspace_idx = idx;
|
||||
|
||||
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
||||
@ -153,7 +159,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
|
||||
pub fn add_window_right_of(
|
||||
&mut self,
|
||||
right_of: &W,
|
||||
right_of: &W::Id,
|
||||
window: W,
|
||||
width: ColumnWidth,
|
||||
is_full_width: bool,
|
||||
@ -461,6 +467,11 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
));
|
||||
}
|
||||
|
||||
fn previous_workspace_idx(&self) -> Option<usize> {
|
||||
let id = self.previous_workspace_id?;
|
||||
self.workspaces.iter().position(|w| w.id() == id)
|
||||
}
|
||||
|
||||
pub fn switch_workspace(&mut self, idx: usize) {
|
||||
self.activate_workspace(min(idx, self.workspaces.len() - 1));
|
||||
// Don't animate this action.
|
||||
@ -469,6 +480,24 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.clean_up_workspaces();
|
||||
}
|
||||
|
||||
pub fn switch_workspace_auto_back_and_forth(&mut self, idx: usize) {
|
||||
let idx = min(idx, self.workspaces.len() - 1);
|
||||
|
||||
if idx == self.active_workspace_idx {
|
||||
if let Some(prev_idx) = self.previous_workspace_idx() {
|
||||
self.switch_workspace(prev_idx);
|
||||
}
|
||||
} else {
|
||||
self.switch_workspace(idx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch_workspace_previous(&mut self) {
|
||||
if let Some(idx) = self.previous_workspace_idx() {
|
||||
self.switch_workspace(idx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume_into_column(&mut self) {
|
||||
self.active_workspace().consume_into_column();
|
||||
}
|
||||
@ -564,8 +593,10 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.workspaces.push(ws);
|
||||
}
|
||||
|
||||
let previous_workspace_id = self.previous_workspace_id;
|
||||
self.activate_workspace(new_idx);
|
||||
self.workspace_switch = None;
|
||||
self.previous_workspace_id = previous_workspace_id;
|
||||
|
||||
self.clean_up_workspaces();
|
||||
}
|
||||
@ -584,8 +615,10 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.workspaces.push(ws);
|
||||
}
|
||||
|
||||
let previous_workspace_id = self.previous_workspace_id;
|
||||
self.activate_workspace(new_idx);
|
||||
self.workspace_switch = None;
|
||||
self.previous_workspace_id = previous_workspace_id;
|
||||
|
||||
self.clean_up_workspaces();
|
||||
}
|
||||
@ -833,6 +866,8 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
gesture.center_idx as f64 + current_pos,
|
||||
);
|
||||
|
||||
self.previous_workspace_id = Some(self.workspaces[self.active_workspace_idx].id());
|
||||
|
||||
self.active_workspace_idx = new_idx;
|
||||
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
||||
gesture.current_idx,
|
||||
|
@ -85,11 +85,22 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
|
||||
let draw_border_with_background = self
|
||||
.window
|
||||
.rules()
|
||||
.draw_border_with_background
|
||||
.unwrap_or_else(|| !self.window.has_ssd());
|
||||
self.border
|
||||
.update(self.window.size(), self.window.has_ssd());
|
||||
.update(self.window.size(), !draw_border_with_background);
|
||||
self.border.set_active(is_active);
|
||||
|
||||
self.focus_ring.update(self.tile_size(), self.has_ssd());
|
||||
let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
|
||||
false
|
||||
} else {
|
||||
draw_border_with_background
|
||||
};
|
||||
self.focus_ring
|
||||
.update(self.tile_size(), !draw_focus_ring_with_background);
|
||||
self.focus_ring.set_active(is_active);
|
||||
|
||||
match &mut self.open_animation {
|
||||
@ -121,6 +132,10 @@ impl<W: LayoutElement> Tile<W> {
|
||||
&self.window
|
||||
}
|
||||
|
||||
pub fn window_mut(&mut self) -> &mut W {
|
||||
&mut self.window
|
||||
}
|
||||
|
||||
pub fn into_window(self) -> W {
|
||||
self.window
|
||||
}
|
||||
@ -291,8 +306,15 @@ impl<W: LayoutElement> Tile<W> {
|
||||
size
|
||||
}
|
||||
|
||||
pub fn has_ssd(&self) -> bool {
|
||||
self.effective_border_width().is_some() || self.window.has_ssd()
|
||||
pub fn draw_border_with_background(&self) -> bool {
|
||||
if self.effective_border_width().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.window
|
||||
.rules()
|
||||
.draw_border_with_background
|
||||
.unwrap_or_else(|| !self.window.has_ssd())
|
||||
}
|
||||
|
||||
fn render_inner<R: NiriRenderer>(
|
||||
|
@ -5,12 +5,12 @@ use std::time::Duration;
|
||||
|
||||
use niri_config::{CenterFocusedColumn, PresetWidth, Struts};
|
||||
use niri_ipc::SizeChange;
|
||||
use smithay::desktop::space::SpaceElement;
|
||||
use smithay::desktop::{layer_map_for_output, Window};
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
||||
use smithay::wayland::compositor::send_surface_state;
|
||||
|
||||
use super::tile::{Tile, TileRenderElement};
|
||||
use super::{LayoutElement, Options};
|
||||
@ -18,6 +18,7 @@ use crate::animation::Animation;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::swipe_tracker::SwipeTracker;
|
||||
use crate::utils::id::IdCounter;
|
||||
use crate::utils::output_size;
|
||||
|
||||
/// Amount of touchpad movement to scroll the view for the width of one working area.
|
||||
@ -76,11 +77,25 @@ pub struct Workspace<W: LayoutElement> {
|
||||
|
||||
/// Configurable properties of the layout.
|
||||
pub options: Rc<Options>,
|
||||
|
||||
/// Unique ID of this workspace.
|
||||
id: WorkspaceId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OutputId(String);
|
||||
|
||||
static WORKSPACE_ID_COUNTER: IdCounter = IdCounter::new();
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct WorkspaceId(u32);
|
||||
|
||||
impl WorkspaceId {
|
||||
fn next() -> WorkspaceId {
|
||||
WorkspaceId(WORKSPACE_ID_COUNTER.next())
|
||||
}
|
||||
}
|
||||
|
||||
niri_render_elements! {
|
||||
WorkspaceRenderElement<R> => {
|
||||
Tile = TileRenderElement<R>,
|
||||
@ -225,6 +240,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
view_offset_adj: None,
|
||||
activate_prev_column_on_removal: None,
|
||||
options,
|
||||
id: WorkspaceId::next(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,9 +256,14 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
view_offset_adj: None,
|
||||
activate_prev_column_on_removal: None,
|
||||
options,
|
||||
id: WorkspaceId::next(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WorkspaceId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
|
||||
if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj {
|
||||
anim.set_current_time(current_time);
|
||||
@ -279,6 +300,13 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
.map(Tile::window)
|
||||
}
|
||||
|
||||
pub fn windows_mut(&mut self) -> impl Iterator<Item = &mut W> + '_ {
|
||||
self.columns
|
||||
.iter_mut()
|
||||
.flat_map(|col| col.tiles.iter_mut())
|
||||
.map(Tile::window_mut)
|
||||
}
|
||||
|
||||
pub fn set_output(&mut self, output: Option<Output>) {
|
||||
if self.output == output {
|
||||
return;
|
||||
@ -387,7 +415,11 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
|
||||
pub fn configure_new_window(&self, window: &Window, width: Option<ColumnWidth>) {
|
||||
if let Some(output) = self.output.as_ref() {
|
||||
set_preferred_scale_transform(window, output);
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let transform = output.current_transform();
|
||||
window.with_surfaces(|surface, data| {
|
||||
send_surface_state(surface, data, scale, transform);
|
||||
});
|
||||
}
|
||||
|
||||
window
|
||||
@ -556,14 +588,18 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
self.windows().next().is_some()
|
||||
}
|
||||
|
||||
pub fn has_window(&self, window: &W) -> bool {
|
||||
self.windows().any(|win| win == window)
|
||||
pub fn has_window(&self, window: &W::Id) -> bool {
|
||||
self.windows().any(|win| win.id() == window)
|
||||
}
|
||||
|
||||
pub fn find_wl_surface(&self, wl_surface: &WlSurface) -> Option<&W> {
|
||||
self.windows().find(|win| win.is_wl_surface(wl_surface))
|
||||
}
|
||||
|
||||
pub fn find_wl_surface_mut(&mut self, wl_surface: &WlSurface) -> Option<&mut W> {
|
||||
self.windows_mut().find(|win| win.is_wl_surface(wl_surface))
|
||||
}
|
||||
|
||||
/// Computes the X position of the windows in the given column, in logical coordinates.
|
||||
fn column_x(&self, column_idx: usize) -> i32 {
|
||||
let mut x = 0;
|
||||
@ -637,7 +673,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
|
||||
pub fn add_window_right_of(
|
||||
&mut self,
|
||||
right_of: &W,
|
||||
right_of: &W::Id,
|
||||
window: W,
|
||||
width: ColumnWidth,
|
||||
is_full_width: bool,
|
||||
@ -814,7 +850,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
column
|
||||
}
|
||||
|
||||
pub fn remove_window(&mut self, window: &W) {
|
||||
pub fn remove_window(&mut self, window: &W::Id) -> W {
|
||||
let column_idx = self
|
||||
.columns
|
||||
.iter()
|
||||
@ -823,10 +859,10 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
let column = &self.columns[column_idx];
|
||||
|
||||
let window_idx = column.position(window).unwrap();
|
||||
self.remove_window_by_idx(column_idx, window_idx);
|
||||
self.remove_window_by_idx(column_idx, window_idx)
|
||||
}
|
||||
|
||||
pub fn update_window(&mut self, window: &W) {
|
||||
pub fn update_window(&mut self, window: &W::Id) {
|
||||
let (idx, column) = self
|
||||
.columns
|
||||
.iter_mut()
|
||||
@ -848,7 +884,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_window(&mut self, window: &W) {
|
||||
pub fn activate_window(&mut self, window: &W::Id) {
|
||||
let column_idx = self
|
||||
.columns
|
||||
.iter()
|
||||
@ -1212,7 +1248,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
self.columns[self.active_column_idx].set_window_height(change);
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) {
|
||||
pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) {
|
||||
let (mut col_idx, tile_idx) = self
|
||||
.columns
|
||||
.iter()
|
||||
@ -1254,7 +1290,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
col.set_fullscreen(is_fullscreen);
|
||||
}
|
||||
|
||||
pub fn toggle_fullscreen(&mut self, window: &W) {
|
||||
pub fn toggle_fullscreen(&mut self, window: &W::Id) {
|
||||
let col = self
|
||||
.columns
|
||||
.iter_mut()
|
||||
@ -1513,9 +1549,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Workspace<Window> {
|
||||
pub fn refresh(&self, is_active: bool) {
|
||||
let bounds = self.toplevel_bounds();
|
||||
|
||||
@ -1527,15 +1561,8 @@ impl Workspace<Window> {
|
||||
&& col.active_tile_idx == tile_idx;
|
||||
win.set_activated(active);
|
||||
|
||||
win.toplevel()
|
||||
.expect("no x11 support")
|
||||
.with_pending_state(|state| {
|
||||
state.bounds = Some(bounds);
|
||||
});
|
||||
|
||||
win.toplevel()
|
||||
.expect("no x11 support")
|
||||
.send_pending_configure();
|
||||
win.set_bounds(bounds);
|
||||
win.send_pending_configure();
|
||||
win.refresh();
|
||||
}
|
||||
}
|
||||
@ -1633,18 +1660,21 @@ impl<W: LayoutElement> Column<W> {
|
||||
self.tiles.iter().any(Tile::are_animations_ongoing)
|
||||
}
|
||||
|
||||
pub fn contains(&self, window: &W) -> bool {
|
||||
self.tiles.iter().map(Tile::window).any(|win| win == window)
|
||||
}
|
||||
|
||||
pub fn position(&self, window: &W) -> Option<usize> {
|
||||
pub fn contains(&self, window: &W::Id) -> bool {
|
||||
self.tiles
|
||||
.iter()
|
||||
.map(Tile::window)
|
||||
.position(|win| win == window)
|
||||
.any(|win| win.id() == window)
|
||||
}
|
||||
|
||||
fn activate_window(&mut self, window: &W) {
|
||||
pub fn position(&self, window: &W::Id) -> Option<usize> {
|
||||
self.tiles
|
||||
.iter()
|
||||
.map(Tile::window)
|
||||
.position(|win| win.id() == window)
|
||||
}
|
||||
|
||||
fn activate_window(&mut self, window: &W::Id) {
|
||||
let idx = self.position(window).unwrap();
|
||||
self.active_tile_idx = idx;
|
||||
}
|
||||
@ -1657,11 +1687,11 @@ impl<W: LayoutElement> Column<W> {
|
||||
self.update_tile_sizes();
|
||||
}
|
||||
|
||||
fn update_window(&mut self, window: &W) {
|
||||
fn update_window(&mut self, window: &W::Id) {
|
||||
let tile = self
|
||||
.tiles
|
||||
.iter_mut()
|
||||
.find(|tile| tile.window() == window)
|
||||
.find(|tile| tile.window().id() == window)
|
||||
.unwrap();
|
||||
tile.update_window();
|
||||
}
|
||||
|
61
src/niri.rs
61
src/niri.rs
@ -114,7 +114,7 @@ use crate::utils::spawning::CHILD_ENV;
|
||||
use crate::utils::{
|
||||
center, center_f64, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
|
||||
};
|
||||
use crate::window::Unmapped;
|
||||
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped};
|
||||
use crate::{animation, niri_render_elements};
|
||||
|
||||
const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.];
|
||||
@ -138,7 +138,7 @@ pub struct Niri {
|
||||
|
||||
// Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it,
|
||||
// however it may have none (when there are no outputs connected) or mutiple (when mirroring).
|
||||
pub layout: Layout<Window>,
|
||||
pub layout: Layout<Mapped>,
|
||||
|
||||
// This space does not actually contain any windows, but all outputs are mapped into it
|
||||
// according to their global position.
|
||||
@ -631,7 +631,7 @@ impl State {
|
||||
self.niri
|
||||
.layout
|
||||
.focus()
|
||||
.map(|win| win.toplevel().expect("no x11 support").wl_surface().clone())
|
||||
.map(|win| win.toplevel().wl_surface().clone())
|
||||
.map(|surface| KeyboardFocus::Layout {
|
||||
surface: Some(surface),
|
||||
})
|
||||
@ -761,6 +761,7 @@ impl State {
|
||||
let mut reload_xkb = None;
|
||||
let mut libinput_config_changed = false;
|
||||
let mut output_config_changed = false;
|
||||
let mut window_rules_changed = false;
|
||||
let mut old_config = self.niri.config.borrow_mut();
|
||||
|
||||
// Reload the cursor.
|
||||
@ -802,6 +803,10 @@ impl State {
|
||||
self.niri.hotkey_overlay.on_hotkey_config_updated();
|
||||
}
|
||||
|
||||
if config.window_rules != old_config.window_rules {
|
||||
window_rules_changed = true;
|
||||
}
|
||||
|
||||
*old_config = config;
|
||||
|
||||
// Release the borrow.
|
||||
@ -865,6 +870,30 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
if window_rules_changed {
|
||||
let _span = tracy_client::span!("recompute window rules");
|
||||
|
||||
let window_rules = &self.niri.config.borrow().window_rules;
|
||||
|
||||
for unmapped in self.niri.unmapped_windows.values_mut() {
|
||||
if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state {
|
||||
*rules = ResolvedWindowRules::compute(
|
||||
window_rules,
|
||||
unmapped.window.toplevel().expect("no X11 support"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut windows = vec![];
|
||||
self.niri.layout.with_windows_mut(|mapped, _| {
|
||||
mapped.rules = ResolvedWindowRules::compute(window_rules, mapped.toplevel());
|
||||
windows.push(mapped.window.clone());
|
||||
});
|
||||
for win in windows {
|
||||
self.niri.layout.update_window(&win);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't really update xdg-decoration settings since we have to hide the globals for CSD
|
||||
// due to the SDL2 bug... I don't imagine clients are prepared for the xdg-decoration
|
||||
// global suddenly appearing? Either way, right now it's live-reloaded in a sense that new
|
||||
@ -1591,7 +1620,7 @@ impl Niri {
|
||||
///
|
||||
/// The cursor may be inside the window's activation region, but not within the window's input
|
||||
/// region.
|
||||
pub fn window_under(&self, pos: Point<f64, Logical>) -> Option<&Window> {
|
||||
pub fn window_under(&self, pos: Point<f64, Logical>) -> Option<&Mapped> {
|
||||
if self.is_locked() || self.screenshot_ui.is_open() {
|
||||
return None;
|
||||
}
|
||||
@ -1618,7 +1647,7 @@ impl Niri {
|
||||
///
|
||||
/// The cursor may be inside the window's activation region, but not within the window's input
|
||||
/// region.
|
||||
pub fn window_under_cursor(&self) -> Option<&Window> {
|
||||
pub fn window_under_cursor(&self) -> Option<&Mapped> {
|
||||
let pos = self.seat.get_pointer().unwrap().current_location();
|
||||
self.window_under(pos)
|
||||
}
|
||||
@ -1684,8 +1713,9 @@ impl Niri {
|
||||
let window_under = || {
|
||||
self.layout
|
||||
.window_under(output, pos_within_output)
|
||||
.and_then(|(window, win_pos_within_output)| {
|
||||
.and_then(|(mapped, win_pos_within_output)| {
|
||||
let win_pos_within_output = win_pos_within_output?;
|
||||
let window = &mapped.window;
|
||||
window
|
||||
.surface_under(
|
||||
pos_within_output - win_pos_within_output.to_f64(),
|
||||
@ -2409,7 +2439,8 @@ impl Niri {
|
||||
// The reason to do this at all is that it keeps track of whether the surface is visible or
|
||||
// not in a unified way with the pointer surfaces, which makes the logic elsewhere simpler.
|
||||
|
||||
for win in self.layout.windows_for_output(output) {
|
||||
for mapped in self.layout.windows_for_output(output) {
|
||||
let win = &mapped.window;
|
||||
let offscreen_id = win
|
||||
.user_data()
|
||||
.get_or_insert(WindowOffscreenId::default)
|
||||
@ -2480,8 +2511,8 @@ impl Niri {
|
||||
// We can unconditionally send the current output's feedback to regular and layer-shell
|
||||
// surfaces, as they can only be displayed on a single output at a time. Even if a surface
|
||||
// is currently invisible, this is the DMABUF feedback that it should know about.
|
||||
for win in self.layout.windows_for_output(output) {
|
||||
win.send_dmabuf_feedback(
|
||||
for mapped in self.layout.windows_for_output(output) {
|
||||
mapped.window.send_dmabuf_feedback(
|
||||
output,
|
||||
|_, _| Some(output.clone()),
|
||||
|surface, _| {
|
||||
@ -2600,8 +2631,8 @@ impl Niri {
|
||||
|
||||
let frame_callback_time = get_monotonic_time();
|
||||
|
||||
for win in self.layout.windows_for_output(output) {
|
||||
win.send_frame(
|
||||
for mapped in self.layout.windows_for_output(output) {
|
||||
mapped.window.send_frame(
|
||||
output,
|
||||
frame_callback_time,
|
||||
FRAME_CALLBACK_THROTTLE,
|
||||
@ -2666,8 +2697,8 @@ impl Niri {
|
||||
|
||||
let frame_callback_time = get_monotonic_time();
|
||||
|
||||
self.layout.with_windows(|win, _| {
|
||||
win.send_frame(
|
||||
self.layout.with_windows(|mapped, _| {
|
||||
mapped.window.send_frame(
|
||||
output,
|
||||
frame_callback_time,
|
||||
FRAME_CALLBACK_THROTTLE,
|
||||
@ -2746,8 +2777,8 @@ impl Niri {
|
||||
);
|
||||
}
|
||||
|
||||
for win in self.layout.windows_for_output(output) {
|
||||
win.take_presentation_feedback(
|
||||
for mapped in self.layout.windows_for_output(output) {
|
||||
mapped.window.take_presentation_feedback(
|
||||
&mut feedback,
|
||||
surface_primary_scanout_output,
|
||||
|surface, _| {
|
||||
|
@ -95,8 +95,8 @@ pub fn refresh(state: &mut State) {
|
||||
// Save the focused window for last, this way when the focus changes, we will first deactivate
|
||||
// the previous window and only then activate the newly focused window.
|
||||
let mut focused = None;
|
||||
state.niri.layout.with_windows(|window, output| {
|
||||
let wl_surface = window.toplevel().expect("no x11 support").wl_surface();
|
||||
state.niri.layout.with_windows(|mapped, output| {
|
||||
let wl_surface = mapped.toplevel().wl_surface();
|
||||
|
||||
with_states(wl_surface, |states| {
|
||||
let role = states
|
||||
@ -107,7 +107,7 @@ pub fn refresh(state: &mut State) {
|
||||
.unwrap();
|
||||
|
||||
if state.niri.keyboard_focus.surface() == Some(wl_surface) {
|
||||
focused = Some((window.clone(), output.cloned()));
|
||||
focused = Some((mapped.window.clone(), output.cloned()));
|
||||
} else {
|
||||
refresh_toplevel(protocol_state, wl_surface, &role, output, false);
|
||||
}
|
||||
|
27
src/utils/id.rs
Normal file
27
src/utils/id.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
/// Counter that returns unique IDs.
|
||||
///
|
||||
/// Under the hood it uses a `u32` that will eventually wrap around. When incrementing it once a
|
||||
/// second, it will wrap around after about 136 years.
|
||||
pub struct IdCounter {
|
||||
value: AtomicU32,
|
||||
}
|
||||
|
||||
impl IdCounter {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
value: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> u32 {
|
||||
self.value.fetch_add(1, Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IdCounter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
@ -14,15 +14,12 @@ use smithay::output::Output;
|
||||
use smithay::reexports::rustix::time::{clock_gettime, ClockId};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||
|
||||
pub mod id;
|
||||
pub mod spawning;
|
||||
pub mod watcher;
|
||||
|
||||
pub static IS_SYSTEMD_SERVICE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn clone2<T: Clone, U: Clone>(t: (&T, &U)) -> (T, U) {
|
||||
(t.0.clone(), t.1.clone())
|
||||
}
|
||||
|
||||
pub fn version() -> String {
|
||||
format!(
|
||||
"{} ({})",
|
||||
|
191
src/window/mapped.rs
Normal file
191
src/window/mapped.rs
Normal file
@ -0,0 +1,191 @@
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use smithay::backend::renderer::element::{AsRenderElements as _, Id};
|
||||
use smithay::desktop::space::SpaceElement as _;
|
||||
use smithay::desktop::Window;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
|
||||
|
||||
use super::ResolvedWindowRules;
|
||||
use crate::layout::{LayoutElement, LayoutElementRenderElement};
|
||||
use crate::niri::WindowOffscreenId;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mapped {
|
||||
pub window: Window,
|
||||
|
||||
/// Up-to-date rules.
|
||||
pub rules: ResolvedWindowRules,
|
||||
}
|
||||
|
||||
impl Mapped {
|
||||
pub fn new(window: Window, rules: ResolvedWindowRules) -> Self {
|
||||
Self { window, rules }
|
||||
}
|
||||
|
||||
pub fn toplevel(&self) -> &ToplevelSurface {
|
||||
self.window.toplevel().expect("no X11 support")
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutElement for Mapped {
|
||||
type Id = Window;
|
||||
|
||||
fn id(&self) -> &Self::Id {
|
||||
&self.window
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<i32, Logical> {
|
||||
self.window.geometry().size
|
||||
}
|
||||
|
||||
fn buf_loc(&self) -> Point<i32, Logical> {
|
||||
Point::from((0, 0)) - self.window.geometry().loc
|
||||
}
|
||||
|
||||
fn is_in_input_region(&self, point: Point<f64, Logical>) -> bool {
|
||||
let surface_local = point + self.window.geometry().loc.to_f64();
|
||||
self.window.is_in_input_region(&surface_local)
|
||||
}
|
||||
|
||||
fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
) -> Vec<LayoutElementRenderElement<R>> {
|
||||
let buf_pos = location - self.window.geometry().loc;
|
||||
self.window.render_elements(
|
||||
renderer,
|
||||
buf_pos.to_physical_precise_round(scale),
|
||||
scale,
|
||||
1.,
|
||||
)
|
||||
}
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
self.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(size);
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
});
|
||||
}
|
||||
|
||||
fn request_fullscreen(&self, size: Size<i32, Logical>) {
|
||||
self.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(size);
|
||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||
});
|
||||
}
|
||||
|
||||
fn min_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = with_states(self.toplevel().wl_surface(), |state| {
|
||||
let curr = state.cached_state.current::<SurfaceCachedState>();
|
||||
curr.min_size
|
||||
});
|
||||
|
||||
if let Some(x) = self.rules.min_width {
|
||||
size.w = max(size.w, i32::from(x));
|
||||
}
|
||||
if let Some(x) = self.rules.min_height {
|
||||
size.h = max(size.h, i32::from(x));
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
fn max_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = with_states(self.toplevel().wl_surface(), |state| {
|
||||
let curr = state.cached_state.current::<SurfaceCachedState>();
|
||||
curr.max_size
|
||||
});
|
||||
|
||||
if let Some(x) = self.rules.max_width {
|
||||
if size.w == 0 {
|
||||
size.w = i32::from(x);
|
||||
} else if x > 0 {
|
||||
size.w = min(size.w, i32::from(x));
|
||||
}
|
||||
}
|
||||
if let Some(x) = self.rules.max_height {
|
||||
if size.h == 0 {
|
||||
size.h = i32::from(x);
|
||||
} else if x > 0 {
|
||||
size.h = min(size.h, i32::from(x));
|
||||
}
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool {
|
||||
self.toplevel().wl_surface() == wl_surface
|
||||
}
|
||||
|
||||
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform) {
|
||||
self.window.with_surfaces(|surface, data| {
|
||||
send_surface_state(surface, data, scale, transform);
|
||||
});
|
||||
}
|
||||
|
||||
fn has_ssd(&self) -> bool {
|
||||
self.toplevel().current_state().decoration_mode
|
||||
== Some(zxdg_toplevel_decoration_v1::Mode::ServerSide)
|
||||
}
|
||||
|
||||
fn output_enter(&self, output: &Output) {
|
||||
let overlap = Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX));
|
||||
self.window.output_enter(output, overlap)
|
||||
}
|
||||
|
||||
fn output_leave(&self, output: &Output) {
|
||||
self.window.output_leave(output)
|
||||
}
|
||||
|
||||
fn set_offscreen_element_id(&self, id: Option<Id>) {
|
||||
let data = self
|
||||
.window
|
||||
.user_data()
|
||||
.get_or_insert(WindowOffscreenId::default);
|
||||
data.0.replace(id);
|
||||
}
|
||||
|
||||
fn set_activated(&self, active: bool) {
|
||||
self.window.set_activated(active);
|
||||
}
|
||||
|
||||
fn set_bounds(&self, bounds: Size<i32, Logical>) {
|
||||
self.toplevel().with_pending_state(|state| {
|
||||
state.bounds = Some(bounds);
|
||||
});
|
||||
}
|
||||
|
||||
fn send_pending_configure(&self) {
|
||||
self.toplevel().send_pending_configure();
|
||||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
self.toplevel()
|
||||
.current_state()
|
||||
.states
|
||||
.contains(xdg_toplevel::State::Fullscreen)
|
||||
}
|
||||
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.toplevel()
|
||||
.with_pending_state(|state| state.states.contains(xdg_toplevel::State::Fullscreen))
|
||||
}
|
||||
|
||||
fn refresh(&self) {
|
||||
self.window.refresh();
|
||||
}
|
||||
|
||||
fn rules(&self) -> &ResolvedWindowRules {
|
||||
&self.rules
|
||||
}
|
||||
}
|
155
src/window/mod.rs
Normal file
155
src/window/mod.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use niri_config::{Match, WindowRule};
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::shell::xdg::{
|
||||
ToplevelSurface, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
||||
};
|
||||
|
||||
use crate::layout::workspace::ColumnWidth;
|
||||
|
||||
pub mod mapped;
|
||||
pub use mapped::Mapped;
|
||||
|
||||
pub mod unmapped;
|
||||
pub use unmapped::{InitialConfigureState, Unmapped};
|
||||
|
||||
/// Rules fully resolved for a window.
|
||||
#[derive(Debug, PartialEq)]
|
||||
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>,
|
||||
|
||||
/// Whether the window should open full-width.
|
||||
pub open_maximized: Option<bool>,
|
||||
|
||||
/// Whether the window should open fullscreen.
|
||||
pub open_fullscreen: Option<bool>,
|
||||
|
||||
/// Extra bound on the minimum window width.
|
||||
pub min_width: Option<u16>,
|
||||
/// Extra bound on the minimum window height.
|
||||
pub min_height: Option<u16>,
|
||||
/// Extra bound on the maximum window width.
|
||||
pub max_width: Option<u16>,
|
||||
/// Extra bound on the maximum window height.
|
||||
pub max_height: Option<u16>,
|
||||
|
||||
/// Whether or not to draw the border with a solid background.
|
||||
///
|
||||
/// `None` means using the SSD heuristic.
|
||||
pub draw_border_with_background: Option<bool>,
|
||||
}
|
||||
|
||||
impl ResolvedWindowRules {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
default_width: None,
|
||||
open_on_output: None,
|
||||
open_maximized: None,
|
||||
open_fullscreen: None,
|
||||
min_width: None,
|
||||
min_height: None,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
draw_border_with_background: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute(rules: &[WindowRule], toplevel: &ToplevelSurface) -> Self {
|
||||
let _span = tracy_client::span!("ResolvedWindowRules::compute");
|
||||
|
||||
let mut resolved = ResolvedWindowRules::empty();
|
||||
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
let role = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
let mut open_on_output = None;
|
||||
|
||||
for rule in rules {
|
||||
if !(rule.matches.is_empty()
|
||||
|| rule.matches.iter().any(|m| window_matches(&role, m)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if rule.excludes.iter().any(|m| window_matches(&role, m)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(x) = rule
|
||||
.default_column_width
|
||||
.as_ref()
|
||||
.map(|d| d.0.map(ColumnWidth::from))
|
||||
{
|
||||
resolved.default_width = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.open_on_output.as_deref() {
|
||||
open_on_output = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.open_maximized {
|
||||
resolved.open_maximized = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.open_fullscreen {
|
||||
resolved.open_fullscreen = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.min_width {
|
||||
resolved.min_width = Some(x);
|
||||
}
|
||||
if let Some(x) = rule.min_height {
|
||||
resolved.min_height = Some(x);
|
||||
}
|
||||
if let Some(x) = rule.max_width {
|
||||
resolved.max_width = Some(x);
|
||||
}
|
||||
if let Some(x) = rule.max_height {
|
||||
resolved.max_height = Some(x);
|
||||
}
|
||||
|
||||
if let Some(x) = rule.draw_border_with_background {
|
||||
resolved.draw_border_with_background = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
||||
});
|
||||
|
||||
resolved
|
||||
}
|
||||
}
|
||||
|
||||
fn window_matches(role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
||||
if let Some(app_id_re) = &m.app_id {
|
||||
let Some(app_id) = &role.app_id else {
|
||||
return false;
|
||||
};
|
||||
if !app_id_re.is_match(app_id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(title_re) = &m.title {
|
||||
let Some(title) = &role.title else {
|
||||
return false;
|
||||
};
|
||||
if !title_re.is_match(title) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use smithay::desktop::Window;
|
||||
use smithay::output::Output;
|
||||
|
||||
use super::ResolvedWindowRules;
|
||||
use crate::layout::workspace::ColumnWidth;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -43,26 +44,6 @@ pub enum InitialConfigureState {
|
||||
},
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
|
||||
/// Whether the window should open full-width.
|
||||
pub open_maximized: Option<bool>,
|
||||
|
||||
/// Whether the window should open fullscreen.
|
||||
pub open_fullscreen: Option<bool>,
|
||||
}
|
||||
|
||||
impl Unmapped {
|
||||
/// Wraps a newly created window that hasn't been initially configured yet.
|
||||
pub fn new(window: Window) -> Self {
|
Loading…
Reference in New Issue
Block a user