diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | src/input.rs | 77 | ||||
| -rw-r--r-- | src/layout.rs | 42 | ||||
| -rw-r--r-- | src/niri.rs | 94 | ||||
| -rw-r--r-- | src/utils.rs | 6 |
5 files changed, 213 insertions, 8 deletions
@@ -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 |
