aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--niri-config/src/lib.rs5
-rw-r--r--resources/default-config.kdl3
-rw-r--r--src/input.rs94
-rw-r--r--src/layout/monitor.rs8
-rw-r--r--src/layout/workspace.rs38
-rw-r--r--src/niri.rs80
-rw-r--r--src/utils/mod.rs4
7 files changed, 214 insertions, 18 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index cad39c66..2df97cef 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -69,6 +69,8 @@ pub struct Input {
pub touch: Touch,
#[knuffel(child)]
pub disable_power_key_handling: bool,
+ #[knuffel(child)]
+ pub warp_mouse_to_focus: bool,
}
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
@@ -1592,6 +1594,8 @@ mod tests {
}
disable-power-key-handling
+
+ warp-mouse-to-focus
}
output "eDP-1" {
@@ -1731,6 +1735,7 @@ mod tests {
map_to_output: Some("eDP-1".to_owned()),
},
disable_power_key_handling: true,
+ warp_mouse_to_focus: true,
},
outputs: vec![Output {
off: false,
diff --git a/resources/default-config.kdl b/resources/default-config.kdl
index 0567573b..7e4a4bd0 100644
--- a/resources/default-config.kdl
+++ b/resources/default-config.kdl
@@ -67,6 +67,9 @@ input {
// Uncomment this if you would like to configure the power button elsewhere
// (i.e. logind.conf).
// disable-power-key-handling
+
+ // Uncomment this to make the mouse warp to the center of newly focused windows.
+ // warp-mouse-to-focus
}
// You can configure outputs by their name, which you can find
diff --git a/src/input.rs b/src/input.rs
index 7f3e6fa7..9536731a 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -411,139 +411,166 @@ impl State {
}
Action::MoveColumnLeft => {
self.niri.layout.move_left();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveColumnRight => {
self.niri.layout.move_right();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveColumnToFirst => {
self.niri.layout.move_column_to_first();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveColumnToLast => {
self.niri.layout.move_column_to_last();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowDown => {
self.niri.layout.move_down();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowUp => {
self.niri.layout.move_up();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowDownOrToWorkspaceDown => {
self.niri.layout.move_down_or_to_workspace_down();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowUpOrToWorkspaceUp => {
self.niri.layout.move_up_or_to_workspace_up();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ConsumeOrExpelWindowLeft => {
self.niri.layout.consume_or_expel_window_left();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ConsumeOrExpelWindowRight => {
self.niri.layout.consume_or_expel_window_right();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusColumnLeft => {
self.niri.layout.focus_left();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusColumnRight => {
self.niri.layout.focus_right();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusColumnFirst => {
self.niri.layout.focus_column_first();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusColumnLast => {
self.niri.layout.focus_column_last();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWindowDown => {
self.niri.layout.focus_down();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWindowUp => {
self.niri.layout.focus_up();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWindowOrWorkspaceDown => {
self.niri.layout.focus_window_or_workspace_down();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWindowOrWorkspaceUp => {
self.niri.layout.focus_window_or_workspace_up();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowToWorkspaceDown => {
self.niri.layout.move_to_workspace_down();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowToWorkspaceUp => {
self.niri.layout.move_to_workspace_up();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowToWorkspace(idx) => {
let idx = idx.saturating_sub(1) as usize;
self.niri.layout.move_to_workspace(idx);
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveColumnToWorkspaceDown => {
self.niri.layout.move_column_to_workspace_down();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveColumnToWorkspaceUp => {
self.niri.layout.move_column_to_workspace_up();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveColumnToWorkspace(idx) => {
let idx = idx.saturating_sub(1) as usize;
self.niri.layout.move_column_to_workspace(idx);
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWorkspaceDown => {
self.niri.layout.switch_workspace_down();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWorkspaceUp => {
self.niri.layout.switch_workspace_up();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWorkspace(idx) => {
let idx = idx.saturating_sub(1) as usize;
self.niri.layout.switch_workspace(idx);
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
@@ -559,11 +586,14 @@ impl State {
}
Action::ConsumeWindowIntoColumn => {
self.niri.layout.consume_into_column();
+ // This does not cause immediate focus or window size change, so warping mouse to
+ // focus won't do anything here.
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ExpelWindowFromColumn => {
self.niri.layout.expel_from_column();
+ self.maybe_warp_cursor_to_focus();
// FIXME: granular
self.niri.queue_redraw_all();
}
@@ -581,81 +611,105 @@ impl State {
Action::FocusMonitorLeft => {
if let Some(output) = self.niri.output_left() {
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::FocusMonitorRight => {
if let Some(output) = self.niri.output_right() {
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::FocusMonitorDown => {
if let Some(output) = self.niri.output_down() {
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::FocusMonitorUp => {
if let Some(output) = self.niri.output_up() {
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveWindowToMonitorLeft => {
if let Some(output) = self.niri.output_left() {
self.niri.layout.move_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveWindowToMonitorRight => {
if let Some(output) = self.niri.output_right() {
self.niri.layout.move_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveWindowToMonitorDown => {
if let Some(output) = self.niri.output_down() {
self.niri.layout.move_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveWindowToMonitorUp => {
if let Some(output) = self.niri.output_up() {
self.niri.layout.move_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveColumnToMonitorLeft => {
if let Some(output) = self.niri.output_left() {
self.niri.layout.move_column_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveColumnToMonitorRight => {
if let Some(output) = self.niri.output_right() {
self.niri.layout.move_column_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveColumnToMonitorDown => {
if let Some(output) = self.niri.output_down() {
self.niri.layout.move_column_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveColumnToMonitorUp => {
if let Some(output) = self.niri.output_up() {
self.niri.layout.move_column_to_output(&output);
self.niri.layout.focus_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::SetColumnWidth(change) => {
@@ -672,25 +726,33 @@ impl State {
Action::MoveWorkspaceToMonitorLeft => {
if let Some(output) = self.niri.output_left() {
self.niri.layout.move_workspace_to_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveWorkspaceToMonitorRight => {
if let Some(output) = self.niri.output_right() {
self.niri.layout.move_workspace_to_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveWorkspaceToMonitorDown => {
if let Some(output) = self.niri.output_down() {
self.niri.layout.move_workspace_to_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
Action::MoveWorkspaceToMonitorUp => {
if let Some(output) = self.niri.output_up() {
self.niri.layout.move_workspace_to_output(&output);
- self.move_cursor_to_output(&output);
+ if !self.maybe_warp_cursor_to_focus_centered() {
+ self.move_cursor_to_output(&output);
+ }
}
}
}
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index f817ef1a..e9fb2271 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -583,6 +583,14 @@ impl<W: LayoutElement> Monitor<W> {
self.clean_up_workspaces();
}
+ /// Returns the geometry of the active tile relative to and clamped to the output.
+ ///
+ /// During animations, assumes the final view position.
+ pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<i32, Logical>> {
+ // FIXME: switch gesture.
+ self.active_workspace_ref().active_tile_visual_rectangle()
+ }
+
pub fn window_under(
&self,
pos_within_output: Point<f64, Logical>,
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 41c9e140..2c18b40e 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -181,6 +181,15 @@ impl OutputId {
}
}
+impl ViewOffsetAdjustment {
+ pub fn target_view_offset(&self) -> f64 {
+ match self {
+ ViewOffsetAdjustment::Animation(anim) => anim.to(),
+ ViewOffsetAdjustment::Gesture(gesture) => gesture.current_view_offset,
+ }
+ }
+}
+
impl ColumnWidth {
fn resolve(self, options: &Options, view_width: i32) -> i32 {
match self {
@@ -1124,6 +1133,31 @@ impl<W: LayoutElement> Workspace<W> {
first.chain(rest)
}
+ fn active_column_ref(&self) -> Option<&Column<W>> {
+ if self.columns.is_empty() {
+ return None;
+ }
+ Some(&self.columns[self.active_column_idx])
+ }
+
+ /// Returns the geometry of the active tile relative to and clamped to the view.
+ ///
+ /// During animations, assumes the final view position.
+ pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<i32, Logical>> {
+ let col = self.active_column_ref()?;
+ let view_pos = self
+ .view_offset_adj
+ .as_ref()
+ .map_or(self.view_offset, |adj| adj.target_view_offset() as i32);
+
+ let tile_pos = Point::from((-view_pos, col.tile_y(col.active_tile_idx)));
+ let tile_size = col.active_tile_ref().tile_size();
+ let tile_rect = Rectangle::from_loc_and_size(tile_pos, tile_size);
+
+ let view = Rectangle::from_loc_and_size((0, 0), self.view_size);
+ view.intersection(tile_rect)
+ }
+
pub fn window_under(
&self,
pos: Point<f64, Logical>,
@@ -2010,6 +2044,10 @@ impl<W: LayoutElement> Column<W> {
pos
})
}
+
+ fn active_tile_ref(&self) -> &Tile<W> {
+ &self.tiles[self.active_tile_idx]
+ }
}
fn compute_new_view_offset(
diff --git a/src/niri.rs b/src/niri.rs
index 37c1e40c..fe14dca3 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -112,7 +112,7 @@ use crate::ui::hotkey_overlay::HotkeyOverlay;
use crate::ui::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
use crate::utils::spawning::CHILD_ENV;
use crate::utils::{
- center, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
+ center, center_f64, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
};
use crate::window::Unmapped;
use crate::{animation, niri_render_elements};
@@ -197,7 +197,6 @@ pub struct Niri {
// popup grabs are active (which means the real keyboard focus is on a popup descending from
// this toplevel surface).
pub keyboard_focus: Option<WlSurface>,
-
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
pub is_fdo_idle_inhibited: Arc<AtomicBool>,
@@ -314,6 +313,11 @@ struct SurfaceFrameThrottlingState {
last_sent_at: RefCell<Option<(Output, u32)>>,
}
+pub enum CenterCoords {
+ Seperately,
+ Both,
+}
+
#[derive(Default)]
pub struct WindowOffscreenId(pub RefCell<Option<Id>>);
@@ -401,6 +405,78 @@ impl State {
self.niri.queue_redraw_all();
}
+ /// Moves cursor within the specified rectangle, only adjusting coordinates if needed.
+ fn move_cursor_to_rect(&mut self, rect: Rectangle<f64, Logical>, mode: CenterCoords) -> bool {
+ let pointer = &self.niri.seat.get_pointer().unwrap();
+ let cur_loc = pointer.current_location();
+ let x_in_bound = cur_loc.x >= rect.loc.x && cur_loc.x <= rect.loc.x + rect.size.w;
+ let y_in_bound = cur_loc.y >= rect.loc.y && cur_loc.y <= rect.loc.y + rect.size.h;
+
+ let p = match mode {
+ CenterCoords::Seperately => {
+ if x_in_bound && y_in_bound {
+ return false;
+ } else if y_in_bound {
+ // adjust x
+ Point::from((rect.loc.x + rect.size.w / 2.0, cur_loc.y))
+ } else if x_in_bound {
+ // adjust y
+ Point::from((cur_loc.x, rect.loc.y + rect.size.h / 2.0))
+ } else {
+ // adjust x and y
+ center_f64(rect)
+ }
+ }
+ CenterCoords::Both => {
+ if x_in_bound && y_in_bound {
+ return false;
+ } else {
+ // adjust x and y
+ center_f64(rect)
+ }
+ }
+ };
+
+ self.move_cursor(p);
+ true
+ }
+
+ pub fn move_cursor_to_focused_tile(&mut self, mode: CenterCoords) -> bool {
+ let Some(output) = self.niri.layout.active_output() else {
+ return false;
+ };
+ let output = output.clone();
+ let monitor = self.niri.layout.monitor_for_output(&output).unwrap();
+
+ let mut rv = false;
+ let rect = monitor.active_tile_visual_rectangle();
+
+ if let Some(rect) = rect {
+ let output_geo = self.niri.global_space.output_geometry(&output).unwrap();
+ let mut rect = rect;
+ rect.loc += output_geo.loc;
+ rv = self.move_cursor_to_rect(rect.to_f64(), mode);
+ }
+
+ rv
+ }
+
+ pub fn maybe_warp_cursor_to_focus(&mut self) -> bool {
+ if !self.niri.config.borrow().input.warp_mouse_to_focus {
+ return false;
+ }
+
+ self.move_cursor_to_focused_tile(CenterCoords::Seperately)
+ }
+
+ pub fn maybe_warp_cursor_to_focus_centered(&mut self) -> bool {
+ if !self.niri.config.borrow().input.warp_mouse_to_focus {
+ return false;
+ }
+
+ self.move_cursor_to_focused_tile(CenterCoords::Both)
+ }
+
pub fn refresh_pointer_focus(&mut self) {
let _span = tracy_client::span!("Niri::refresh_pointer_focus");
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index df21ffdb..e9e56bcf 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -40,6 +40,10 @@ pub fn center(rect: Rectangle<i32, Logical>) -> Point<i32, Logical> {
rect.loc + rect.size.downscale(2).to_point()
}
+pub fn center_f64(rect: Rectangle<f64, Logical>) -> Point<f64, Logical> {
+ rect.loc + rect.size.downscale(2.0).to_point()
+}
+
pub fn output_size(output: &Output) -> Size<i32, Logical> {
let output_scale = output.current_scale().integer_scale();
let output_transform = output.current_transform();