Account for border in contents_under()

Fixes pointer clicks going through window borders to a layer surface below,
also fixes window not getting activated in all cases from a border click.
This commit is contained in:
Ivan Molodetskikh
2025-02-10 08:03:39 +03:00
parent b65fad09d8
commit 1d883931b4
4 changed files with 63 additions and 41 deletions
+28 -10
View File
@@ -450,6 +450,20 @@ pub enum AddWindowTarget<'a, W: LayoutElement> {
NextTo(&'a W::Id), NextTo(&'a W::Id),
} }
/// Type of the window hit from `window_under()`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HitType {
/// The hit is within a window's input region and can be used for sending events to it.
Input {
/// Position of the window's buffer.
win_pos: Point<f64, Logical>,
},
/// The hit can activate a window, but it is not in the input region so cannot send events.
///
/// For example, this could be clicking on a tile border outside the window.
Activate,
}
impl<W: LayoutElement> InteractiveMoveState<W> { impl<W: LayoutElement> InteractiveMoveState<W> {
fn moving(&self) -> Option<&InteractiveMoveData<W>> { fn moving(&self) -> Option<&InteractiveMoveData<W>> {
match self { match self {
@@ -485,6 +499,16 @@ impl ActivateWindow {
} }
} }
impl HitType {
pub fn offset_win_pos(mut self, offset: Point<f64, Logical>) -> Self {
match &mut self {
HitType::Input { win_pos } => *win_pos += offset,
HitType::Activate => (),
}
self
}
}
impl Options { impl Options {
fn from_config(config: &Config) -> Self { fn from_config(config: &Config) -> Self {
let layout = &config.layout; let layout = &config.layout;
@@ -2155,18 +2179,12 @@ impl<W: LayoutElement> Layout<W> {
mon.active_window().map(|win| (win, &mon.output)) mon.active_window().map(|win| (win, &mon.output))
} }
/// Returns the window under the cursor and the position of its toplevel surface within the /// Returns the window under the cursor and the hit type.
/// output.
///
/// `Some((w, Some(p)))` means that the cursor is within the window's input region and can be
/// used for delivering events to the window. `Some((w, None))` means that the cursor is within
/// the window's activation region, but not within the window's input region. For example, the
/// cursor may be on the window's server-side border.
pub fn window_under( pub fn window_under(
&self, &self,
output: &Output, output: &Output,
pos_within_output: Point<f64, Logical>, pos_within_output: Point<f64, Logical>,
) -> Option<(&W, Option<Point<f64, Logical>>)> { ) -> Option<(&W, HitType)> {
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
return None; return None;
}; };
@@ -2177,9 +2195,9 @@ impl<W: LayoutElement> Layout<W> {
if move_.tile.is_in_input_region(pos_within_tile) { if move_.tile.is_in_input_region(pos_within_tile) {
let win_pos = tile_pos + move_.tile.buf_loc(); let win_pos = tile_pos + move_.tile.buf_loc();
return Some((move_.tile.window(), Some(win_pos))); return Some((move_.tile.window(), HitType::Input { win_pos }));
} else if move_.tile.is_in_activation_region(pos_within_tile) { } else if move_.tile.is_in_activation_region(pos_within_tile) {
return Some((move_.tile.window(), None)); return Some((move_.tile.window(), HitType::Activate));
} }
return None; return None;
+4 -7
View File
@@ -14,7 +14,7 @@ use super::tile::Tile;
use super::workspace::{ use super::workspace::{
OutputId, Workspace, WorkspaceAddWindowTarget, WorkspaceId, WorkspaceRenderElement, OutputId, Workspace, WorkspaceAddWindowTarget, WorkspaceId, WorkspaceRenderElement,
}; };
use super::{ActivateWindow, LayoutElement, Options}; use super::{ActivateWindow, HitType, LayoutElement, Options};
use crate::animation::{Animation, Clock}; use crate::animation::{Animation, Clock};
use crate::input::swipe_tracker::SwipeTracker; use crate::input::swipe_tracker::SwipeTracker;
use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::renderer::NiriRenderer;
@@ -1007,13 +1007,10 @@ impl<W: LayoutElement> Monitor<W> {
Some((ws, bounds.loc)) Some((ws, bounds.loc))
} }
pub fn window_under( pub fn window_under(&self, pos_within_output: Point<f64, Logical>) -> Option<(&W, HitType)> {
&self,
pos_within_output: Point<f64, Logical>,
) -> Option<(&W, Option<Point<f64, Logical>>)> {
let (ws, offset) = self.workspace_under(pos_within_output)?; let (ws, offset) = self.workspace_under(pos_within_output)?;
let (win, win_pos) = ws.window_under(pos_within_output - offset)?; let (win, hit) = ws.window_under(pos_within_output - offset)?;
Some((win, win_pos.map(|p| p + offset))) Some((win, hit.offset_win_pos(offset)))
} }
pub fn resize_edges_under(&self, pos_within_output: Point<f64, Logical>) -> Option<ResizeEdge> { pub fn resize_edges_under(&self, pos_within_output: Point<f64, Logical>) -> Option<ResizeEdge> {
+6 -7
View File
@@ -19,7 +19,9 @@ use super::scrolling::{
ScrollingSpaceRenderElement, ScrollingSpaceRenderElement,
}; };
use super::tile::{Tile, TileRenderSnapshot}; use super::tile::{Tile, TileRenderSnapshot};
use super::{ActivateWindow, InteractiveResizeData, LayoutElement, Options, RemovedTile, SizeFrac}; use super::{
ActivateWindow, HitType, InteractiveResizeData, LayoutElement, Options, RemovedTile, SizeFrac,
};
use crate::animation::Clock; use crate::animation::Clock;
use crate::niri_render_elements; use crate::niri_render_elements;
use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::renderer::NiriRenderer;
@@ -1459,10 +1461,7 @@ impl<W: LayoutElement> Workspace<W> {
.start_close_animation_for_tile(renderer, snapshot, tile_size, tile_pos, blocker); .start_close_animation_for_tile(renderer, snapshot, tile_size, tile_pos, blocker);
} }
pub fn window_under( pub fn window_under(&self, pos: Point<f64, Logical>) -> Option<(&W, HitType)> {
&self,
pos: Point<f64, Logical>,
) -> Option<(&W, Option<Point<f64, Logical>>)> {
self.tiles_with_render_positions() self.tiles_with_render_positions()
.find_map(|(tile, tile_pos, visible)| { .find_map(|(tile, tile_pos, visible)| {
if !visible { if !visible {
@@ -1473,9 +1472,9 @@ impl<W: LayoutElement> Workspace<W> {
if tile.is_in_input_region(pos_within_tile) { if tile.is_in_input_region(pos_within_tile) {
let win_pos = tile_pos + tile.buf_loc(); let win_pos = tile_pos + tile.buf_loc();
return Some((tile.window(), Some(win_pos))); return Some((tile.window(), HitType::Input { win_pos }));
} else if tile.is_in_activation_region(pos_within_tile) { } else if tile.is_in_activation_region(pos_within_tile) {
return Some((tile.window(), None)); return Some((tile.window(), HitType::Activate));
} }
None None
+17 -9
View File
@@ -128,7 +128,7 @@ use crate::layer::mapped::LayerSurfaceRenderElement;
use crate::layer::MappedLayer; use crate::layer::MappedLayer;
use crate::layout::tile::TileRenderElement; use crate::layout::tile::TileRenderElement;
use crate::layout::workspace::WorkspaceId; use crate::layout::workspace::WorkspaceId;
use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement}; use crate::layout::{HitType, Layout, LayoutElement as _, MonitorRenderElement};
use crate::niri_render_elements; use crate::niri_render_elements;
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState}; use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
use crate::protocols::gamma_control::GammaControlManagerState; use crate::protocols::gamma_control::GammaControlManagerState;
@@ -450,6 +450,9 @@ pub struct PointContents {
// Output under point. // Output under point.
pub output: Option<Output>, pub output: Option<Output>,
// Surface under point and its location in the global coordinate space. // Surface under point and its location in the global coordinate space.
//
// Can be `None` even when `window` is set, for example when the pointer is over the niri
// border around the window.
pub surface: Option<(WlSurface, Point<f64, Logical>)>, pub surface: Option<(WlSurface, Point<f64, Logical>)>,
// If surface belongs to a window, this is that window. // If surface belongs to a window, this is that window.
pub window: Option<Window>, pub window: Option<Window>,
@@ -2693,7 +2696,7 @@ impl Niri {
) )
}) })
}) })
.map(|(s, l)| (s, (None, Some(l.clone())))) .map(|(s, l)| (Some(s), (None, Some(l.clone()))))
}; };
let layer_toplevel_under = |layer| layer_surface_under(layer, false); let layer_toplevel_under = |layer| layer_surface_under(layer, false);
@@ -2702,9 +2705,10 @@ impl Niri {
let window_under = || { let window_under = || {
self.layout self.layout
.window_under(output, pos_within_output) .window_under(output, pos_within_output)
.and_then(|(mapped, win_pos_within_output)| { .map(|(mapped, hit)| {
let win_pos_within_output = win_pos_within_output?;
let window = &mapped.window; let window = &mapped.window;
let surface_and_pos = if let HitType::Input { win_pos } = hit {
let win_pos_within_output = win_pos;
window window
.surface_under( .surface_under(
pos_within_output - win_pos_within_output, pos_within_output - win_pos_within_output,
@@ -2713,7 +2717,10 @@ impl Niri {
.map(|(s, pos_within_window)| { .map(|(s, pos_within_window)| {
(s, pos_within_window.to_f64() + win_pos_within_output) (s, pos_within_window.to_f64() + win_pos_within_output)
}) })
.map(|s| (s, (Some(window.clone()), None))) } else {
None
};
(surface_and_pos, (Some(window.clone()), None))
}) })
}; };
@@ -2744,14 +2751,15 @@ impl Niri {
.or_else(|| layer_toplevel_under(Layer::Background)); .or_else(|| layer_toplevel_under(Layer::Background));
} }
let Some(((surface, surface_pos_within_output), (window, layer))) = under else { let Some((mut surface_and_pos, (window, layer))) = under else {
return rv; return rv;
}; };
let surface_loc_in_global_space = if let Some((_, surface_pos)) = &mut surface_and_pos {
surface_pos_within_output + output_pos_in_global_space.to_f64(); *surface_pos += output_pos_in_global_space.to_f64();
}
rv.surface = Some((surface, surface_loc_in_global_space)); rv.surface = surface_and_pos;
rv.window = window; rv.window = window;
rv.layer = layer; rv.layer = layer;
rv rv