Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions docs/wiki/Configuration:-Window-Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ window-rule {
match is-window-cast-target=true
match is-urgent=true
match at-startup=true
match window-label="private" with-value="yes" with-no-value=true

// Properties that apply once upon window opening.
default-column-width { proportion 0.75; }
Expand Down Expand Up @@ -177,7 +178,7 @@ You can find the title and the app ID of a window by running `niri msg pick-wind

> [!TIP]
> Another way to find the window title and app ID is to configure the `wlr/taskbar` module in [Waybar](https://github.com/Alexays/Waybar) to include them in the tooltip:
>
>
> ```json
> "wlr/taskbar": {
> "tooltip-format": "{title} | {app_id}",
Expand Down Expand Up @@ -318,6 +319,47 @@ window-rule {
}
```

#### `window-label`

<sup>Since: 26.x</sup>

Matches the windows by their assigned label by a regex on the label name. Each window can have multiple labels. Label has a name and optionally also a value.
You can read about the supported regular expression syntax [here](https://docs.rs/regex/latest/regex/#syntax).

The following properties can further limit the match by examining the value of the label that matches the `window-label`:

* `with-value` - only matches if the label also has a matching value. This is also a regex.
* `with-no-value` - only matches the label if it has no value.

```kdl
// Change the border for windows that are labeled "important"
window-rule {
match window-label="important"

border {
active-color "#ee0000"
inactive-color "#ee5555"
}
}

binds {
Mod+F5 { toggle-label-on-focused-window "important"; }
}
```

```kdl
// Hide the windows labeled nsfw from screen cast
window-rule {
match window-label="role" with-value="nsfw"

block-out-from "screencast"
}

binds {
Mod+F12 { set-label-on-focused-window "role" value="nsfw"; }
}
```

### Window Opening Properties

These properties apply once, when a window first opens.
Expand Down Expand Up @@ -627,9 +669,10 @@ Can be `normal` or `tabbed`.

This is used any time a window goes into its own column.
For example:
- Opening a new window.
- Expelling a window into its own column.
- Moving a window from the floating layout to the tiling layout.

* Opening a new window.
* Expelling a window into its own column.
* Moving a window from the floating layout to the tiling layout.

```kdl
// Make Evince windows open as tabbed columns.
Expand Down Expand Up @@ -905,7 +948,7 @@ window-rule {

<video controls src="https://github.com/user-attachments/assets/3f4cb1a4-40b2-4766-98b7-eec014c19509">

https://github.com/user-attachments/assets/3f4cb1a4-40b2-4766-98b7-eec014c19509
<https://github.com/user-attachments/assets/3f4cb1a4-40b2-4766-98b7-eec014c19509>

</video>

Expand Down
26 changes: 26 additions & 0 deletions niri-config/src/binds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,18 @@ pub enum Action {
MruSetScope(MruScope),
#[knuffel(skip)]
MruCycleScope,
#[knuffel(skip)]
SetWindowLabel(u64, String, Option<String>),
#[knuffel(skip)]
UnsetWindowLabel(u64, String),
SetLabelOnFocusedWindow(
#[knuffel(argument)] String,
#[knuffel(property(name = "value"))] Option<String>,
),
UnsetLabelOnFocusedWindow(#[knuffel(argument)] String),
#[knuffel(skip)]
ToggleWindowLabel(u64, String),
ToggleLabelOnFocusedWindow(#[knuffel(argument)] String),
}

impl From<niri_ipc::Action> for Action {
Expand Down Expand Up @@ -700,6 +712,20 @@ impl From<niri_ipc::Action> for Action {
niri_ipc::Action::SetWindowUrgent { id } => Self::SetWindowUrgent(id),
niri_ipc::Action::UnsetWindowUrgent { id } => Self::UnsetWindowUrgent(id),
niri_ipc::Action::LoadConfigFile { path } => Self::LoadConfigFile(path),
niri_ipc::Action::SetWindowLabel { id, name, value } => {
Self::SetWindowLabel(id, name, value)
}
niri_ipc::Action::UnsetWindowLabel { id, name } => Self::UnsetWindowLabel(id, name),
niri_ipc::Action::SetLabelOnFocusedWindow { name, value } => {
Self::SetLabelOnFocusedWindow(name, value)
}
niri_ipc::Action::UnsetLabelOnFocusedWindow { name } => {
Self::UnsetLabelOnFocusedWindow(name)
}
niri_ipc::Action::ToggleWindowLabel { id, name } => Self::ToggleWindowLabel(id, name),
niri_ipc::Action::ToggleLabelOnFocusedWindow { name } => {
Self::ToggleLabelOnFocusedWindow(name)
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions niri-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,9 @@ mod tests {
is_window_cast_target: None,
is_urgent: None,
at_startup: None,
window_label: None,
with_value: None,
with_no_value: None,
},
],
excludes: [
Expand All @@ -1719,6 +1722,9 @@ mod tests {
is_window_cast_target: None,
is_urgent: None,
at_startup: None,
window_label: None,
with_value: None,
with_no_value: None,
},
Match {
app_id: None,
Expand All @@ -1734,6 +1740,9 @@ mod tests {
is_window_cast_target: None,
is_urgent: None,
at_startup: None,
window_label: None,
with_value: None,
with_no_value: None,
},
],
default_column_width: None,
Expand Down
6 changes: 6 additions & 0 deletions niri-config/src/window_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ pub struct Match {
pub is_urgent: Option<bool>,
#[knuffel(property)]
pub at_startup: Option<bool>,
#[knuffel(property, str)]
pub window_label: Option<RegexEq>,
#[knuffel(property, str)]
pub with_value: Option<RegexEq>,
#[knuffel(property)]
pub with_no_value: Option<bool>,
}

#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
Expand Down
66 changes: 66 additions & 0 deletions niri-ipc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ pub enum Request {
OverviewState,
/// Request information about screencasts.
Casts,
/// Request window labels
WindowLabels {
/// Window id
id: u64,
},
}

/// Reply from niri to client.
Expand Down Expand Up @@ -165,6 +170,8 @@ pub enum Response {
OverviewState(Overview),
/// Information about screencasts.
Casts(Vec<Cast>),
/// Information about the window labels
WindowLabels(Option<HashMap<String, Option<String>>>),
}

/// Overview information.
Expand Down Expand Up @@ -943,6 +950,62 @@ pub enum Action {
#[cfg_attr(feature = "clap", arg(long))]
path: Option<String>,
},
/// Sets a label on a window
SetWindowLabel {
/// Id of the window.
#[cfg_attr(feature = "clap", arg(long))]
id: u64,

/// the name of the label
#[cfg_attr(feature = "clap", arg(long))]
name: String,

/// the value of the label
#[cfg_attr(feature = "clap", arg(long))]
value: Option<String>,
},
/// Unsets a label on a window
UnsetWindowLabel {
/// Id of the window.
#[cfg_attr(feature = "clap", arg(long))]
id: u64,

/// the name of the label
#[cfg_attr(feature = "clap", arg(long))]
name: String,
},
/// Set a label on the currently focused window
SetLabelOnFocusedWindow {
/// the name of the label
#[cfg_attr(feature = "clap", arg(long))]
name: String,

/// the value of the label
#[cfg_attr(feature = "clap", arg(long))]
value: Option<String>,
},
/// Unsets a label on the currenlty focused window
UnsetLabelOnFocusedWindow {
/// the name of the label
#[cfg_attr(feature = "clap", arg(long))]
name: String,
},
/// Toggles an unvalued label on a window
ToggleWindowLabel {
/// Id of the window.
#[cfg_attr(feature = "clap", arg(long))]
id: u64,

/// the name of the label
#[cfg_attr(feature = "clap", arg(long))]
name: String,
},
/// Toggles an unvalued label on the focused window
ToggleLabelOnFocusedWindow {
/// the name of the label
#[cfg_attr(feature = "clap", arg(long))]
name: String,
},
}

/// Change in window or column size.
Expand Down Expand Up @@ -1334,6 +1397,9 @@ pub struct Window {
///
/// The timestamp comes from the monotonic clock.
pub focus_timestamp: Option<Timestamp>,

///Window labels
pub labels: Option<HashMap<String, Option<String>>>,
}

/// A moment in time.
Expand Down
6 changes: 6 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ pub enum Msg {
OverviewState,
/// List screencasts.
Casts,
/// List window labels
WindowLabels {
/// Window id
#[arg()]
id: u64,
},
}

#[derive(Clone, Debug, clap::ValueEnum)]
Expand Down
66 changes: 66 additions & 0 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2407,6 +2407,72 @@ impl State {
self.niri.queue_redraw_mru_output();
}
}
Action::SetWindowLabel(id, name, value) => {
let window = self
.niri
.layout
.workspaces_mut()
.find_map(|ws| ws.windows_mut().find(|w| w.id().get() == id));
if let Some(window) = window {
window.set_label(name, value);
self.niri.queue_redraw_all();
}
}
Action::UnsetWindowLabel(id, name) => {
let window = self
.niri
.layout
.workspaces_mut()
.find_map(|ws| ws.windows_mut().find(|w| w.id().get() == id));
if let Some(window) = window {
window.unset_label(name);
self.niri.queue_redraw_all();
}
}
Action::SetLabelOnFocusedWindow(name, value) => {
let window = self
.niri
.layout
.active_workspace_mut()
.and_then(|ws| ws.active_window_mut());
if let Some(window) = window {
window.set_label(name, value);
self.niri.queue_redraw_all();
}
}
Action::UnsetLabelOnFocusedWindow(name) => {
let window = self
.niri
.layout
.active_workspace_mut()
.and_then(|ws| ws.active_window_mut());
if let Some(window) = window {
window.unset_label(name);
self.niri.queue_redraw_all();
}
}
Action::ToggleWindowLabel(id, name) => {
let window = self
.niri
.layout
.workspaces_mut()
.find_map(|ws| ws.windows_mut().find(|w| w.id().get() == id));
if let Some(window) = window {
window.toggle_label(name);
self.niri.queue_redraw_all();
}
}
Action::ToggleLabelOnFocusedWindow(name) => {
let window = self
.niri
.layout
.active_workspace_mut()
.and_then(|ws| ws.active_window_mut());
if let Some(window) = window {
window.toggle_label(name);
self.niri.queue_redraw_all();
}
}
}
}

Expand Down
Loading