Unify per-monitor workspace numbering and lifecycle policy
This commit is contained in:
1 parent
07bda5a21c
commit
1441875096
20 files changed
+707
-316
No files matched your search
+7
-28
@@ -781,39 +781,18 @@ impl Ferese {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn relative_workspace_target(&mut self, next: bool) -> Option<ferese_core::WorkspaceId> {
|
||||
pub(crate) fn relative_workspace_target(&self, next: bool) -> Option<ferese_core::WorkspaceId> {
|
||||
let output = self.output_workspaces.focused_output()?;
|
||||
let current = self.output_workspaces.active_workspace(output)?;
|
||||
let mut candidates = self.workspaces.ordered();
|
||||
candidates.retain(|workspace| {
|
||||
self.output_workspaces
|
||||
.output_for_workspace(workspace.id)
|
||||
.is_none_or(|owner| owner == output)
|
||||
});
|
||||
let index = candidates.iter().position(|workspace| workspace.id == current)?;
|
||||
let candidates = self.output_workspaces.ordered_workspaces(&self.workspaces, output);
|
||||
let index = candidates.iter().position(|id| *id == current)?;
|
||||
let neighbor = if next {
|
||||
index.checked_add(1)
|
||||
index.checked_add(1)?
|
||||
} else {
|
||||
index.checked_sub(1)
|
||||
index.checked_sub(1)?
|
||||
};
|
||||
if let Some(workspace) = neighbor.and_then(|index| candidates.get(index)) {
|
||||
return Some(workspace.id);
|
||||
}
|
||||
if next
|
||||
&& self.workspaces.workspace(current).is_some_and(|workspace| {
|
||||
workspace.layout.window_ids().next().is_some() || !workspace.floating.is_empty()
|
||||
})
|
||||
{
|
||||
let number = self
|
||||
.workspaces
|
||||
.iter()
|
||||
.filter_map(|workspace| workspace.name.parse::<u32>().ok())
|
||||
.max()
|
||||
.unwrap_or(1)
|
||||
.checked_add(1)?;
|
||||
return self.workspaces.ensure_numeric(number).ok();
|
||||
}
|
||||
None
|
||||
|
||||
candidates.get(neighbor).copied()
|
||||
}
|
||||
|
||||
fn focus_window_at(&mut self, position: Point<f64, Logical>, serial: Serial, raise: bool) {
|
||||
|
||||
+11
-6
@@ -987,20 +987,25 @@ impl Ferese {
|
||||
}
|
||||
|
||||
fn workspaces_json(&self) -> Value {
|
||||
let active = self.workspaces.active_id();
|
||||
let workspaces = self
|
||||
.workspaces
|
||||
.ordered()
|
||||
.output_workspaces
|
||||
.workspace_views(&self.workspaces)
|
||||
.into_iter()
|
||||
.map(|workspace| {
|
||||
.map(|view| {
|
||||
let workspace = self.workspaces.workspace(view.id).expect("workspace view exists");
|
||||
let mode = match workspace.layout.mode() {
|
||||
LayoutMode::Scrolling => "scrolling",
|
||||
LayoutMode::Tree => "tree",
|
||||
};
|
||||
json!({
|
||||
"id": workspace.id.0,
|
||||
"name": workspace.name,
|
||||
"active": workspace.id == active,
|
||||
"name": view.index.to_string(),
|
||||
"index": view.index,
|
||||
"output": view.output.0,
|
||||
"window_count": view.window_count,
|
||||
"visible": view.visible,
|
||||
"focused": view.focused,
|
||||
"active": view.focused,
|
||||
"layout": mode,
|
||||
"focused_window": workspace.last_focused.map(|window| window.0),
|
||||
"fullscreen_window": workspace.fullscreen.map(|window| window.0),
|
||||
|
||||
@@ -125,6 +125,7 @@ impl Default for OverviewState {
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct WorkspaceCard {
|
||||
pub workspace: WorkspaceId,
|
||||
pub index: u32,
|
||||
pub rect: Rect,
|
||||
pub selected: bool,
|
||||
pub windows: Vec<(WindowId, Rect)>,
|
||||
@@ -459,12 +460,11 @@ impl Ferese {
|
||||
|
||||
pub(crate) fn overview_workspace_label(
|
||||
&mut self,
|
||||
workspace: WorkspaceId,
|
||||
index: u32,
|
||||
scale: f64,
|
||||
) -> Option<(MemoryRenderBuffer, smithay::utils::Size<i32, smithay::utils::Buffer>)> {
|
||||
let name = self.workspaces.workspace(workspace)?.name.clone();
|
||||
self.overview
|
||||
.label(&name, scale, self.theme_settings.text_primary_color.0)
|
||||
.label(&index.to_string(), scale, self.theme_settings.text_primary_color.0)
|
||||
}
|
||||
|
||||
pub(crate) fn set_overview_active(&mut self, active: bool) {
|
||||
@@ -606,7 +606,12 @@ impl Ferese {
|
||||
let selected = self.output_workspaces.active_workspace(output_id);
|
||||
let strip = workspace_strip(bounds);
|
||||
let capacity = strip_capacity(strip);
|
||||
let workspaces = self.workspaces.ordered();
|
||||
let workspaces = self
|
||||
.output_workspaces
|
||||
.workspace_views(&self.workspaces)
|
||||
.into_iter()
|
||||
.filter(|view| view.output == output_id)
|
||||
.collect::<Vec<_>>();
|
||||
let selected_index = workspaces
|
||||
.iter()
|
||||
.position(|workspace| Some(workspace.id) == selected)
|
||||
@@ -629,7 +634,9 @@ impl Ferese {
|
||||
.skip(offset)
|
||||
.take(capacity)
|
||||
.enumerate()
|
||||
.map(|(index, workspace)| {
|
||||
.map(|(index, view)| {
|
||||
let workspace = self.workspaces.workspace(view.id).expect("workspace view exists");
|
||||
|
||||
let rect = Rect::new(
|
||||
start_x + index as f64 * (card_width + WORKSPACE_CARD_GAP),
|
||||
strip.y + 12.0,
|
||||
@@ -659,6 +666,7 @@ impl Ferese {
|
||||
let windows = preview_layout(preview_bounds, &windows, 4.0, 4.0).into_iter().collect();
|
||||
WorkspaceCard {
|
||||
workspace: workspace.id,
|
||||
index: view.index,
|
||||
rect,
|
||||
selected: Some(workspace.id) == selected,
|
||||
windows,
|
||||
@@ -721,10 +729,10 @@ impl Ferese {
|
||||
};
|
||||
let capacity = strip_capacity(workspace_strip(bounds));
|
||||
let cards = self.overview_workspace_cards(&output);
|
||||
let ordered = self.workspaces.ordered();
|
||||
let ordered = self.output_workspaces.ordered_workspaces(&self.workspaces, output_id);
|
||||
let current = cards
|
||||
.first()
|
||||
.and_then(|card| ordered.iter().position(|workspace| workspace.id == card.workspace))
|
||||
.and_then(|card| ordered.iter().position(|id| *id == card.workspace))
|
||||
.unwrap_or(0);
|
||||
let next = if delta > 0.0 {
|
||||
current.saturating_add(1)
|
||||
|
||||
@@ -237,7 +237,7 @@ pub(super) fn overview_strip_elements(
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((buffer, size)) = state.overview_workspace_label(card.workspace, scale) {
|
||||
if let Some((buffer, size)) = state.overview_workspace_label(card.index, scale) {
|
||||
let location = Point::<i32, Physical>::from((
|
||||
((card.rect.x - f64::from(output_geometry.loc.x) + card.rect.width * 0.5) * scale).round() as i32
|
||||
- size.w / 2,
|
||||
|
||||
@@ -318,20 +318,17 @@ impl Ferese {
|
||||
}
|
||||
|
||||
fn workspace_snapshots(&self) -> Vec<WorkspaceSnapshot> {
|
||||
self.workspaces
|
||||
.ordered()
|
||||
self.output_workspaces
|
||||
.workspace_views(&self.workspaces)
|
||||
.into_iter()
|
||||
.map(|workspace| {
|
||||
let output = self.output_workspaces.output_for_workspace(workspace.id);
|
||||
let active =
|
||||
output.is_some_and(|output| self.output_workspaces.active_workspace(output) == Some(workspace.id));
|
||||
|
||||
WorkspaceSnapshot {
|
||||
id: workspace.id,
|
||||
output,
|
||||
name: workspace.name.clone(),
|
||||
active,
|
||||
}
|
||||
.map(|view| WorkspaceSnapshot {
|
||||
id: view.id,
|
||||
output: Some(view.output),
|
||||
name: view.index.to_string(),
|
||||
index: view.index,
|
||||
window_count: view.window_count as u32,
|
||||
visible: view.visible,
|
||||
focused: view.focused,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -456,7 +453,10 @@ impl ShellSnapshot {
|
||||
output_hi,
|
||||
output_lo,
|
||||
workspace.name.clone(),
|
||||
u32::from(workspace.active),
|
||||
workspace.index,
|
||||
workspace.window_count,
|
||||
u32::from(workspace.visible),
|
||||
u32::from(workspace.focused),
|
||||
);
|
||||
}
|
||||
for snapshot in &self.windows {
|
||||
@@ -499,7 +499,10 @@ struct WorkspaceSnapshot {
|
||||
id: WorkspaceId,
|
||||
output: Option<OutputId>,
|
||||
name: String,
|
||||
active: bool,
|
||||
index: u32,
|
||||
window_count: u32,
|
||||
visible: bool,
|
||||
focused: bool,
|
||||
}
|
||||
|
||||
fn split_id(id: u64) -> (u32, u32) {
|
||||
@@ -559,7 +562,10 @@ mod tests {
|
||||
id: WorkspaceId(1),
|
||||
output: Some(OutputId(1)),
|
||||
name: "1".into(),
|
||||
active: true,
|
||||
index: 1,
|
||||
window_count: 1,
|
||||
visible: true,
|
||||
focused: true,
|
||||
}],
|
||||
windows: vec![ManagedWindowSnapshot {
|
||||
id: WindowId(1),
|
||||
@@ -575,7 +581,10 @@ mod tests {
|
||||
|s| s.outputs[0].active_workspace = WorkspaceId(2),
|
||||
|s| s.outputs.clear(),
|
||||
|s| s.workspaces[0].output = None,
|
||||
|s| s.workspaces[0].active = false,
|
||||
|s| s.workspaces[0].visible = false,
|
||||
|s| s.workspaces[0].focused = false,
|
||||
|s| s.workspaces[0].index = 2,
|
||||
|s| s.workspaces[0].window_count = 0,
|
||||
|s| s.workspaces[0].name = "renamed".into(),
|
||||
|s| s.windows[0].workspace = 2,
|
||||
|s| s.windows[0].title = "new title".into(),
|
||||
|
||||
@@ -1016,7 +1016,7 @@ mod tests {
|
||||
let mut workspaces = WorkspaceSet::default();
|
||||
let first = workspaces.active_id();
|
||||
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
|
||||
let second = workspaces.ensure_numeric(2).unwrap();
|
||||
let second = workspaces.create_workspace();
|
||||
workspaces.activate(second).unwrap();
|
||||
workspaces.insert_window(WindowId(2), Axis::Horizontal, 0.5).unwrap();
|
||||
workspaces.insert_window(WindowId(3), Axis::Horizontal, 0.5).unwrap();
|
||||
@@ -1027,7 +1027,7 @@ mod tests {
|
||||
.connect(OutputId(1), OutputGeometry::new(0, 0, 1920, 1080), first)
|
||||
.unwrap();
|
||||
let owner = if assignment == Some(OutputId(2)) {
|
||||
let third = workspaces.ensure_numeric(3).unwrap();
|
||||
let third = workspaces.create_workspace();
|
||||
outputs
|
||||
.connect(OutputId(2), OutputGeometry::new(1920, 0, 1920, 1080), third)
|
||||
.unwrap();
|
||||
|
||||
@@ -313,7 +313,7 @@ impl Ferese {
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
self.unmap_invisible_windows(&visible_windows);
|
||||
self.prune_empty_workspaces(&visible);
|
||||
self.reconcile_workspaces(&visible);
|
||||
self.send_shell_snapshots();
|
||||
active_animation = true;
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ impl Ferese {
|
||||
visible
|
||||
}
|
||||
|
||||
pub(super) fn prune_empty_workspaces(&mut self, visible: &HashSet<WorkspaceId>) {
|
||||
let mut protected = visible.clone();
|
||||
protected.extend(self.output_workspaces.history_workspaces());
|
||||
for workspace in self.workspaces.prune_empty(&protected) {
|
||||
self.output_workspaces.forget_workspace(workspace);
|
||||
pub(super) fn reconcile_workspaces(&mut self, visible: &HashSet<WorkspaceId>) {
|
||||
for workspace in self
|
||||
.output_workspaces
|
||||
.reconcile_workspaces(&mut self.workspaces, visible)
|
||||
{
|
||||
self.viewport_animations.remove(&workspace);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ impl Ferese {
|
||||
pub fn relayout(&mut self) {
|
||||
self.refresh_input_capture_zones();
|
||||
let visible_workspaces = self.visible_workspace_ids();
|
||||
self.prune_empty_workspaces(&visible_workspaces);
|
||||
self.reconcile_workspaces(&visible_workspaces);
|
||||
if self.session_lock.active {
|
||||
self.configure_lock_surfaces();
|
||||
}
|
||||
|
||||
@@ -394,16 +394,13 @@ impl Ferese {
|
||||
slide_direction: Option<SwipeDirection>,
|
||||
auto_back_and_forth: bool,
|
||||
) {
|
||||
let workspace = match self.workspaces.ensure_numeric(index) {
|
||||
Ok(workspace) => workspace,
|
||||
Err(error) => {
|
||||
tracing::error!(%error, index, "failed to switch workspace");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some(output) = self.output_workspaces.focused_output() else {
|
||||
return;
|
||||
};
|
||||
let Some(workspace) = self.output_workspaces.workspace_at(&self.workspaces, output, index) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let previous = self.output_workspaces.active_workspace(output);
|
||||
let owner = match self
|
||||
.output_workspaces
|
||||
@@ -526,26 +523,23 @@ impl Ferese {
|
||||
let Some(window) = self.focused_window else {
|
||||
return;
|
||||
};
|
||||
let Some(output) = self.output_workspaces.focused_output() else {
|
||||
return;
|
||||
};
|
||||
let Some(destination) = self.output_workspaces.workspace_at(&self.workspaces, output, index) else {
|
||||
return;
|
||||
};
|
||||
let axis = self
|
||||
.output_bounds()
|
||||
.and_then(|bounds| {
|
||||
let workspace = self.workspaces.ensure_numeric(index).ok()?;
|
||||
let target = self.workspaces.workspace(workspace)?;
|
||||
let target = self.workspaces.workspace(destination)?;
|
||||
target.layout.automatic_axis(target.last_focused, bounds).ok()
|
||||
})
|
||||
.unwrap_or(Axis::Horizontal);
|
||||
|
||||
let destination = match self.workspaces.move_window_to_numeric(window, index, axis, 0.5) {
|
||||
Ok(destination) => destination,
|
||||
Err(error) => {
|
||||
tracing::error!(%error, ?window, index, "failed to move window to workspace");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Some(output) = self.output_workspaces.focused_output()
|
||||
&& let Err(error) = self.output_workspaces.assign_workspace(output, destination)
|
||||
{
|
||||
tracing::error!(%error, ?destination, "failed to assign destination workspace");
|
||||
if let Err(error) = self.workspaces.move_window_to_workspace(window, destination, axis, 0.5) {
|
||||
tracing::error!(%error, ?window, index, "failed to move window to workspace");
|
||||
return;
|
||||
}
|
||||
|
||||
self.focused_window = self.workspaces.active().last_focused;
|
||||
|
||||
@@ -15,25 +15,23 @@ impl Ferese {
|
||||
self.next_output_id = self.next_output_id.saturating_add(1);
|
||||
id
|
||||
});
|
||||
let fallback_workspace = (1..)
|
||||
.find_map(|index| {
|
||||
let workspace = self.workspaces.ensure_numeric(index).ok()?;
|
||||
self.output_workspaces
|
||||
.output_for_workspace(workspace)
|
||||
.is_none()
|
||||
.then_some(workspace)
|
||||
})
|
||||
.expect("numeric workspaces are inexhaustible");
|
||||
let fallback_workspace = self
|
||||
.workspaces
|
||||
.iter()
|
||||
.filter(|workspace| self.output_workspaces.output_for_workspace(workspace.id).is_none())
|
||||
.map(|workspace| workspace.id)
|
||||
.min_by_key(|id| id.0)
|
||||
.unwrap_or_else(|| self.workspaces.create_workspace());
|
||||
let geometry = OutputGeometry::new(geometry.loc.x, geometry.loc.y, geometry.size.w, geometry.size.h);
|
||||
|
||||
let registration = self.output_workspaces.connect(output_id, geometry, fallback_workspace);
|
||||
|
||||
match registration {
|
||||
Ok(workspace) => {
|
||||
Ok(_) => {
|
||||
self.output_ids.insert(output.clone(), output_id);
|
||||
if self.output_workspaces.focused_output() == Some(output_id) {
|
||||
self.activate_output_workspace(output_id, workspace);
|
||||
}
|
||||
self.restore_output_focus();
|
||||
let visible = self.visible_workspace_ids();
|
||||
self.reconcile_workspaces(&visible);
|
||||
self.send_shell_snapshots();
|
||||
}
|
||||
Err(error) => {
|
||||
|
||||
@@ -287,10 +287,15 @@ impl Ferese {
|
||||
.automatic_axis(self.focused_window, bounds)
|
||||
.unwrap_or(Axis::Horizontal);
|
||||
|
||||
if let Some(workspace) = rule.workspace
|
||||
&& let Err(error) = self.workspaces.move_window_to_numeric(id, workspace, axis, 0.5)
|
||||
if let Some(index) = rule.workspace
|
||||
&& let Some(output) = self
|
||||
.workspaces
|
||||
.workspace_for_window(id)
|
||||
.and_then(|workspace| self.output_workspaces.output_for_workspace(workspace))
|
||||
&& let Some(workspace) = self.output_workspaces.workspace_at(&self.workspaces, output, index)
|
||||
&& let Err(error) = self.workspaces.move_window_to_workspace(id, workspace, axis, 0.5)
|
||||
{
|
||||
tracing::warn!(%error, ?id, workspace, "failed to apply window workspace rule");
|
||||
tracing::warn!(%error, ?id, ?workspace, "failed to apply window workspace rule");
|
||||
}
|
||||
|
||||
let bounds = self.floating_bounds_for_window(id).unwrap_or(bounds);
|
||||
|
||||
Reference in new issue
Block a user