mirror of
https://github.com/YaLTeR/niri.git
synced 2024-08-18 00:30:24 +03:00
Implement focus-follows-mouse max-scroll-amount
This commit is contained in:
parent
120eaa6c56
commit
1da99f4003
@ -75,7 +75,7 @@ pub struct Input {
|
|||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub warp_mouse_to_focus: bool,
|
pub warp_mouse_to_focus: bool,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub focus_follows_mouse: bool,
|
pub focus_follows_mouse: Option<FocusFollowsMouse>,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub workspace_auto_back_and_forth: bool,
|
pub workspace_auto_back_and_forth: bool,
|
||||||
}
|
}
|
||||||
@ -289,6 +289,15 @@ pub struct Touch {
|
|||||||
pub map_to_output: Option<String>,
|
pub map_to_output: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct FocusFollowsMouse {
|
||||||
|
#[knuffel(property, str)]
|
||||||
|
pub max_scroll_amount: Option<Percent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Percent(pub f64);
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct Outputs(pub Vec<Output>);
|
pub struct Outputs(pub Vec<Output>);
|
||||||
|
|
||||||
@ -1794,6 +1803,16 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Animation {
|
impl Animation {
|
||||||
|
pub fn new_off() -> Self {
|
||||||
|
Self {
|
||||||
|
off: true,
|
||||||
|
kind: AnimationKind::Easing(EasingParams {
|
||||||
|
duration_ms: 0,
|
||||||
|
curve: AnimationCurve::Linear,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn decode_node<S: knuffel::traits::ErrorSpan>(
|
fn decode_node<S: knuffel::traits::ErrorSpan>(
|
||||||
node: &knuffel::ast::SpannedNode<S>,
|
node: &knuffel::ast::SpannedNode<S>,
|
||||||
ctx: &mut knuffel::decode::Context<S>,
|
ctx: &mut knuffel::decode::Context<S>,
|
||||||
@ -2418,6 +2437,23 @@ impl FromStr for TapButtonMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for Percent {
|
||||||
|
type Err = miette::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let Some((value, empty)) = s.split_once('%') else {
|
||||||
|
return Err(miette!("value must end with '%'"));
|
||||||
|
};
|
||||||
|
|
||||||
|
if !empty.is_empty() {
|
||||||
|
return Err(miette!("trailing characters after '%' are not allowed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let value: f64 = value.parse().map_err(|_| miette!("error parsing value"))?;
|
||||||
|
Ok(Percent(value / 100.))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_miette_hook() -> Result<(), miette::InstallError> {
|
pub fn set_miette_hook() -> Result<(), miette::InstallError> {
|
||||||
miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new())))
|
miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new())))
|
||||||
}
|
}
|
||||||
@ -2664,7 +2700,9 @@ mod tests {
|
|||||||
},
|
},
|
||||||
disable_power_key_handling: true,
|
disable_power_key_handling: true,
|
||||||
warp_mouse_to_focus: true,
|
warp_mouse_to_focus: true,
|
||||||
focus_follows_mouse: true,
|
focus_follows_mouse: Some(FocusFollowsMouse {
|
||||||
|
max_scroll_amount: None,
|
||||||
|
}),
|
||||||
workspace_auto_back_and_forth: true,
|
workspace_auto_back_and_forth: true,
|
||||||
},
|
},
|
||||||
outputs: Outputs(vec![Output {
|
outputs: Outputs(vec![Output {
|
||||||
|
@ -44,7 +44,8 @@ input {
|
|||||||
// warp-mouse-to-focus
|
// warp-mouse-to-focus
|
||||||
|
|
||||||
// Focus windows and outputs automatically when moving the mouse into them.
|
// Focus windows and outputs automatically when moving the mouse into them.
|
||||||
// focus-follows-mouse
|
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
|
||||||
|
// focus-follows-mouse max-scroll-amount="0%"
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can configure outputs by their name, which you can find
|
// You can configure outputs by their name, which you can find
|
||||||
|
@ -947,6 +947,22 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scroll_amount_to_activate(&self, window: &W::Id) -> f64 {
|
||||||
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
||||||
|
return 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
for mon in monitors {
|
||||||
|
for ws in &mon.workspaces {
|
||||||
|
if ws.has_window(window) {
|
||||||
|
return ws.scroll_amount_to_activate(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
|
||||||
pub fn activate_window(&mut self, window: &W::Id) {
|
pub fn activate_window(&mut self, window: &W::Id) {
|
||||||
let MonitorSet::Normal {
|
let MonitorSet::Normal {
|
||||||
monitors,
|
monitors,
|
||||||
|
@ -1391,6 +1391,37 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scroll_amount_to_activate(&self, window: &W::Id) -> f64 {
|
||||||
|
let column_idx = self
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.position(|col| col.contains(window))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if self.active_column_idx == column_idx {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_x = self.view_pos();
|
||||||
|
let new_view_offset = self.compute_new_view_offset_for_column(
|
||||||
|
current_x,
|
||||||
|
column_idx,
|
||||||
|
Some(self.active_column_idx),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Consider the end of an ongoing animation because that's what compute to fit does too.
|
||||||
|
let final_x = if let Some(ViewOffsetAdjustment::Animation(anim)) = &self.view_offset_adj {
|
||||||
|
current_x - self.view_offset + anim.to()
|
||||||
|
} else {
|
||||||
|
current_x
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_col_x = self.column_x(column_idx);
|
||||||
|
let from_view_offset = final_x - new_col_x;
|
||||||
|
|
||||||
|
(from_view_offset - new_view_offset).abs() / self.working_area.size.w
|
||||||
|
}
|
||||||
|
|
||||||
pub fn activate_window(&mut self, window: &W::Id) {
|
pub fn activate_window(&mut self, window: &W::Id) {
|
||||||
let column_idx = self
|
let column_idx = self
|
||||||
.columns
|
.columns
|
||||||
|
10
src/niri.rs
10
src/niri.rs
@ -4140,9 +4140,9 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_focus_follows_mouse(&mut self, new_focus: &PointerFocus) {
|
pub fn handle_focus_follows_mouse(&mut self, new_focus: &PointerFocus) {
|
||||||
if !self.config.borrow().input.focus_follows_mouse {
|
let Some(ffm) = self.config.borrow().input.focus_follows_mouse else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
let pointer = &self.seat.get_pointer().unwrap();
|
let pointer = &self.seat.get_pointer().unwrap();
|
||||||
if pointer.is_grabbed() {
|
if pointer.is_grabbed() {
|
||||||
@ -4160,6 +4160,12 @@ impl Niri {
|
|||||||
|
|
||||||
if let Some(window) = &new_focus.window {
|
if let Some(window) = &new_focus.window {
|
||||||
if current_focus.window.as_ref() != Some(window) {
|
if current_focus.window.as_ref() != Some(window) {
|
||||||
|
if let Some(threshold) = ffm.max_scroll_amount {
|
||||||
|
if self.layout.scroll_amount_to_activate(window) > threshold.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.layout.activate_window(window);
|
self.layout.activate_window(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ input {
|
|||||||
|
|
||||||
// disable-power-key-handling
|
// disable-power-key-handling
|
||||||
// warp-mouse-to-focus
|
// warp-mouse-to-focus
|
||||||
// focus-follows-mouse
|
// focus-follows-mouse max-scroll-amount="0%"
|
||||||
// workspace-auto-back-and-forth
|
// workspace-auto-back-and-forth
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -207,6 +207,24 @@ input {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<sup>Since: 0.1.8</sup> You can optionally set `max-scroll-amount`.
|
||||||
|
Then, focus-follows-mouse won't focus a window if it will result in the view scrolling more than the set amount.
|
||||||
|
The value is a percentage of the working area width.
|
||||||
|
|
||||||
|
```
|
||||||
|
input {
|
||||||
|
// Allow focus-follows-mouse when it results in scrolling at most 10% of the screen.
|
||||||
|
focus-follows-mouse max-scroll-amount="10%"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
input {
|
||||||
|
// Allow focus-follows-mouse only when it will not scroll the view.
|
||||||
|
focus-follows-mouse max-scroll-amount="0%"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### `workspace-auto-back-and-forth`
|
#### `workspace-auto-back-and-forth`
|
||||||
|
|
||||||
Normally, switching to the same workspace by index twice will do nothing (since you're already on that workspace).
|
Normally, switching to the same workspace by index twice will do nothing (since you're already on that workspace).
|
||||||
|
Loading…
Reference in New Issue
Block a user