aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--src/input.rs77
-rw-r--r--src/layout.rs42
-rw-r--r--src/niri.rs94
-rw-r--r--src/utils.rs6
5 files changed, 213 insertions, 8 deletions
diff --git a/README.md b/README.md
index 2ddca347..6362f846 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,8 @@ The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kb
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>→</kbd> | Move the focused column to the right |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>J</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>↓</kbd> | Move the focused window below in a column |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>K</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>↑</kbd> | Move the focused window above in a column |
+| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>↑</kbd><kbd>→</kbd> | Focus the monitor to the side |
+| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>↑</kbd><kbd>→</kbd> | Move the focused window to the monitor to the side |
| <kbd>Mod</kbd><kbd>U</kbd> | Switch to the workspace below |
| <kbd>Mod</kbd><kbd>I</kbd> | Switch to the workspace above |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>U</kbd> | Move the focused window to the workspace below |
diff --git a/src/input.rs b/src/input.rs
index 35482ed9..e5dba51d 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -34,6 +34,14 @@ enum Action {
SwitchWorkspaceUp,
MoveToWorkspaceDown,
MoveToWorkspaceUp,
+ FocusMonitorLeft,
+ FocusMonitorRight,
+ FocusMonitorDown,
+ FocusMonitorUp,
+ MoveToMonitorLeft,
+ MoveToMonitorRight,
+ MoveToMonitorDown,
+ MoveToMonitorUp,
ToggleWidth,
ToggleFullWidth,
}
@@ -80,22 +88,35 @@ fn action(comp_mod: CompositorMod, keysym: KeysymHandle, mods: ModifiersState) -
KEY_n => Action::Spawn("nautilus".to_owned()),
KEY_q => Action::CloseWindow,
KEY_F => Action::ToggleFullscreen,
+ KEY_comma => Action::ConsumeIntoColumn,
+ KEY_period => Action::ExpelFromColumn,
+ KEY_r => Action::ToggleWidth,
+ KEY_f => Action::ToggleFullWidth,
+ // Move to monitor.
+ KEY_H | KEY_Left if mods.shift && mods.ctrl => Action::MoveToMonitorLeft,
+ KEY_L | KEY_Right if mods.shift && mods.ctrl => Action::MoveToMonitorRight,
+ KEY_J | KEY_Down if mods.shift && mods.ctrl => Action::MoveToMonitorDown,
+ KEY_K | KEY_Up if mods.shift && mods.ctrl => Action::MoveToMonitorUp,
+ // Focus monitor.
+ KEY_H | KEY_Left if mods.shift => Action::FocusMonitorLeft,
+ KEY_L | KEY_Right if mods.shift => Action::FocusMonitorRight,
+ KEY_J | KEY_Down if mods.shift => Action::FocusMonitorDown,
+ KEY_K | KEY_Up if mods.shift => Action::FocusMonitorUp,
+ // Move.
KEY_h | KEY_Left if mods.ctrl => Action::MoveLeft,
KEY_l | KEY_Right if mods.ctrl => Action::MoveRight,
KEY_j | KEY_Down if mods.ctrl => Action::MoveDown,
KEY_k | KEY_Up if mods.ctrl => Action::MoveUp,
+ // Focus.
KEY_h | KEY_Left => Action::FocusLeft,
KEY_l | KEY_Right => Action::FocusRight,
KEY_j | KEY_Down => Action::FocusDown,
KEY_k | KEY_Up => Action::FocusUp,
+ // Workspaces.
KEY_u if mods.ctrl => Action::MoveToWorkspaceDown,
KEY_i if mods.ctrl => Action::MoveToWorkspaceUp,
KEY_u => Action::SwitchWorkspaceDown,
KEY_i => Action::SwitchWorkspaceUp,
- KEY_comma => Action::ConsumeIntoColumn,
- KEY_period => Action::ExpelFromColumn,
- KEY_r => Action::ToggleWidth,
- KEY_f => Action::ToggleFullWidth,
_ => Action::None,
}
}
@@ -229,6 +250,54 @@ impl Niri {
Action::ToggleFullWidth => {
self.monitor_set.toggle_full_width();
}
+ Action::FocusMonitorLeft => {
+ if let Some(output) = self.output_left() {
+ self.monitor_set.focus_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
+ Action::FocusMonitorRight => {
+ if let Some(output) = self.output_right() {
+ self.monitor_set.focus_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
+ Action::FocusMonitorDown => {
+ if let Some(output) = self.output_down() {
+ self.monitor_set.focus_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
+ Action::FocusMonitorUp => {
+ if let Some(output) = self.output_up() {
+ self.monitor_set.focus_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
+ Action::MoveToMonitorLeft => {
+ if let Some(output) = self.output_left() {
+ self.monitor_set.move_to_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
+ Action::MoveToMonitorRight => {
+ if let Some(output) = self.output_right() {
+ self.monitor_set.move_to_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
+ Action::MoveToMonitorDown => {
+ if let Some(output) = self.output_down() {
+ self.monitor_set.move_to_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
+ Action::MoveToMonitorUp => {
+ if let Some(output) = self.output_up() {
+ self.monitor_set.move_to_output(&output);
+ self.move_cursor_to_output(&output);
+ }
+ }
}
}
}
diff --git a/src/layout.rs b/src/layout.rs
index 7dc89c3d..8dba3eed 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -776,6 +776,48 @@ impl<W: LayoutElement> MonitorSet<W> {
};
monitor.toggle_full_width();
}
+
+ pub fn focus_output(&mut self, output: &Output) {
+ if let MonitorSet::Normal {
+ monitors,
+ active_monitor_idx,
+ ..
+ } = self
+ {
+ for (idx, mon) in monitors.iter().enumerate() {
+ if &mon.output == output {
+ *active_monitor_idx = idx;
+ return;
+ }
+ }
+ }
+ }
+
+ pub fn move_to_output(&mut self, output: &Output) {
+ if let MonitorSet::Normal {
+ monitors,
+ active_monitor_idx,
+ ..
+ } = self
+ {
+ let new_idx = monitors
+ .iter()
+ .position(|mon| &mon.output == output)
+ .unwrap();
+
+ let current = &mut monitors[*active_monitor_idx];
+ let ws = current.active_workspace();
+ if !ws.has_windows() {
+ return;
+ }
+ let column = &ws.columns[ws.active_column_idx];
+ let window = column.windows[column.active_window_idx].clone();
+ ws.remove_window(&window);
+
+ let workspace_idx = monitors[new_idx].active_workspace_idx;
+ self.add_window(new_idx, workspace_idx, window, true);
+ }
+ }
}
impl MonitorSet<Window> {
diff --git a/src/niri.rs b/src/niri.rs
index afc1d305..32a5d319 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -15,7 +15,7 @@ use smithay::desktop::{
layer_map_for_output, LayerSurface, PopupManager, Space, Window, WindowSurfaceType,
};
use smithay::input::keyboard::XkbConfig;
-use smithay::input::pointer::{CursorImageAttributes, CursorImageStatus};
+use smithay::input::pointer::{CursorImageAttributes, CursorImageStatus, MotionEvent};
use smithay::input::{Seat, SeatState};
use smithay::output::Output;
use smithay::reexports::calloop::generic::Generic;
@@ -26,7 +26,7 @@ use smithay::reexports::wayland_server::backend::{
};
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::{Display, DisplayHandle};
-use smithay::utils::{IsAlive, Logical, Physical, Point, Scale, SERIAL_COUNTER};
+use smithay::utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, SERIAL_COUNTER};
use smithay::wayland::compositor::{with_states, CompositorClientState, CompositorState};
use smithay::wayland::data_device::DataDeviceState;
use smithay::wayland::output::OutputManagerState;
@@ -38,7 +38,7 @@ use smithay::wayland::socket::ListeningSocketSource;
use crate::backend::Backend;
use crate::frame_clock::FrameClock;
use crate::layout::{MonitorRenderElement, MonitorSet};
-use crate::utils::load_default_cursor;
+use crate::utils::{center, get_monotonic_time, load_default_cursor};
use crate::LoopData;
pub struct Niri {
@@ -268,6 +268,94 @@ impl Niri {
self.global_space.output_under(pos).next().cloned()
}
+ pub fn output_left(&self) -> Option<Output> {
+ let active = self.monitor_set.active_output()?;
+ let active_geo = self.global_space.output_geometry(active).unwrap();
+ let extended_geo = Rectangle::from_loc_and_size(
+ (i32::MIN / 2, active_geo.loc.y),
+ (i32::MAX, active_geo.size.h),
+ );
+
+ self.global_space
+ .outputs()
+ .map(|output| (output, self.global_space.output_geometry(output).unwrap()))
+ .filter(|(_, geo)| center(*geo).x < center(active_geo).x && geo.overlaps(extended_geo))
+ .min_by_key(|(_, geo)| center(active_geo).x - center(*geo).x)
+ .map(|(output, _)| output)
+ .cloned()
+ }
+
+ pub fn output_right(&self) -> Option<Output> {
+ let active = self.monitor_set.active_output()?;
+ let active_geo = self.global_space.output_geometry(active).unwrap();
+ let extended_geo = Rectangle::from_loc_and_size(
+ (i32::MIN / 2, active_geo.loc.y),
+ (i32::MAX, active_geo.size.h),
+ );
+
+ self.global_space
+ .outputs()
+ .map(|output| (output, self.global_space.output_geometry(output).unwrap()))
+ .filter(|(_, geo)| center(*geo).x > center(active_geo).x && geo.overlaps(extended_geo))
+ .min_by_key(|(_, geo)| center(*geo).x - center(active_geo).x)
+ .map(|(output, _)| output)
+ .cloned()
+ }
+
+ pub fn output_up(&self) -> Option<Output> {
+ let active = self.monitor_set.active_output()?;
+ let active_geo = self.global_space.output_geometry(active).unwrap();
+ let extended_geo = Rectangle::from_loc_and_size(
+ (active_geo.loc.x, i32::MIN / 2),
+ (active_geo.size.w, i32::MAX),
+ );
+
+ self.global_space
+ .outputs()
+ .map(|output| (output, self.global_space.output_geometry(output).unwrap()))
+ .filter(|(_, geo)| center(*geo).y < center(active_geo).y && geo.overlaps(extended_geo))
+ .min_by_key(|(_, geo)| center(active_geo).y - center(*geo).y)
+ .map(|(output, _)| output)
+ .cloned()
+ }
+
+ pub fn output_down(&self) -> Option<Output> {
+ let active = self.monitor_set.active_output()?;
+ let active_geo = self.global_space.output_geometry(active).unwrap();
+ let extended_geo = Rectangle::from_loc_and_size(
+ (active_geo.loc.x, i32::MIN / 2),
+ (active_geo.size.w, i32::MAX),
+ );
+
+ self.global_space
+ .outputs()
+ .map(|output| (output, self.global_space.output_geometry(output).unwrap()))
+ .filter(|(_, geo)| center(active_geo).y < center(*geo).y && geo.overlaps(extended_geo))
+ .min_by_key(|(_, geo)| center(*geo).y - center(active_geo).y)
+ .map(|(output, _)| output)
+ .cloned()
+ }
+
+ pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
+ let under = self.surface_under_and_global_space(location);
+ self.seat.get_pointer().unwrap().motion(
+ self,
+ under,
+ &MotionEvent {
+ location,
+ serial: SERIAL_COUNTER.next_serial(),
+ time: get_monotonic_time().as_millis() as u32,
+ },
+ );
+ // FIXME: granular
+ self.queue_redraw_all();
+ }
+
+ pub fn move_cursor_to_output(&mut self, output: &Output) {
+ let geo = self.global_space.output_geometry(output).unwrap();
+ self.move_cursor(center(geo).to_f64());
+ }
+
fn layer_surface_focus(&self) -> Option<WlSurface> {
let output = self.monitor_set.active_output()?;
let layers = layer_map_for_output(output);
diff --git a/src/utils.rs b/src/utils.rs
index 7d895530..b7dee6e2 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -7,7 +7,7 @@ use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::texture::TextureBuffer;
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
use smithay::reexports::nix::time::{clock_gettime, ClockId};
-use smithay::utils::{Physical, Point, Transform};
+use smithay::utils::{Logical, Physical, Point, Rectangle, Transform};
use xcursor::parser::parse_xcursor;
use xcursor::CursorTheme;
@@ -18,6 +18,10 @@ pub fn get_monotonic_time() -> Duration {
Duration::from(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap())
}
+pub fn center(rect: Rectangle<i32, Logical>) -> Point<i32, Logical> {
+ rect.loc + rect.size.downscale(2).to_point()
+}
+
fn load_xcursor() -> anyhow::Result<xcursor::parser::Image> {
let theme = CursorTheme::load("default");
let path = theme