aboutsummaryrefslogtreecommitdiff
path: root/src/handlers
diff options
context:
space:
mode:
authorGergely Nagy <niri@gergo.csillger.hu>2024-05-11 22:40:30 +0200
committerIvan Molodetskikh <yalterz@gmail.com>2024-05-16 01:24:34 -0700
commiteb9bbe3352820754a4ee3c19f15cff690d1c193d (patch)
tree65264aa9d03cc1ed63b4a540d0bca438ba75624b /src/handlers
parent229ca905071d837dbe6cfd9383e51cbb3c4634d7 (diff)
downloadniri-eb9bbe3352820754a4ee3c19f15cff690d1c193d.tar.gz
niri-eb9bbe3352820754a4ee3c19f15cff690d1c193d.tar.bz2
niri-eb9bbe3352820754a4ee3c19f15cff690d1c193d.zip
Implement named workspaces
This is an implementation of named, pre-declared workspaces. With this implementation, workspaces can be declared in the configuration file by name: ``` workspace "name" { open-on-output "winit" } ``` The `open-on-output` property is optional, and can be skipped, in which case the workspace will open on the primary output. All actions that were able to target a workspace by index can now target them by either an index, or a name. In case of the command line, where we do not have types available, this means that workspace names that also pass as `u8` cannot be switched to by name, only by index. Unlike dynamic workspaces, named workspaces do not close when they are empty, they remain static. Like dynamic workspaces, named workspaces are bound to a particular output. Switching to a named workspace, or moving a window or column to one will also switch to, or move the thing in question to the output of the workspace. When reloading the configuration, newly added named workspaces will be created, and removed ones will lose their name. If any such orphaned workspace was empty, they will be removed. If they weren't, they'll remain as a dynamic workspace, without a name. Re-declaring a workspace with the same name later will create a new one. Additionally, this also implements a `open-on-workspace "<name>"` window rule. Matching windows will open on the given workspace (or the current one, if the named workspace does not exist). Signed-off-by: Gergely Nagy <niri@gergo.csillger.hu>
Diffstat (limited to 'src/handlers')
-rw-r--r--src/handlers/compositor.rs18
-rw-r--r--src/handlers/xdg_shell.rs88
2 files changed, 73 insertions, 33 deletions
diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs
index 7d3f0419..2afb8fe8 100644
--- a/src/handlers/compositor.rs
+++ b/src/handlers/compositor.rs
@@ -119,22 +119,27 @@ impl CompositorHandler for State {
let toplevel = window.toplevel().expect("no X11 support");
- let (rules, width, is_full_width, output) =
+ let (rules, width, is_full_width, output, workspace_name) =
if let InitialConfigureState::Configured {
rules,
width,
is_full_width,
output,
+ workspace_name,
} = state
{
// Check that the output is still connected.
let output =
output.filter(|o| self.niri.layout.monitor_for_output(o).is_some());
- (rules, width, is_full_width, output)
+ // Chech that the workspace still exists.
+ let workspace_name = workspace_name
+ .filter(|n| self.niri.layout.find_workspace_by_name(n).is_some());
+
+ (rules, width, is_full_width, output, workspace_name)
} else {
error!("window map must happen after initial configure");
- (ResolvedWindowRules::empty(), None, false, None)
+ (ResolvedWindowRules::empty(), None, false, None, None)
};
let parent = toplevel
@@ -160,6 +165,13 @@ impl CompositorHandler for State {
self.niri
.layout
.add_window_right_of(&p, mapped, width, is_full_width)
+ } else if let Some(workspace_name) = &workspace_name {
+ self.niri.layout.add_window_to_named_workspace(
+ workspace_name,
+ mapped,
+ width,
+ is_full_width,
+ )
} else if let Some(output) = &output {
self.niri
.layout
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs
index 44f07839..30df1d5f 100644
--- a/src/handlers/xdg_shell.rs
+++ b/src/handlers/xdg_shell.rs
@@ -369,38 +369,52 @@ impl XdgShellHandler for State {
width,
is_full_width,
output,
+ workspace_name,
} => {
// Figure out the monitor following a similar logic to initial configure.
// FIXME: deduplicate.
- let mon = output
- .as_ref()
- .and_then(|o| self.niri.layout.monitor_for_output(o))
- .map(|mon| (mon, false))
- // If not, check if we have a parent with a monitor.
- .or_else(|| {
- toplevel
- .parent()
- .and_then(|parent| self.niri.layout.find_window_and_output(&parent))
- .map(|(_win, output)| output)
- .and_then(|o| self.niri.layout.monitor_for_output(o))
- .map(|mon| (mon, true))
- })
- // If not, fall back to the active monitor.
- .or_else(|| {
- self.niri
- .layout
- .active_monitor_ref()
- .map(|mon| (mon, false))
- });
+ let mon = workspace_name
+ .as_deref()
+ .and_then(|name| self.niri.layout.monitor_for_workspace(name))
+ .map(|mon| (mon, false));
+
+ let mon = mon.or_else(|| {
+ output
+ .as_ref()
+ .and_then(|o| self.niri.layout.monitor_for_output(o))
+ .map(|mon| (mon, false))
+ // If not, check if we have a parent with a monitor.
+ .or_else(|| {
+ toplevel
+ .parent()
+ .and_then(|parent| {
+ self.niri.layout.find_window_and_output(&parent)
+ })
+ .map(|(_win, output)| output)
+ .and_then(|o| self.niri.layout.monitor_for_output(o))
+ .map(|mon| (mon, true))
+ })
+ // If not, fall back to the active monitor.
+ .or_else(|| {
+ self.niri
+ .layout
+ .active_monitor_ref()
+ .map(|mon| (mon, false))
+ })
+ });
*output = mon
.filter(|(_, parent)| !parent)
.map(|(mon, _)| mon.output.clone());
let mon = mon.map(|(mon, _)| mon);
- let ws = mon
- .map(|mon| mon.active_workspace_ref())
- .or_else(|| self.niri.layout.active_workspace());
+ let ws = workspace_name
+ .as_deref()
+ .and_then(|name| mon.map(|mon| mon.find_named_workspace(name)))
+ .unwrap_or_else(|| {
+ mon.map(|mon| mon.active_workspace_ref())
+ .or_else(|| self.niri.layout.active_workspace())
+ });
if let Some(ws) = ws {
toplevel.with_pending_state(|state| {
@@ -577,12 +591,20 @@ impl State {
return;
};
- // 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 a workspace set in the window rules.
let mon = rules
- .open_on_output
+ .open_on_workspace
.as_deref()
- .and_then(|name| self.niri.output_by_name.get(name))
- .and_then(|o| self.niri.layout.monitor_for_output(o));
+ .and_then(|name| self.niri.layout.monitor_for_workspace(name));
+
+ // If not, check if we had an output set in the window rules.
+ let mon = mon.or_else(|| {
+ rules
+ .open_on_output
+ .as_deref()
+ .and_then(|name| self.niri.output_by_name.get(name))
+ .and_then(|o| self.niri.layout.monitor_for_output(o))
+ });
// If not, check if the window requested one for fullscreen.
let mon = mon.or_else(|| {
@@ -622,9 +644,14 @@ impl State {
let is_full_width = rules.open_maximized.unwrap_or(false);
// Tell the surface the preferred size and bounds for its likely output.
- let ws = mon
- .map(|mon| mon.active_workspace_ref())
- .or_else(|| self.niri.layout.active_workspace());
+ let ws = rules
+ .open_on_workspace
+ .as_deref()
+ .and_then(|name| mon.map(|mon| mon.find_named_workspace(name)))
+ .unwrap_or_else(|| {
+ mon.map(|mon| mon.active_workspace_ref())
+ .or_else(|| self.niri.layout.active_workspace())
+ });
if let Some(ws) = ws {
// Set a fullscreen state based on window request and window rule.
@@ -663,6 +690,7 @@ impl State {
width,
is_full_width,
output,
+ workspace_name: ws.and_then(|w| w.name.clone()),
};
toplevel.send_configure();