aboutsummaryrefslogtreecommitdiff
path: root/src/layout
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/layout
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/layout')
-rw-r--r--src/layout/mod.rs349
-rw-r--r--src/layout/monitor.rs24
-rw-r--r--src/layout/workspace.rs48
3 files changed, 396 insertions, 25 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index d091c024..0eb451e6 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -34,7 +34,7 @@ use std::mem;
use std::rc::Rc;
use std::time::Duration;
-use niri_config::{CenterFocusedColumn, Config, Struts};
+use niri_config::{CenterFocusedColumn, Config, Struts, Workspace as WorkspaceConfig};
use niri_ipc::SizeChange;
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
@@ -279,7 +279,7 @@ impl Options {
impl<W: LayoutElement> Layout<W> {
pub fn new(config: &Config) -> Self {
- Self::with_options(Options::from_config(config))
+ Self::with_options_and_workspaces(config, Options::from_config(config))
}
pub fn with_options(options: Options) -> Self {
@@ -289,6 +289,21 @@ impl<W: LayoutElement> Layout<W> {
}
}
+ fn with_options_and_workspaces(config: &Config, options: Options) -> Self {
+ let opts = Rc::new(options);
+
+ let workspaces = config
+ .workspaces
+ .iter()
+ .map(|ws| Workspace::new_with_config_no_outputs(Some(ws.clone()), opts.clone()))
+ .collect();
+
+ Self {
+ monitor_set: MonitorSet::NoOutputs { workspaces },
+ options: opts,
+ }
+ }
+
pub fn add_output(&mut self, output: Output) {
let id = OutputId::new(&output);
@@ -318,7 +333,7 @@ impl<W: LayoutElement> Layout<W> {
// The user could've closed a window while remaining on this workspace, on
// another monitor. However, we will add an empty workspace in the end
// instead.
- if ws.has_windows() {
+ if ws.has_windows() || ws.name.is_some() {
workspaces.push(ws);
}
@@ -463,6 +478,67 @@ impl<W: LayoutElement> Layout<W> {
}
}
+ /// Adds a new window to the layout on a specific workspace.
+ pub fn add_window_to_named_workspace(
+ &mut self,
+ workspace_name: &str,
+ window: W,
+ width: Option<ColumnWidth>,
+ is_full_width: bool,
+ ) -> Option<&Output> {
+ let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
+ if let ColumnWidth::Fixed(w) = &mut width {
+ let rules = window.rules();
+ let border_config = rules.border.resolve_against(self.options.border);
+ if !border_config.off {
+ *w += border_config.width as i32 * 2;
+ }
+ }
+
+ match &mut self.monitor_set {
+ MonitorSet::Normal {
+ monitors,
+ active_monitor_idx,
+ ..
+ } => {
+ let (mon_idx, mon, ws_idx) = monitors
+ .iter_mut()
+ .enumerate()
+ .find_map(|(mon_idx, mon)| {
+ mon.find_named_workspace_index(workspace_name)
+ .map(move |ws_idx| (mon_idx, mon, ws_idx))
+ })
+ .unwrap();
+
+ // Don't steal focus from an active fullscreen window.
+ let mut activate = true;
+ let ws = &mon.workspaces[ws_idx];
+ if mon_idx == *active_monitor_idx
+ && !ws.columns.is_empty()
+ && ws.columns[ws.active_column_idx].is_fullscreen
+ {
+ activate = false;
+ }
+
+ // Don't activate if on a different workspace.
+ if mon.active_workspace_idx != ws_idx {
+ activate = false;
+ }
+
+ mon.add_window(ws_idx, window, activate, width, is_full_width);
+ Some(&mon.output)
+ }
+ MonitorSet::NoOutputs { workspaces } => {
+ let ws = workspaces
+ .iter_mut()
+ .find(|ws| ws.name.as_deref() == Some(workspace_name))
+ .unwrap();
+ ws.add_window(window, true, width, is_full_width);
+ None
+ }
+ }
+ }
+
pub fn add_column_by_idx(
&mut self,
monitor_idx: usize,
@@ -649,6 +725,7 @@ impl<W: LayoutElement> Layout<W> {
&& idx != mon.active_workspace_idx
&& idx != mon.workspaces.len() - 1
&& mon.workspace_switch.is_none()
+ && mon.workspaces[idx].name.is_none()
{
mon.workspaces.remove(idx);
@@ -668,7 +745,7 @@ impl<W: LayoutElement> Layout<W> {
rv = Some(ws.remove_window(window));
// Clean up empty workspaces.
- if !ws.has_windows() {
+ if !ws.has_windows() && workspaces[idx].name.is_none() {
workspaces.remove(idx);
}
@@ -718,6 +795,63 @@ impl<W: LayoutElement> Layout<W> {
None
}
+ pub fn find_workspace_by_name(&self, workspace_name: &str) -> Option<(usize, &Workspace<W>)> {
+ match &self.monitor_set {
+ MonitorSet::Normal { ref monitors, .. } => {
+ for mon in monitors {
+ if let Some((index, workspace)) = mon
+ .workspaces
+ .iter()
+ .enumerate()
+ .find(|(_, w)| w.name.as_deref() == Some(workspace_name))
+ {
+ return Some((index, workspace));
+ }
+ }
+ }
+ MonitorSet::NoOutputs { workspaces } => {
+ if let Some((index, workspace)) = workspaces
+ .iter()
+ .enumerate()
+ .find(|(_, w)| w.name.as_deref() == Some(workspace_name))
+ {
+ return Some((index, workspace));
+ }
+ }
+ }
+
+ None
+ }
+
+ pub fn unname_workspace(&mut self, workspace_name: &str) {
+ match &mut self.monitor_set {
+ MonitorSet::Normal { monitors, .. } => {
+ for mon in monitors {
+ if mon.unname_workspace(workspace_name) {
+ if mon.workspace_switch.is_none() {
+ mon.clean_up_workspaces();
+ }
+ return;
+ }
+ }
+ }
+ MonitorSet::NoOutputs { workspaces } => {
+ for (idx, ws) in workspaces.iter_mut().enumerate() {
+ if ws.name.as_deref() == Some(workspace_name) {
+ ws.unname();
+
+ // Clean up empty workspaces.
+ if !ws.has_windows() {
+ workspaces.remove(idx);
+ }
+
+ return;
+ }
+ }
+ }
+ }
+ }
+
pub fn find_window_and_output_mut(
&mut self,
wl_surface: &WlSurface,
@@ -970,6 +1104,19 @@ impl<W: LayoutElement> Layout<W> {
monitors.iter().find(|monitor| &monitor.output == output)
}
+ pub fn monitor_for_workspace(&self, workspace_name: &str) -> Option<&Monitor<W>> {
+ let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
+ return None;
+ };
+
+ monitors.iter().find(|monitor| {
+ monitor
+ .workspaces
+ .iter()
+ .any(|ws| ws.name.as_deref() == Some(workspace_name))
+ })
+ }
+
pub fn outputs(&self) -> impl Iterator<Item = &Output> + '_ {
let monitors = if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
&monitors[..]
@@ -1127,6 +1274,12 @@ impl<W: LayoutElement> Layout<W> {
monitor.move_to_workspace(idx);
}
+ pub fn move_to_workspace_on_output(&mut self, output: &Output, idx: usize) {
+ self.move_to_output(output);
+ self.focus_output(output);
+ self.move_to_workspace(idx);
+ }
+
pub fn move_column_to_workspace_up(&mut self) {
let Some(monitor) = self.active_monitor() else {
return;
@@ -1148,6 +1301,12 @@ impl<W: LayoutElement> Layout<W> {
monitor.move_column_to_workspace(idx);
}
+ pub fn move_column_to_workspace_on_output(&mut self, output: &Output, idx: usize) {
+ self.move_to_output(output);
+ self.focus_output(output);
+ self.move_column_to_workspace(idx);
+ }
+
pub fn switch_workspace_up(&mut self) {
let Some(monitor) = self.active_monitor() else {
return;
@@ -1257,6 +1416,7 @@ impl<W: LayoutElement> Layout<W> {
use crate::layout::monitor::WorkspaceSwitch;
let mut seen_workspace_id = HashSet::new();
+ let mut seen_workspace_name = HashSet::new();
let (monitors, &primary_idx, &active_monitor_idx) = match &self.monitor_set {
MonitorSet::Normal {
@@ -1267,8 +1427,8 @@ impl<W: LayoutElement> Layout<W> {
MonitorSet::NoOutputs { workspaces } => {
for workspace in workspaces {
assert!(
- workspace.has_windows(),
- "with no outputs there cannot be empty workspaces"
+ workspace.has_windows() || workspace.name.is_some(),
+ "with no outputs there cannot be empty unnamed workspaces"
);
assert_eq!(
@@ -1281,6 +1441,13 @@ impl<W: LayoutElement> Layout<W> {
"workspace id must be unique"
);
+ if let Some(name) = &workspace.name {
+ assert!(
+ seen_workspace_name.insert(name),
+ "workspace name must be unique"
+ );
+ }
+
workspace.verify_invariants();
}
@@ -1343,14 +1510,19 @@ impl<W: LayoutElement> Layout<W> {
"monitor must have an empty workspace in the end"
);
+ assert!(
+ monitor.workspaces.last().unwrap().name.is_none(),
+ "monitor must have an unnamed workspace in the end"
+ );
+
// If there's no workspace switch in progress, there can't be any non-last non-active
// empty workspaces.
if monitor.workspace_switch.is_none() {
for (idx, ws) in monitor.workspaces.iter().enumerate().rev().skip(1) {
if idx != monitor.active_workspace_idx {
assert!(
- !ws.columns.is_empty(),
- "non-active workspace can't be empty except the last one"
+ !ws.columns.is_empty() || ws.name.is_some(),
+ "non-active workspace can't be empty and unnamed except the last one"
);
}
}
@@ -1370,6 +1542,13 @@ impl<W: LayoutElement> Layout<W> {
"workspace id must be unique"
);
+ if let Some(name) = &workspace.name {
+ assert!(
+ seen_workspace_name.insert(name),
+ "workspace name must be unique"
+ );
+ }
+
workspace.verify_invariants();
}
}
@@ -1448,6 +1627,48 @@ impl<W: LayoutElement> Layout<W> {
}
}
+ pub fn ensure_named_workspace(&mut self, ws_config: &WorkspaceConfig) {
+ if self.find_workspace_by_name(&ws_config.name.0).is_some() {
+ return;
+ }
+
+ let options = self.options.clone();
+
+ match &mut self.monitor_set {
+ MonitorSet::Normal {
+ monitors,
+ primary_idx,
+ active_monitor_idx,
+ } => {
+ let mon_idx = ws_config
+ .open_on_output
+ .as_deref()
+ .map(|name| {
+ monitors
+ .iter_mut()
+ .position(|monitor| monitor.output.name().eq_ignore_ascii_case(name))
+ .unwrap_or(*primary_idx)
+ })
+ .unwrap_or(*active_monitor_idx);
+ let mon = &mut monitors[mon_idx];
+
+ let ws = Workspace::new_with_config(
+ mon.output.clone(),
+ Some(ws_config.clone()),
+ options,
+ );
+ mon.workspaces.insert(0, ws);
+ mon.active_workspace_idx += 1;
+ mon.workspace_switch = None;
+ mon.clean_up_workspaces();
+ }
+ MonitorSet::NoOutputs { workspaces } => {
+ let ws = Workspace::new_with_config_no_outputs(Some(ws_config.clone()), options);
+ workspaces.insert(0, ws);
+ }
+ }
+ }
+
pub fn update_config(&mut self, config: &Config) {
let options = Rc::new(Options::from_config(config));
@@ -2053,6 +2274,7 @@ impl<W: LayoutElement> Default for MonitorSet<W> {
mod tests {
use std::cell::Cell;
+ use niri_config::WorkspaceName;
use proptest::prelude::*;
use proptest_derive::Arbitrary;
use smithay::output::{Mode, PhysicalProperties, Subpixel};
@@ -2284,6 +2506,16 @@ mod tests {
AddOutput(#[proptest(strategy = "1..=5usize")] usize),
RemoveOutput(#[proptest(strategy = "1..=5usize")] usize),
FocusOutput(#[proptest(strategy = "1..=5usize")] usize),
+ AddNamedWorkspace {
+ #[proptest(strategy = "1..=5usize")]
+ ws_name: usize,
+ #[proptest(strategy = "prop::option::of(1..=5usize)")]
+ output_name: Option<usize>,
+ },
+ UnnameWorkspace {
+ #[proptest(strategy = "1..=5usize")]
+ ws_name: usize,
+ },
AddWindow {
#[proptest(strategy = "1..=5usize")]
id: usize,
@@ -2302,6 +2534,16 @@ mod tests {
#[proptest(strategy = "arbitrary_min_max_size()")]
min_max_size: (Size<i32, Logical>, Size<i32, Logical>),
},
+ AddWindowToNamedWorkspace {
+ #[proptest(strategy = "1..=5usize")]
+ id: usize,
+ #[proptest(strategy = "1..=5usize")]
+ ws_name: usize,
+ #[proptest(strategy = "arbitrary_bbox()")]
+ bbox: Rectangle<i32, Logical>,
+ #[proptest(strategy = "arbitrary_min_max_size()")]
+ min_max_size: (Size<i32, Logical>, Size<i32, Logical>),
+ },
CloseWindow(#[proptest(strategy = "1..=5usize")] usize),
FullscreenWindow(#[proptest(strategy = "1..=5usize")] usize),
FocusColumnLeft,
@@ -2438,6 +2680,18 @@ mod tests {
layout.focus_output(&output);
}
+ Op::AddNamedWorkspace {
+ ws_name,
+ output_name,
+ } => {
+ layout.ensure_named_workspace(&WorkspaceConfig {
+ name: WorkspaceName(format!("ws{ws_name}")),
+ open_on_output: output_name.map(|name| format!("output{name}")),
+ });
+ }
+ Op::UnnameWorkspace { ws_name } => {
+ layout.unname_workspace(&format!("ws{ws_name}"));
+ }
Op::AddWindow {
id,
bbox,
@@ -2515,6 +2769,53 @@ mod tests {
let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1);
layout.add_window_right_of(&right_of_id, win, None, false);
}
+ Op::AddWindowToNamedWorkspace {
+ id,
+ ws_name,
+ bbox,
+ min_max_size,
+ } => {
+ let ws_name = format!("ws{ws_name}");
+ let mut found_workspace = false;
+
+ match &mut layout.monitor_set {
+ MonitorSet::Normal { monitors, .. } => {
+ for mon in monitors {
+ for ws in &mut mon.workspaces {
+ for win in ws.windows() {
+ if win.0.id == id {
+ return;
+ }
+ }
+
+ if ws.name.as_deref() == Some(&ws_name) {
+ found_workspace = true;
+ }
+ }
+ }
+ }
+ MonitorSet::NoOutputs { workspaces, .. } => {
+ for ws in workspaces {
+ for win in ws.windows() {
+ if win.0.id == id {
+ return;
+ }
+ }
+
+ if ws.name.as_deref() == Some(&ws_name) {
+ found_workspace = true;
+ }
+ }
+ }
+ }
+
+ if !found_workspace {
+ return;
+ }
+
+ let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1);
+ layout.add_window_to_named_workspace(&ws_name, win, None, false);
+ }
Op::CloseWindow(id) => {
layout.remove_window(&id);
}
@@ -2702,6 +3003,11 @@ mod tests {
Op::FocusOutput(0),
Op::FocusOutput(1),
Op::FocusOutput(2),
+ Op::AddNamedWorkspace {
+ ws_name: 1,
+ output_name: Some(1),
+ },
+ Op::UnnameWorkspace { ws_name: 1 },
Op::AddWindow {
id: 0,
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
@@ -2712,20 +3018,15 @@ mod tests {
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
min_max_size: Default::default(),
},
- Op::AddWindow {
+ Op::AddWindowRightOf {
id: 2,
+ right_of_id: 1,
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
min_max_size: Default::default(),
},
- Op::AddWindowRightOf {
+ Op::AddWindowToNamedWorkspace {
id: 3,
- right_of_id: 0,
- bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
- min_max_size: Default::default(),
- },
- Op::AddWindowRightOf {
- id: 4,
- right_of_id: 1,
+ ws_name: 1,
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
min_max_size: Default::default(),
},
@@ -2750,17 +3051,14 @@ mod tests {
Op::FocusWorkspaceUp,
Op::FocusWorkspace(1),
Op::FocusWorkspace(2),
- Op::FocusWorkspace(3),
Op::MoveWindowToWorkspaceDown,
Op::MoveWindowToWorkspaceUp,
Op::MoveWindowToWorkspace(1),
Op::MoveWindowToWorkspace(2),
- Op::MoveWindowToWorkspace(3),
Op::MoveColumnToWorkspaceDown,
Op::MoveColumnToWorkspaceUp,
Op::MoveColumnToWorkspace(1),
Op::MoveColumnToWorkspace(2),
- Op::MoveColumnToWorkspace(3),
Op::MoveWindowDown,
Op::MoveWindowDownOrToWorkspaceDown,
Op::MoveWindowUp,
@@ -2847,6 +3145,11 @@ mod tests {
Op::FocusOutput(0),
Op::FocusOutput(1),
Op::FocusOutput(2),
+ Op::AddNamedWorkspace {
+ ws_name: 1,
+ output_name: Some(1),
+ },
+ Op::UnnameWorkspace { ws_name: 1 },
Op::AddWindow {
id: 0,
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
@@ -2874,6 +3177,12 @@ mod tests {
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
min_max_size: Default::default(),
},
+ Op::AddWindowToNamedWorkspace {
+ id: 5,
+ ws_name: 1,
+ bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
+ min_max_size: Default::default(),
+ },
Op::CloseWindow(0),
Op::CloseWindow(1),
Op::CloseWindow(2),
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index 390abf0c..9dcc552e 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -103,6 +103,18 @@ impl<W: LayoutElement> Monitor<W> {
&self.workspaces[self.active_workspace_idx]
}
+ pub fn find_named_workspace(&self, workspace_name: &str) -> Option<&Workspace<W>> {
+ self.workspaces
+ .iter()
+ .find(|w| w.name.as_deref() == Some(workspace_name))
+ }
+
+ pub fn find_named_workspace_index(&self, workspace_name: &str) -> Option<usize> {
+ self.workspaces
+ .iter()
+ .position(|w| w.name.as_deref() == Some(workspace_name))
+ }
+
pub fn active_workspace(&mut self) -> &mut Workspace<W> {
&mut self.workspaces[self.active_workspace_idx]
}
@@ -204,7 +216,7 @@ impl<W: LayoutElement> Monitor<W> {
continue;
}
- if !self.workspaces[idx].has_windows() {
+ if !self.workspaces[idx].has_windows() && self.workspaces[idx].name.is_none() {
self.workspaces.remove(idx);
if self.active_workspace_idx > idx {
self.active_workspace_idx -= 1;
@@ -213,6 +225,16 @@ impl<W: LayoutElement> Monitor<W> {
}
}
+ pub fn unname_workspace(&mut self, workspace_name: &str) -> bool {
+ for ws in &mut self.workspaces {
+ if ws.name.as_deref() == Some(workspace_name) {
+ ws.unname();
+ return true;
+ }
+ }
+ false
+ }
+
pub fn move_left(&mut self) {
self.active_workspace().move_left();
}
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 4a6ad91c..827228c6 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -3,7 +3,7 @@ use std::iter::{self, zip};
use std::rc::Rc;
use std::time::Duration;
-use niri_config::{CenterFocusedColumn, PresetWidth, Struts};
+use niri_config::{CenterFocusedColumn, PresetWidth, Struts, Workspace as WorkspaceConfig};
use niri_ipc::SizeChange;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::desktop::{layer_map_for_output, Window};
@@ -94,6 +94,9 @@ pub struct Workspace<W: LayoutElement> {
/// Configurable properties of the layout.
pub options: Rc<Options>,
+ /// Optional name of this workspace.
+ pub name: Option<String>,
+
/// Unique ID of this workspace.
id: WorkspaceId,
}
@@ -313,9 +316,23 @@ impl TileData {
impl<W: LayoutElement> Workspace<W> {
pub fn new(output: Output, options: Rc<Options>) -> Self {
+ Self::new_with_config(output, None, options)
+ }
+
+ pub fn new_with_config(
+ output: Output,
+ config: Option<WorkspaceConfig>,
+ options: Rc<Options>,
+ ) -> Self {
+ let original_output = config
+ .as_ref()
+ .and_then(|c| c.open_on_output.clone())
+ .map(OutputId)
+ .unwrap_or(OutputId::new(&output));
+
let working_area = compute_working_area(&output, options.struts);
Self {
- original_output: OutputId::new(&output),
+ original_output,
view_size: output_size(&output),
working_area,
output: Some(output),
@@ -329,14 +346,24 @@ impl<W: LayoutElement> Workspace<W> {
view_offset_before_fullscreen: None,
closing_windows: vec![],
options,
+ name: config.map(|c| c.name.0),
id: WorkspaceId::next(),
}
}
- pub fn new_no_outputs(options: Rc<Options>) -> Self {
+ pub fn new_with_config_no_outputs(
+ config: Option<WorkspaceConfig>,
+ options: Rc<Options>,
+ ) -> Self {
+ let original_output = OutputId(
+ config
+ .clone()
+ .and_then(|c| c.open_on_output)
+ .unwrap_or_default(),
+ );
Self {
output: None,
- original_output: OutputId(String::new()),
+ original_output,
view_size: Size::from((1280, 720)),
working_area: Rectangle::from_loc_and_size((0, 0), (1280, 720)),
columns: vec![],
@@ -349,14 +376,23 @@ impl<W: LayoutElement> Workspace<W> {
view_offset_before_fullscreen: None,
closing_windows: vec![],
options,
+ name: config.map(|c| c.name.0),
id: WorkspaceId::next(),
}
}
+ pub fn new_no_outputs(options: Rc<Options>) -> Self {
+ Self::new_with_config_no_outputs(None, options)
+ }
+
pub fn id(&self) -> WorkspaceId {
self.id
}
+ pub fn unname(&mut self) {
+ self.name = None;
+ }
+
pub fn advance_animations(&mut self, current_time: Duration) {
if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj {
anim.set_current_time(current_time);
@@ -435,6 +471,10 @@ impl<W: LayoutElement> Workspace<W> {
.map(Tile::window_mut)
}
+ pub fn current_output(&self) -> Option<&Output> {
+ self.output.as_ref()
+ }
+
pub fn set_output(&mut self, output: Option<Output>) {
if self.output == output {
return;