Keep fullscreen dialogs visible and preserve window focus
This commit is contained in:
1 parent
eda453dabd
commit
b3de13e27e
3 files changed
+142
-22
No files matched your search
@@ -1,8 +1,15 @@
|
|||||||
use ferese_layout::WindowId;
|
use ferese_layout::WindowId;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub(crate) fn layer_priority(floating: bool, zooming: bool, fullscreen: bool) -> u8 {
|
pub(crate) fn layer_priority(
|
||||||
if fullscreen {
|
floating: bool,
|
||||||
|
zooming: bool,
|
||||||
|
fullscreen: bool,
|
||||||
|
above_fullscreen: bool,
|
||||||
|
) -> u8 {
|
||||||
|
if above_fullscreen {
|
||||||
|
4
|
||||||
|
} else if fullscreen {
|
||||||
3
|
3
|
||||||
} else if floating {
|
} else if floating {
|
||||||
2
|
2
|
||||||
@@ -90,9 +97,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn floating_windows_stay_above_tiled_and_maximized_windows() {
|
fn floating_windows_stay_above_tiled_and_maximized_windows() {
|
||||||
assert!(layer_priority(true, false, false) > layer_priority(false, true, false));
|
assert!(
|
||||||
assert!(layer_priority(false, true, false) > layer_priority(false, false, false));
|
layer_priority(true, false, false, false) > layer_priority(false, true, false, false)
|
||||||
assert!(layer_priority(false, false, true) > layer_priority(true, false, false));
|
);
|
||||||
|
assert!(
|
||||||
|
layer_priority(false, true, false, false) > layer_priority(false, false, false, false)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
layer_priority(false, false, true, false) > layer_priority(true, false, false, false)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
layer_priority(true, false, false, true) > layer_priority(false, false, true, false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+80
-10
@@ -128,6 +128,7 @@ pub struct Ferese {
|
|||||||
maximized_windows: HashSet<WindowId>,
|
maximized_windows: HashSet<WindowId>,
|
||||||
maximized_column_widths: HashMap<WindowId, ColumnWidth>,
|
maximized_column_widths: HashMap<WindowId, ColumnWidth>,
|
||||||
window_stack: crate::stacking::WindowStack,
|
window_stack: crate::stacking::WindowStack,
|
||||||
|
floating_above_fullscreen: HashMap<WindowId, WindowId>,
|
||||||
natural_floating_pending: HashSet<WindowId>,
|
natural_floating_pending: HashSet<WindowId>,
|
||||||
pub(crate) window_borders: HashMap<WindowId, crate::winit::WindowBorderBuffers>,
|
pub(crate) window_borders: HashMap<WindowId, crate::winit::WindowBorderBuffers>,
|
||||||
pub(crate) window_dims: HashMap<WindowId, crate::winit::WindowBorderBuffers>,
|
pub(crate) window_dims: HashMap<WindowId, crate::winit::WindowBorderBuffers>,
|
||||||
@@ -347,6 +348,7 @@ impl Ferese {
|
|||||||
maximized_windows: HashSet::new(),
|
maximized_windows: HashSet::new(),
|
||||||
maximized_column_widths: HashMap::new(),
|
maximized_column_widths: HashMap::new(),
|
||||||
window_stack: crate::stacking::WindowStack::default(),
|
window_stack: crate::stacking::WindowStack::default(),
|
||||||
|
floating_above_fullscreen: HashMap::new(),
|
||||||
natural_floating_pending: HashSet::new(),
|
natural_floating_pending: HashSet::new(),
|
||||||
window_borders: HashMap::new(),
|
window_borders: HashMap::new(),
|
||||||
window_dims: HashMap::new(),
|
window_dims: HashMap::new(),
|
||||||
@@ -804,15 +806,27 @@ impl Ferese {
|
|||||||
self.inverse_presented_window_point(*id, position.x, position.y)?;
|
self.inverse_presented_window_point(*id, position.x, position.y)?;
|
||||||
let source_point = Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
|
let source_point = Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
|
||||||
|
|
||||||
window
|
let surface = window.surface_under(source_point, WindowSurfaceType::ALL);
|
||||||
.surface_under(source_point, WindowSurfaceType::ALL)
|
let (surface, surface_location) = match surface {
|
||||||
.map(|(surface, surface_location)| {
|
Some(hit) => hit,
|
||||||
let surface_point = source_point - surface_location.to_f64();
|
None if self.window_fills_visual_bounds(*id, workspace) => {
|
||||||
(surface, position - surface_point)
|
(window.toplevel()?.wl_surface().clone(), Point::from((0, 0)))
|
||||||
})
|
}
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let surface_point = source_point - surface_location.to_f64();
|
||||||
|
Some((surface, position - surface_point))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window_fills_visual_bounds(&self, id: WindowId, workspace: WorkspaceId) -> bool {
|
||||||
|
self.maximized_windows.contains(&id)
|
||||||
|
|| self
|
||||||
|
.workspaces
|
||||||
|
.workspace(workspace)
|
||||||
|
.is_some_and(|workspace| workspace.fullscreen == Some(id))
|
||||||
|
}
|
||||||
|
|
||||||
fn layer_surface_under(
|
fn layer_surface_under(
|
||||||
&self,
|
&self,
|
||||||
position: Point<f64, Logical>,
|
position: Point<f64, Logical>,
|
||||||
@@ -887,7 +901,13 @@ impl Ferese {
|
|||||||
self.inverse_presented_window_point(*id, position.x, position.y)?;
|
self.inverse_presented_window_point(*id, position.x, position.y)?;
|
||||||
let source_point =
|
let source_point =
|
||||||
Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
|
Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
|
||||||
window.surface_under(source_point, WindowSurfaceType::ALL)?;
|
if window
|
||||||
|
.surface_under(source_point, WindowSurfaceType::ALL)
|
||||||
|
.is_none()
|
||||||
|
&& !self.window_fills_visual_bounds(*id, workspace)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(window.clone())
|
Some(window.clone())
|
||||||
@@ -941,6 +961,17 @@ impl Ferese {
|
|||||||
floating,
|
floating,
|
||||||
geometry.is_some_and(|geometry| geometry.is_zooming()),
|
geometry.is_some_and(|geometry| geometry.is_zooming()),
|
||||||
geometry.is_some_and(|geometry| geometry.is_fullscreen()),
|
geometry.is_some_and(|geometry| geometry.is_fullscreen()),
|
||||||
|
floating
|
||||||
|
&& id.is_some_and(|id| {
|
||||||
|
self.floating_above_fullscreen
|
||||||
|
.get(&id)
|
||||||
|
.is_some_and(|parent| {
|
||||||
|
self.workspaces
|
||||||
|
.workspace_for_window(id)
|
||||||
|
.and_then(|workspace| self.workspaces.workspace(workspace))
|
||||||
|
.is_some_and(|workspace| workspace.fullscreen == Some(*parent))
|
||||||
|
})
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
(
|
(
|
||||||
priority,
|
priority,
|
||||||
@@ -1027,7 +1058,8 @@ impl Ferese {
|
|||||||
let rect = client_size(&window)
|
let rect = client_size(&window)
|
||||||
.map(|size| natural_floating_rect(bounds, size))
|
.map(|size| natural_floating_rect(bounds, size))
|
||||||
.unwrap_or_else(|| centered_floating_rect(bounds));
|
.unwrap_or_else(|| centered_floating_rect(bounds));
|
||||||
let focus = self.workspaces.active().fullscreen.is_none();
|
let fullscreen = self.workspaces.active().fullscreen;
|
||||||
|
let focus = true;
|
||||||
if let Err(error) =
|
if let Err(error) =
|
||||||
self.workspaces
|
self.workspaces
|
||||||
.insert_floating_window(id, self.workspaces.active_id(), rect, focus)
|
.insert_floating_window(id, self.workspaces.active_id(), rect, focus)
|
||||||
@@ -1040,6 +1072,9 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
self.window_ids.insert(window.clone(), id);
|
self.window_ids.insert(window.clone(), id);
|
||||||
self.window_stack.insert(id);
|
self.window_stack.insert(id);
|
||||||
|
if let Some(fullscreen) = fullscreen {
|
||||||
|
self.floating_above_fullscreen.insert(id, fullscreen);
|
||||||
|
}
|
||||||
if focus {
|
if focus {
|
||||||
self.focused_window = Some(id);
|
self.focused_window = Some(id);
|
||||||
}
|
}
|
||||||
@@ -1095,9 +1130,12 @@ impl Ferese {
|
|||||||
let id = WindowId(self.next_window_id);
|
let id = WindowId(self.next_window_id);
|
||||||
self.next_window_id += 1;
|
self.next_window_id += 1;
|
||||||
|
|
||||||
|
let focus = workspace == self.workspaces.active_id()
|
||||||
|
&& (self.focused_window == Some(parent)
|
||||||
|
|| self.workspaces.active().fullscreen == Some(parent));
|
||||||
if let Err(error) = self
|
if let Err(error) = self
|
||||||
.workspaces
|
.workspaces
|
||||||
.insert_floating_window(id, workspace, rect, false)
|
.insert_floating_window(id, workspace, rect, focus)
|
||||||
{
|
{
|
||||||
tracing::error!(%error, ?id, ?parent, "failed to insert transient window");
|
tracing::error!(%error, ?id, ?parent, "failed to insert transient window");
|
||||||
return;
|
return;
|
||||||
@@ -1105,8 +1143,21 @@ impl Ferese {
|
|||||||
|
|
||||||
self.window_ids.insert(window.clone(), id);
|
self.window_ids.insert(window.clone(), id);
|
||||||
self.window_stack.insert(id);
|
self.window_stack.insert(id);
|
||||||
self.space.map_element(window, (0, 0), false);
|
if self
|
||||||
|
.workspaces
|
||||||
|
.workspace(workspace)
|
||||||
|
.is_some_and(|workspace| workspace.fullscreen == Some(parent))
|
||||||
|
{
|
||||||
|
self.floating_above_fullscreen.insert(id, parent);
|
||||||
|
}
|
||||||
|
if focus {
|
||||||
|
self.focused_window = Some(id);
|
||||||
|
}
|
||||||
|
self.space.map_element(window, (0, 0), focus);
|
||||||
self.relayout();
|
self.relayout();
|
||||||
|
if focus {
|
||||||
|
self.restore_keyboard_focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_initial_window_rules(
|
pub(crate) fn apply_initial_window_rules(
|
||||||
@@ -1273,7 +1324,25 @@ impl Ferese {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self
|
||||||
|
.workspaces
|
||||||
|
.workspace_for_window(parent)
|
||||||
|
.and_then(|workspace| self.workspaces.workspace(workspace))
|
||||||
|
.is_some_and(|workspace| workspace.fullscreen == Some(parent))
|
||||||
|
{
|
||||||
|
self.floating_above_fullscreen.insert(id, parent);
|
||||||
|
}
|
||||||
|
if self.focused_window == Some(parent) {
|
||||||
|
if let Err(error) = self.workspaces.focus_window(id) {
|
||||||
|
tracing::warn!(%error, ?id, "failed to focus transient window");
|
||||||
|
} else {
|
||||||
|
self.focused_window = Some(id);
|
||||||
|
self.window_stack.raise(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.relayout();
|
self.relayout();
|
||||||
|
self.restore_keyboard_focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_tiled_window(&mut self, window: &Window) {
|
pub fn remove_tiled_window(&mut self, window: &Window) {
|
||||||
@@ -1289,6 +1358,7 @@ impl Ferese {
|
|||||||
self.maximized_windows.remove(&id);
|
self.maximized_windows.remove(&id);
|
||||||
self.maximized_column_widths.remove(&id);
|
self.maximized_column_widths.remove(&id);
|
||||||
self.window_stack.remove(id);
|
self.window_stack.remove(id);
|
||||||
|
self.floating_above_fullscreen.remove(&id);
|
||||||
self.natural_floating_pending.remove(&id);
|
self.natural_floating_pending.remove(&id);
|
||||||
self.window_borders.remove(&id);
|
self.window_borders.remove(&id);
|
||||||
self.window_dims.remove(&id);
|
self.window_dims.remove(&id);
|
||||||
|
|||||||
@@ -515,7 +515,14 @@ impl WorkspaceSet {
|
|||||||
self.active = workspace;
|
self.active = workspace;
|
||||||
|
|
||||||
debug_assert!(self.validate().is_ok());
|
debug_assert!(self.validate().is_ok());
|
||||||
Ok(self.active().fullscreen.or(self.active().last_focused))
|
let focused = self.active().last_focused;
|
||||||
|
let visible_floating = focused.filter(|window| {
|
||||||
|
matches!(
|
||||||
|
self.placement(*window),
|
||||||
|
Some(WindowPlacement::Floating { .. })
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(visible_floating.or(self.active().fullscreen).or(focused))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn workspace(&self, id: WorkspaceId) -> Option<&Workspace> {
|
pub fn workspace(&self, id: WorkspaceId) -> Option<&Workspace> {
|
||||||
@@ -631,11 +638,13 @@ impl WorkspaceSet {
|
|||||||
"focused window is not on the active workspace",
|
"focused window is not on the active workspace",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if self
|
if self.active().fullscreen.is_some_and(|fullscreen| {
|
||||||
.active()
|
fullscreen != window
|
||||||
.fullscreen
|
&& !matches!(
|
||||||
.is_some_and(|fullscreen| fullscreen != window)
|
self.placement(window),
|
||||||
{
|
Some(WindowPlacement::Floating { .. })
|
||||||
|
)
|
||||||
|
}) {
|
||||||
return Err(WorkspaceError::InvalidState(
|
return Err(WorkspaceError::InvalidState(
|
||||||
"focused window is hidden by fullscreen",
|
"focused window is hidden by fullscreen",
|
||||||
));
|
));
|
||||||
@@ -823,7 +832,7 @@ impl WorkspaceSet {
|
|||||||
.get_mut(&workspace_id)
|
.get_mut(&workspace_id)
|
||||||
.ok_or(WorkspaceError::UnknownWorkspace(workspace_id))?;
|
.ok_or(WorkspaceError::UnknownWorkspace(workspace_id))?;
|
||||||
workspace.floating.push(window);
|
workspace.floating.push(window);
|
||||||
if focus && workspace.fullscreen.is_none() {
|
if focus {
|
||||||
workspace.last_focused = Some(window);
|
workspace.last_focused = Some(window);
|
||||||
}
|
}
|
||||||
self.window_workspaces.insert(window, workspace_id);
|
self.window_workspaces.insert(window, workspace_id);
|
||||||
@@ -1613,6 +1622,31 @@ mod tests {
|
|||||||
assert!(workspaces.validate().is_ok());
|
assert!(workspaces.validate().is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn floating_dialog_can_focus_above_fullscreen() {
|
||||||
|
let mut workspaces = WorkspaceSet::default();
|
||||||
|
let workspace = workspaces.active_id();
|
||||||
|
workspaces
|
||||||
|
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
workspaces.toggle_fullscreen(WindowId(1)).unwrap();
|
||||||
|
workspaces
|
||||||
|
.insert_floating_window(
|
||||||
|
WindowId(2),
|
||||||
|
workspace,
|
||||||
|
Rect::new(100.0, 100.0, 500.0, 400.0),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(workspaces.active().last_focused, Some(WindowId(2)));
|
||||||
|
assert!(workspaces.focus_window(WindowId(2)).is_ok());
|
||||||
|
workspaces.switch_to_numeric(2).unwrap();
|
||||||
|
assert_eq!(workspaces.activate(workspace).unwrap(), Some(WindowId(2)));
|
||||||
|
assert_eq!(workspaces.active().fullscreen, Some(WindowId(1)));
|
||||||
|
assert!(workspaces.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn switching_to_fullscreen_restores_the_visible_window() {
|
fn switching_to_fullscreen_restores_the_visible_window() {
|
||||||
let mut workspaces = WorkspaceSet::default();
|
let mut workspaces = WorkspaceSet::default();
|
||||||
|
|||||||
Reference in new issue
Block a user