Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions niri-config/src/gestures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,10 @@ pub struct HotCorners {
pub bottom_left: bool,
#[knuffel(child)]
pub bottom_right: bool,
#[knuffel(child, unwrap(argument))]
pub open_delay_ms: Option<u16>,
#[knuffel(child, unwrap(argument))]
pub open_region_width: Option<u16>,
#[knuffel(child, unwrap(argument))]
pub open_region_height: Option<u16>,
}
109 changes: 84 additions & 25 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::time::Duration;
use calloop::timer::{TimeoutAction, Timer};
use input::event::gesture::GestureEventCoordinates as _;
use niri_config::{
Action, Bind, Binds, Config, Key, ModKey, Modifiers, MruDirection, SwitchBinds, Trigger,
Action, Bind, Binds, Config, Key, ModKey, Modifiers, MruDirection, OutputName, SwitchBinds,
Trigger,
};
use niri_ipc::LayoutSwitchTarget;
use smithay::backend::input::{
Expand Down Expand Up @@ -2378,12 +2379,15 @@ impl State {
}

fn on_pointer_motion<I: InputBackend>(&mut self, event: I::PointerMotionEvent) {
let was_inside_hot_corner = self.niri.pointer_inside_hot_corner;
// Any of the early returns here mean that the pointer is not inside the hot corner.
self.niri.pointer_inside_hot_corner = false;
fn cancel_hot_corner_timer(state: &mut State) {
if let Some(token) = state.niri.pointer_inside_hot_corner_timer.take() {
state.niri.event_loop.remove(token);
}
}

// We need an output to be able to move the pointer.
if self.niri.global_space.outputs().next().is_none() {
cancel_hot_corner_timer(self);
return;
}

Expand Down Expand Up @@ -2450,6 +2454,7 @@ impl State {

// I guess a redraw to hide the tablet cursor could be nice? Doesn't matter too
// much here I think.
cancel_hot_corner_timer(self);
return;
}
}
Expand Down Expand Up @@ -2536,6 +2541,7 @@ impl State {

pointer.frame(self);

cancel_hot_corner_timer(self);
return;
}
}
Expand Down Expand Up @@ -2568,15 +2574,40 @@ impl State {

// contents_under() will return no surface when the hot corner should trigger, so
// pointer.motion() will set the current focus to None.
if under.hot_corner && pointer.current_focus().is_none() {
if !was_inside_hot_corner
&& pointer
.with_grab(|_, grab| grab_allows_hot_corner(grab))
.unwrap_or(true)
{
self.niri.layout.toggle_overview();
}
self.niri.pointer_inside_hot_corner = true;
if under.hot_corner
&& pointer.current_focus().is_none()
&& self.niri.pointer_inside_hot_corner_timer.is_none()
&& pointer
.with_grab(|_, grab| grab_allows_hot_corner(grab))
.unwrap_or(true)
{
let config = self.niri.config.borrow();
let hot_corners = under
.output
.unwrap()
.user_data()
.get::<OutputName>()
.and_then(|name| config.outputs.find(name))
.and_then(|c| c.hot_corners)
.unwrap_or(config.gestures.hot_corners);
let delay = hot_corners.open_delay_ms;

let timer = Timer::from_duration(Duration::from_millis(u64::from(delay.unwrap_or(0))));

let token = self
.niri
.event_loop
.insert_source(timer, |_, _, state| {
state.niri.layout.toggle_overview();
TimeoutAction::Drop
})
.unwrap();

self.niri.pointer_inside_hot_corner_timer = Some(token);
}

if !under.hot_corner {
cancel_hot_corner_timer(self);
}

// Activate a new confinement if necessary.
Expand All @@ -2603,15 +2634,18 @@ impl State {
&mut self,
event: I::PointerMotionAbsoluteEvent,
) {
let was_inside_hot_corner = self.niri.pointer_inside_hot_corner;
// Any of the early returns here mean that the pointer is not inside the hot corner.
self.niri.pointer_inside_hot_corner = false;
fn cancel_hot_corner_timer(state: &mut State) {
if let Some(token) = state.niri.pointer_inside_hot_corner_timer.take() {
state.niri.event_loop.remove(token);
}
}

let Some(pos) = self.compute_absolute_location(&event, None).or_else(|| {
self.global_bounding_rectangle().map(|output_geo| {
event.position_transformed(output_geo.size) + output_geo.loc.to_f64()
})
}) else {
cancel_hot_corner_timer(self);
return;
};

Expand Down Expand Up @@ -2662,15 +2696,40 @@ impl State {

// contents_under() will return no surface when the hot corner should trigger, so
// pointer.motion() will set the current focus to None.
if under.hot_corner && pointer.current_focus().is_none() {
if !was_inside_hot_corner
&& pointer
.with_grab(|_, grab| grab_allows_hot_corner(grab))
.unwrap_or(true)
{
self.niri.layout.toggle_overview();
}
self.niri.pointer_inside_hot_corner = true;
if under.hot_corner
&& pointer.current_focus().is_none()
&& self.niri.pointer_inside_hot_corner_timer.is_none()
&& pointer
.with_grab(|_, grab| grab_allows_hot_corner(grab))
.unwrap_or(true)
{
let config = self.niri.config.borrow();
let hot_corners = under
.output
.unwrap()
.user_data()
.get::<OutputName>()
.and_then(|name| config.outputs.find(name))
.and_then(|c| c.hot_corners)
.unwrap_or(config.gestures.hot_corners);
let delay = hot_corners.open_delay_ms;

let timer = Timer::from_duration(Duration::from_millis(u64::from(delay.unwrap_or(0))));

let token = self
.niri
.event_loop
.insert_source(timer, |_, _, state| {
state.niri.layout.toggle_overview();
TimeoutAction::Drop
})
.unwrap();

self.niri.pointer_inside_hot_corner_timer = Some(token);
}

if !under.hot_corner {
cancel_hot_corner_timer(self);
}

self.niri.maybe_activate_pointer_constraint();
Expand Down
17 changes: 11 additions & 6 deletions src/niri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ pub struct Niri {
/// Used for limiting the notify to once per iteration, so that it's not spammed with high
/// resolution mice.
pub notified_activity_this_iteration: bool,
pub pointer_inside_hot_corner: bool,
pub pointer_inside_hot_corner_timer: Option<RegistrationToken>,
pub tablet_cursor_location: Option<Point<f64, Logical>>,
pub gesture_swipe_3f_cumulative: Option<(f64, f64)>,
pub overview_scroll_swipe_gesture: ScrollSwipeGesture,
Expand Down Expand Up @@ -2784,7 +2784,7 @@ impl Niri {
pointer_inactivity_timer: None,
pointer_inactivity_timer_got_reset: false,
notified_activity_this_iteration: false,
pointer_inside_hot_corner: false,
pointer_inside_hot_corner_timer: None,
tablet_cursor_location: None,
gesture_swipe_3f_cumulative: None,
overview_scroll_swipe_gesture: ScrollSwipeGesture::new(),
Expand Down Expand Up @@ -3273,17 +3273,22 @@ impl Niri {
let geom = self.global_space.output_geometry(output).unwrap();
let size = geom.size.to_f64();

let region_size = Size::new(
f64::from(hot_corners.open_region_width.unwrap_or(1)),
f64::from(hot_corners.open_region_height.unwrap_or(1)),
);

let contains = move |corner: Point<f64, Logical>| {
Rectangle::new(corner, Size::new(1., 1.)).contains(pos)
Rectangle::new(corner, region_size).contains(pos)
};

if hot_corners.top_right && contains(Point::new(size.w - 1., 0.)) {
if hot_corners.top_right && contains(Point::new(size.w - region_size.w, 0.)) {
return true;
}
if hot_corners.bottom_left && contains(Point::new(0., size.h - 1.)) {
if hot_corners.bottom_left && contains(Point::new(0., size.h - region_size.h)) {
return true;
}
if hot_corners.bottom_right && contains(Point::new(size.w - 1., size.h - 1.)) {
if hot_corners.bottom_right && contains(Point::new(size.w - region_size.w, size.h - region_size.h)) {
return true;
}

Expand Down