mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Implement window rule reloading and min/max size rules
This commit is contained in:
@@ -674,6 +674,7 @@ pub struct WindowRule {
|
|||||||
#[knuffel(children(name = "exclude"))]
|
#[knuffel(children(name = "exclude"))]
|
||||||
pub excludes: Vec<Match>,
|
pub excludes: Vec<Match>,
|
||||||
|
|
||||||
|
// Rules applied at initial configure.
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub default_column_width: Option<DefaultColumnWidth>,
|
pub default_column_width: Option<DefaultColumnWidth>,
|
||||||
#[knuffel(child, unwrap(argument))]
|
#[knuffel(child, unwrap(argument))]
|
||||||
@@ -682,6 +683,16 @@ pub struct WindowRule {
|
|||||||
pub open_maximized: Option<bool>,
|
pub open_maximized: Option<bool>,
|
||||||
#[knuffel(child, unwrap(argument))]
|
#[knuffel(child, unwrap(argument))]
|
||||||
pub open_fullscreen: Option<bool>,
|
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, Clone)]
|
#[derive(knuffel::Decode, Debug, Default, Clone)]
|
||||||
|
|||||||
@@ -362,6 +362,8 @@ animations {
|
|||||||
exclude app-id=r#"\.unwanted\."#
|
exclude app-id=r#"\.unwanted\."#
|
||||||
|
|
||||||
// Here are the properties that you can set on a window rule.
|
// 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.
|
// You can override the default column width.
|
||||||
default-column-width { proportion 0.75; }
|
default-column-width { proportion 0.75; }
|
||||||
|
|
||||||
@@ -377,6 +379,20 @@ animations {
|
|||||||
open-fullscreen true
|
open-fullscreen true
|
||||||
// You can also set this to false to prevent a window from opening fullscreen.
|
// You can also set this to false to prevent a window from opening fullscreen.
|
||||||
// open-fullscreen false
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here's a useful example. Work around WezTerm's initial configure bug
|
// Here's a useful example. Work around WezTerm's initial configure bug
|
||||||
|
|||||||
+21
-82
@@ -1,4 +1,3 @@
|
|||||||
use niri_config::{Match, WindowRule};
|
|
||||||
use smithay::desktop::{
|
use smithay::desktop::{
|
||||||
find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, LayerSurface,
|
find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, LayerSurface,
|
||||||
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy, Window,
|
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::decoration::XdgDecorationHandler;
|
||||||
use smithay::wayland::shell::xdg::{
|
use smithay::wayland::shell::xdg::{
|
||||||
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
|
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
|
||||||
XdgShellState, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
XdgShellState, XdgToplevelSurfaceData,
|
||||||
};
|
};
|
||||||
use smithay::wayland::xdg_foreign::{XdgForeignHandler, XdgForeignState};
|
use smithay::wayland::xdg_foreign::{XdgForeignHandler, XdgForeignState};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
@@ -31,82 +30,6 @@ use crate::layout::workspace::ColumnWidth;
|
|||||||
use crate::niri::{PopupGrabState, State};
|
use crate::niri::{PopupGrabState, State};
|
||||||
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped};
|
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 {
|
impl XdgShellHandler for State {
|
||||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||||
&mut self.niri.xdg_shell_state
|
&mut self.niri.xdg_shell_state
|
||||||
@@ -574,7 +497,7 @@ impl State {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let config = self.niri.config.borrow();
|
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.
|
// Pick the target monitor. First, check if we had an output set in the window rules.
|
||||||
let mon = rules
|
let mon = rules
|
||||||
@@ -807,14 +730,30 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_window_rules(&mut self, toplevel: &ToplevelSurface) {
|
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 Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) {
|
||||||
if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state {
|
if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state {
|
||||||
*rules = resolve();
|
*rules = resolve();
|
||||||
}
|
}
|
||||||
} else if let Some(mapped) = self.niri.layout.find_window_mut(toplevel.wl_surface()) {
|
} else if let Some((mapped, output)) = self
|
||||||
mapped.rules = resolve();
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-21
@@ -643,21 +643,12 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_window_mut(&mut self, wl_surface: &WlSurface) -> Option<&mut W> {
|
pub fn find_window_and_output(&self, wl_surface: &WlSurface) -> Option<(&W, &Output)> {
|
||||||
match &mut self.monitor_set {
|
if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
|
||||||
MonitorSet::Normal { monitors, .. } => {
|
for mon in monitors {
|
||||||
for mon in monitors {
|
for ws in &mon.workspaces {
|
||||||
for ws in &mut mon.workspaces {
|
if let Some(window) = ws.find_wl_surface(wl_surface) {
|
||||||
if let Some(window) = ws.find_wl_surface_mut(wl_surface) {
|
return Some((window, &mon.output));
|
||||||
return Some(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MonitorSet::NoOutputs { workspaces } => {
|
|
||||||
for ws in workspaces {
|
|
||||||
if let Some(window) = ws.find_wl_surface_mut(wl_surface) {
|
|
||||||
return Some(window);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -666,12 +657,24 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_window_and_output(&self, wl_surface: &WlSurface) -> Option<(&W, &Output)> {
|
pub fn find_window_and_output_mut(
|
||||||
if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
|
&mut self,
|
||||||
for mon in monitors {
|
wl_surface: &WlSurface,
|
||||||
for ws in &mon.workspaces {
|
) -> Option<(&mut W, Option<&Output>)> {
|
||||||
if let Some(window) = ws.find_wl_surface(wl_surface) {
|
match &mut self.monitor_set {
|
||||||
return Some((window, &mon.output));
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -851,6 +854,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>> {
|
fn active_monitor(&mut self) -> Option<&mut Monitor<W>> {
|
||||||
let MonitorSet::Normal {
|
let MonitorSet::Normal {
|
||||||
monitors,
|
monitors,
|
||||||
|
|||||||
+30
-1
@@ -114,7 +114,7 @@ use crate::utils::spawning::CHILD_ENV;
|
|||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
center, center_f64, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
|
center, center_f64, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
|
||||||
};
|
};
|
||||||
use crate::window::{Mapped, Unmapped};
|
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, 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.];
|
||||||
@@ -761,6 +761,7 @@ impl State {
|
|||||||
let mut reload_xkb = None;
|
let mut reload_xkb = None;
|
||||||
let mut libinput_config_changed = false;
|
let mut libinput_config_changed = false;
|
||||||
let mut output_config_changed = false;
|
let mut output_config_changed = false;
|
||||||
|
let mut window_rules_changed = false;
|
||||||
let mut old_config = self.niri.config.borrow_mut();
|
let mut old_config = self.niri.config.borrow_mut();
|
||||||
|
|
||||||
// Reload the cursor.
|
// Reload the cursor.
|
||||||
@@ -802,6 +803,10 @@ impl State {
|
|||||||
self.niri.hotkey_overlay.on_hotkey_config_updated();
|
self.niri.hotkey_overlay.on_hotkey_config_updated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.window_rules != old_config.window_rules {
|
||||||
|
window_rules_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
*old_config = config;
|
*old_config = config;
|
||||||
|
|
||||||
// Release the borrow.
|
// 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
|
// 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
|
// 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
|
// global suddenly appearing? Either way, right now it's live-reloaded in a sense that new
|
||||||
|
|||||||
+32
-4
@@ -1,3 +1,5 @@
|
|||||||
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
use smithay::backend::renderer::element::{AsRenderElements as _, Id};
|
use smithay::backend::renderer::element::{AsRenderElements as _, Id};
|
||||||
use smithay::desktop::space::SpaceElement as _;
|
use smithay::desktop::space::SpaceElement as _;
|
||||||
use smithay::desktop::Window;
|
use smithay::desktop::Window;
|
||||||
@@ -82,17 +84,43 @@ impl LayoutElement for Mapped {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn min_size(&self) -> Size<i32, Logical> {
|
fn min_size(&self) -> Size<i32, Logical> {
|
||||||
with_states(self.toplevel().wl_surface(), |state| {
|
let mut size = with_states(self.toplevel().wl_surface(), |state| {
|
||||||
let curr = state.cached_state.current::<SurfaceCachedState>();
|
let curr = state.cached_state.current::<SurfaceCachedState>();
|
||||||
curr.min_size
|
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> {
|
fn max_size(&self) -> Size<i32, Logical> {
|
||||||
with_states(self.toplevel().wl_surface(), |state| {
|
let mut size = with_states(self.toplevel().wl_surface(), |state| {
|
||||||
let curr = state.cached_state.current::<SurfaceCachedState>();
|
let curr = state.cached_state.current::<SurfaceCachedState>();
|
||||||
curr.max_size
|
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 {
|
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool {
|
||||||
|
|||||||
+106
-1
@@ -1,3 +1,9 @@
|
|||||||
|
use niri_config::{Match, WindowRule};
|
||||||
|
use smithay::wayland::compositor::with_states;
|
||||||
|
use smithay::wayland::shell::xdg::{
|
||||||
|
ToplevelSurface, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::layout::workspace::ColumnWidth;
|
use crate::layout::workspace::ColumnWidth;
|
||||||
|
|
||||||
pub mod mapped;
|
pub mod mapped;
|
||||||
@@ -7,7 +13,7 @@ pub mod unmapped;
|
|||||||
pub use unmapped::{InitialConfigureState, Unmapped};
|
pub use unmapped::{InitialConfigureState, Unmapped};
|
||||||
|
|
||||||
/// Rules fully resolved for a window.
|
/// Rules fully resolved for a window.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct ResolvedWindowRules {
|
pub struct ResolvedWindowRules {
|
||||||
/// Default width for this window.
|
/// Default width for this window.
|
||||||
///
|
///
|
||||||
@@ -24,4 +30,103 @@ pub struct ResolvedWindowRules {
|
|||||||
|
|
||||||
/// Whether the window should open fullscreen.
|
/// Whether the window should open fullscreen.
|
||||||
pub open_fullscreen: Option<bool>,
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolvedWindowRules {
|
||||||
|
pub fn compute(rules: &[WindowRule], toplevel: &ToplevelSurface) -> Self {
|
||||||
|
let _span = tracy_client::span!("ResolvedWindowRules::compute");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user