feat: integrate numeric workspaces
This commit is contained in:
1 parent
19abb033c5
commit
3d8c7b0c4f
4 files changed
+187
-16
No files matched your search
@@ -48,6 +48,12 @@ impl XdgActivationHandler for Ferese {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.focused_window = self.window_ids.get(&window).copied();
|
self.focused_window = self.window_ids.get(&window).copied();
|
||||||
|
if let Some(focused) = self.focused_window
|
||||||
|
&& let Err(error) = self.workspaces.focus_window(focused)
|
||||||
|
{
|
||||||
|
tracing::error!(%error, ?focused, "failed to update workspace focus");
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.space.raise_element(&window, true);
|
self.space.raise_element(&window, true);
|
||||||
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
|
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
|
||||||
keyboard.set_focus(self, Some(surface), SERIAL_COUNTER.next_serial());
|
keyboard.set_focus(self, Some(surface), SERIAL_COUNTER.next_serial());
|
||||||
|
|||||||
+36
-1
@@ -37,8 +37,9 @@ impl Ferese {
|
|||||||
let supported_modifiers = modifiers.logo
|
let supported_modifiers = modifiers.logo
|
||||||
&& !modifiers.alt
|
&& !modifiers.alt
|
||||||
&& !(modifiers.ctrl && modifiers.shift);
|
&& !(modifiers.ctrl && modifiers.shift);
|
||||||
|
let symbol = keysym.modified_sym().raw();
|
||||||
let direction = if supported_modifiers {
|
let direction = if supported_modifiers {
|
||||||
match keysym.modified_sym().raw() {
|
match symbol {
|
||||||
keysyms::KEY_h | keysyms::KEY_H => Some(Direction::Left),
|
keysyms::KEY_h | keysyms::KEY_H => Some(Direction::Left),
|
||||||
keysyms::KEY_j | keysyms::KEY_J => Some(Direction::Down),
|
keysyms::KEY_j | keysyms::KEY_J => Some(Direction::Down),
|
||||||
keysyms::KEY_k | keysyms::KEY_K => Some(Direction::Up),
|
keysyms::KEY_k | keysyms::KEY_K => Some(Direction::Up),
|
||||||
@@ -63,6 +64,35 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FilterResult::Intercept(())
|
FilterResult::Intercept(())
|
||||||
|
} else if modifiers.logo && !modifiers.ctrl && !modifiers.alt {
|
||||||
|
let workspace = match symbol {
|
||||||
|
keysyms::KEY_1 => Some(1),
|
||||||
|
keysyms::KEY_2 => Some(2),
|
||||||
|
keysyms::KEY_3 => Some(3),
|
||||||
|
keysyms::KEY_4 => Some(4),
|
||||||
|
keysyms::KEY_5 => Some(5),
|
||||||
|
keysyms::KEY_6 => Some(6),
|
||||||
|
keysyms::KEY_7 => Some(7),
|
||||||
|
keysyms::KEY_8 => Some(8),
|
||||||
|
keysyms::KEY_9 => Some(9),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(workspace) = workspace {
|
||||||
|
if state == KeyState::Pressed {
|
||||||
|
data.intercepted_keys.insert(keycode);
|
||||||
|
|
||||||
|
if modifiers.shift {
|
||||||
|
data.move_focused_to_workspace(workspace);
|
||||||
|
} else {
|
||||||
|
data.switch_workspace(workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterResult::Intercept(())
|
||||||
|
} else {
|
||||||
|
FilterResult::Forward
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
FilterResult::Forward
|
FilterResult::Forward
|
||||||
}
|
}
|
||||||
@@ -175,6 +205,11 @@ impl Ferese {
|
|||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
|
|
||||||
self.focused_window = self.window_ids.get(&window).copied();
|
self.focused_window = self.window_ids.get(&window).copied();
|
||||||
|
if let Some(focused) = self.focused_window
|
||||||
|
&& let Err(error) = self.workspaces.focus_window(focused)
|
||||||
|
{
|
||||||
|
tracing::error!(%error, ?focused, "failed to update workspace focus");
|
||||||
|
}
|
||||||
self.space.raise_element(&window, true);
|
self.space.raise_element(&window, true);
|
||||||
let surface = window
|
let surface = window
|
||||||
.toplevel()
|
.toplevel()
|
||||||
|
|||||||
+113
-15
@@ -6,7 +6,7 @@ use std::{
|
|||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ferese_layout::{Axis, Direction, GapConfig, LayoutTree, Rect, WindowId};
|
use ferese_layout::{Axis, Direction, GapConfig, Rect, WindowId, WorkspaceSet};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::{PopupManager, Space, Window, WindowSurfaceType},
|
desktop::{PopupManager, Space, Window, WindowSurfaceType},
|
||||||
@@ -43,7 +43,7 @@ pub struct Ferese {
|
|||||||
pub display_handle: DisplayHandle,
|
pub display_handle: DisplayHandle,
|
||||||
pub loop_signal: LoopSignal,
|
pub loop_signal: LoopSignal,
|
||||||
pub space: Space<Window>,
|
pub space: Space<Window>,
|
||||||
pub layout: LayoutTree,
|
pub workspaces: WorkspaceSet,
|
||||||
pub window_ids: HashMap<Window, WindowId>,
|
pub window_ids: HashMap<Window, WindowId>,
|
||||||
pub focused_window: Option<WindowId>,
|
pub focused_window: Option<WindowId>,
|
||||||
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
|
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
|
||||||
@@ -98,7 +98,7 @@ impl Ferese {
|
|||||||
display_handle,
|
display_handle,
|
||||||
loop_signal: event_loop.get_signal(),
|
loop_signal: event_loop.get_signal(),
|
||||||
space: Space::default(),
|
space: Space::default(),
|
||||||
layout: LayoutTree::default(),
|
workspaces: WorkspaceSet::default(),
|
||||||
window_ids: HashMap::new(),
|
window_ids: HashMap::new(),
|
||||||
focused_window: None,
|
focused_window: None,
|
||||||
intercepted_keys: HashSet::new(),
|
intercepted_keys: HashSet::new(),
|
||||||
@@ -170,10 +170,16 @@ impl Ferese {
|
|||||||
|
|
||||||
let axis = self
|
let axis = self
|
||||||
.output_bounds()
|
.output_bounds()
|
||||||
.and_then(|bounds| self.layout.automatic_axis(self.focused_window, bounds).ok())
|
.and_then(|bounds| {
|
||||||
|
self.workspaces
|
||||||
|
.active()
|
||||||
|
.layout
|
||||||
|
.automatic_axis(self.focused_window, bounds)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
.unwrap_or(Axis::Horizontal);
|
.unwrap_or(Axis::Horizontal);
|
||||||
|
|
||||||
if let Err(error) = self.layout.insert(id, self.focused_window, axis, 0.5) {
|
if let Err(error) = self.workspaces.insert_window(id, axis, 0.5) {
|
||||||
tracing::error!(%error, ?id, "failed to insert window into layout");
|
tracing::error!(%error, ?id, "failed to insert window into layout");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -190,15 +196,11 @@ impl Ferese {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.space.unmap_elem(window);
|
self.space.unmap_elem(window);
|
||||||
if let Err(error) = self.layout.remove(id) {
|
if let Err(error) = self.workspaces.remove_window(id) {
|
||||||
tracing::error!(%error, ?id, "failed to remove window from layout");
|
tracing::error!(%error, ?id, "failed to remove window from layout");
|
||||||
}
|
}
|
||||||
if self.focused_window == Some(id) {
|
if self.focused_window == Some(id) {
|
||||||
self.focused_window = self
|
self.focused_window = self.workspaces.active().last_focused;
|
||||||
.space
|
|
||||||
.elements()
|
|
||||||
.rev()
|
|
||||||
.find_map(|window| self.window_ids.get(window).copied());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.relayout();
|
self.relayout();
|
||||||
@@ -208,7 +210,24 @@ impl Ferese {
|
|||||||
let Some(bounds) = self.output_bounds() else {
|
let Some(bounds) = self.output_bounds() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let geometry = match self.layout.geometry_with_gaps(bounds, GapConfig::default()) {
|
let active = self.workspaces.active_id();
|
||||||
|
let inactive = self
|
||||||
|
.window_ids
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, id)| self.workspaces.workspace_for_window(**id) != Some(active))
|
||||||
|
.map(|(window, _)| window.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for window in inactive {
|
||||||
|
self.space.unmap_elem(&window);
|
||||||
|
}
|
||||||
|
|
||||||
|
let geometry = match self
|
||||||
|
.workspaces
|
||||||
|
.active()
|
||||||
|
.layout
|
||||||
|
.geometry_with_gaps(bounds, GapConfig::default())
|
||||||
|
{
|
||||||
Ok(geometry) => geometry,
|
Ok(geometry) => geometry,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(%error, "failed to compute tiled geometry");
|
tracing::error!(%error, "failed to compute tiled geometry");
|
||||||
@@ -243,9 +262,19 @@ impl Ferese {
|
|||||||
let Some(bounds) = self.output_bounds() else {
|
let Some(bounds) = self.output_bounds() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Ok(Some(next)) = self.layout.directional_neighbor(current, direction, bounds) else {
|
let Ok(Some(next)) = self
|
||||||
|
.workspaces
|
||||||
|
.active()
|
||||||
|
.layout
|
||||||
|
.directional_neighbor(current, direction, bounds)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Err(error) = self.workspaces.focus_window(next) {
|
||||||
|
tracing::error!(%error, ?next, "failed to update workspace focus");
|
||||||
|
return;
|
||||||
|
}
|
||||||
let Some(window) = self
|
let Some(window) = self
|
||||||
.window_ids
|
.window_ids
|
||||||
.iter()
|
.iter()
|
||||||
@@ -286,7 +315,12 @@ impl Ferese {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.layout.move_window(current, direction, bounds) {
|
match self
|
||||||
|
.workspaces
|
||||||
|
.active_mut()
|
||||||
|
.layout
|
||||||
|
.move_window(current, direction, bounds)
|
||||||
|
{
|
||||||
Ok(true) => self.relayout(),
|
Ok(true) => self.relayout(),
|
||||||
Ok(false) => {}
|
Ok(false) => {}
|
||||||
Err(error) => tracing::error!(%error, ?current, "failed to move tiled window"),
|
Err(error) => tracing::error!(%error, ?current, "failed to move tiled window"),
|
||||||
@@ -300,13 +334,77 @@ impl Ferese {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.layout.resize_window(current, direction, RESIZE_STEP) {
|
match self
|
||||||
|
.workspaces
|
||||||
|
.active_mut()
|
||||||
|
.layout
|
||||||
|
.resize_window(current, direction, RESIZE_STEP)
|
||||||
|
{
|
||||||
Ok(true) => self.relayout(),
|
Ok(true) => self.relayout(),
|
||||||
Ok(false) => {}
|
Ok(false) => {}
|
||||||
Err(error) => tracing::error!(%error, ?current, "failed to resize tiled window"),
|
Err(error) => tracing::error!(%error, ?current, "failed to resize tiled window"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn switch_workspace(&mut self, index: u32) {
|
||||||
|
let focus = match self.workspaces.switch_to_numeric(index) {
|
||||||
|
Ok(focus) => focus,
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(%error, index, "failed to switch workspace");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.focused_window = focus;
|
||||||
|
self.relayout();
|
||||||
|
self.restore_keyboard_focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_focused_to_workspace(&mut self, index: u32) {
|
||||||
|
let Some(window) = self.focused_window else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let axis = self
|
||||||
|
.output_bounds()
|
||||||
|
.and_then(|bounds| {
|
||||||
|
let workspace = self.workspaces.ensure_numeric(index).ok()?;
|
||||||
|
let target = self.workspaces.workspace(workspace)?;
|
||||||
|
target
|
||||||
|
.layout
|
||||||
|
.automatic_axis(target.last_focused, bounds)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.unwrap_or(Axis::Horizontal);
|
||||||
|
|
||||||
|
if let Err(error) = self
|
||||||
|
.workspaces
|
||||||
|
.move_window_to_numeric(window, index, axis, 0.5)
|
||||||
|
{
|
||||||
|
tracing::error!(%error, ?window, index, "failed to move window to workspace");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.focused_window = self.workspaces.active().last_focused;
|
||||||
|
self.relayout();
|
||||||
|
self.restore_keyboard_focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_keyboard_focus(&mut self) {
|
||||||
|
let surface = self.focused_window.and_then(|focused| {
|
||||||
|
self.window_ids.iter().find_map(|(window, id)| {
|
||||||
|
(*id == focused)
|
||||||
|
.then(|| window.toplevel())
|
||||||
|
.flatten()
|
||||||
|
.map(|toplevel| toplevel.wl_surface().clone())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
self.seat
|
||||||
|
.get_keyboard()
|
||||||
|
.expect("seat has a keyboard")
|
||||||
|
.set_focus(self, surface, smithay::utils::SERIAL_COUNTER.next_serial());
|
||||||
|
}
|
||||||
|
|
||||||
fn output_bounds(&self) -> Option<Rect> {
|
fn output_bounds(&self) -> Option<Rect> {
|
||||||
let output = self.space.outputs().next()?;
|
let output = self.space.outputs().next()?;
|
||||||
let geometry = self.space.output_geometry(output)?;
|
let geometry = self.space.output_geometry(output)?;
|
||||||
|
|||||||
@@ -111,6 +111,19 @@ impl WorkspaceSet {
|
|||||||
self.window_workspaces.get(&window).copied()
|
self.window_workspaces.get(&window).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focus_window(&mut self, window: WindowId) -> Result<(), WorkspaceError> {
|
||||||
|
if self.workspace_for_window(window) != Some(self.active) {
|
||||||
|
return Err(WorkspaceError::InvalidState(
|
||||||
|
"focused window is not on the active workspace",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_mut().last_focused = Some(window);
|
||||||
|
|
||||||
|
debug_assert!(self.validate().is_ok());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ensure_numeric(&mut self, index: u32) -> Result<WorkspaceId, WorkspaceError> {
|
pub fn ensure_numeric(&mut self, index: u32) -> Result<WorkspaceId, WorkspaceError> {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
return Err(WorkspaceError::InvalidNumericName(index));
|
return Err(WorkspaceError::InvalidNumericName(index));
|
||||||
@@ -360,4 +373,23 @@ mod tests {
|
|||||||
assert_eq!(workspaces.active().last_focused, Some(WindowId(2)));
|
assert_eq!(workspaces.active().last_focused, Some(WindowId(2)));
|
||||||
assert!(workspaces.validate().is_ok());
|
assert!(workspaces.validate().is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn focus_rejects_a_window_on_an_inactive_workspace() {
|
||||||
|
let mut workspaces = WorkspaceSet::default();
|
||||||
|
workspaces
|
||||||
|
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
workspaces
|
||||||
|
.move_window_to_numeric(WindowId(1), 2, Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
workspaces.focus_window(WindowId(1)),
|
||||||
|
Err(WorkspaceError::InvalidState(
|
||||||
|
"focused window is not on the active workspace"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert!(workspaces.validate().is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user