aboutsummaryrefslogtreecommitdiff
path: root/src/niri.rs
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2023-08-13 12:46:53 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2023-08-13 12:46:53 +0400
commit95c810c855a27a28f4dfa7dc6b949fef0901c7b2 (patch)
treec240dd8d8c6eac7cd18c507fbe35724ca7de8aeb /src/niri.rs
parente02e35f9c61e103a01640d3dc95a894e8855e1c9 (diff)
downloadniri-95c810c855a27a28f4dfa7dc6b949fef0901c7b2.tar.gz
niri-95c810c855a27a28f4dfa7dc6b949fef0901c7b2.tar.bz2
niri-95c810c855a27a28f4dfa7dc6b949fef0901c7b2.zip
Refactor everything, add initial tiling code
Diffstat (limited to 'src/niri.rs')
-rw-r--r--src/niri.rs247
1 files changed, 204 insertions, 43 deletions
diff --git a/src/niri.rs b/src/niri.rs
index e5246313..d856b8c6 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -1,9 +1,10 @@
+use std::collections::HashMap;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use std::time::Duration;
-use smithay::backend::renderer::element::render_elements;
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
+use smithay::backend::renderer::element::{render_elements, RenderElement};
use smithay::backend::renderer::ImportAll;
use smithay::desktop::space::{space_render_elements, SpaceRenderElements};
use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};
@@ -11,12 +12,14 @@ use smithay::input::keyboard::XkbConfig;
use smithay::input::{Seat, SeatState};
use smithay::output::Output;
use smithay::reexports::calloop::generic::Generic;
-use smithay::reexports::calloop::{Interest, LoopHandle, LoopSignal, Mode, PostAction};
+use smithay::reexports::calloop::{Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
-use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};
+use smithay::reexports::wayland_server::backend::{
+ ClientData, ClientId, DisconnectReason, GlobalId,
+};
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::{Display, DisplayHandle};
-use smithay::utils::{Logical, Point};
+use smithay::utils::{Logical, Point, SERIAL_COUNTER};
use smithay::wayland::compositor::{CompositorClientState, CompositorState};
use smithay::wayland::data_device::DataDeviceState;
use smithay::wayland::output::OutputManagerState;
@@ -25,6 +28,7 @@ use smithay::wayland::shm::ShmState;
use smithay::wayland::socket::ListeningSocketSource;
use crate::backend::Backend;
+use crate::layout::MonitorSet;
use crate::LoopData;
pub struct Niri {
@@ -33,7 +37,18 @@ pub struct Niri {
pub stop_signal: LoopSignal,
pub display_handle: DisplayHandle,
- pub space: Space<Window>,
+ // Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it,
+ // however it may have none (when there are no outputs connected) or mutiple (when mirroring).
+ pub monitor_set: MonitorSet,
+
+ // This space does not actually contain any windows, but all outputs are mapped into it
+ // according to their global position.
+ pub global_space: Space<Window>,
+
+ // Windows which don't have a buffer attached yet.
+ pub unmapped_windows: HashMap<WlSurface, Window>,
+
+ pub output_state: HashMap<Output, OutputState>,
// Smithay state.
pub compositor_state: CompositorState,
@@ -45,13 +60,17 @@ pub struct Niri {
pub popups: PopupManager,
pub seat: Seat<Self>,
- pub output: Option<Output>,
pub pointer_buffer: SolidColorBuffer,
+}
- // Set to `true` if there's a redraw queued on the event loop. Reset to `false` in redraw()
- // which means that you cannot queue more than one redraw at once.
- pub redraw_queued: bool,
+pub struct OutputState {
+ pub global: GlobalId,
+ // Set if there's a redraw queued on the event loop. Reset in redraw() which means that you
+ // cannot queue more than one redraw at once.
+ pub queued_redraw: Option<Idle<'static>>,
+ // Set to `true` when the output was redrawn and is waiting for a VBlank. Upon VBlank a redraw
+ // will always be queued, so you cannot queue a redraw while waiting for a VBlank.
pub waiting_for_vblank: bool,
}
@@ -90,8 +109,6 @@ impl Niri {
seat.add_keyboard(xkb, 400, 30).unwrap();
seat.add_pointer();
- let space = Space::default();
-
let socket_source = ListeningSocketSource::new_auto().unwrap();
let socket_name = socket_source.socket_name().to_os_string();
event_loop
@@ -130,7 +147,10 @@ impl Niri {
stop_signal,
display_handle,
- space,
+ monitor_set: MonitorSet::new(),
+ global_space: Space::default(),
+ output_state: HashMap::new(),
+ unmapped_windows: HashMap::new(),
compositor_state,
xdg_shell_state,
@@ -141,60 +161,188 @@ impl Niri {
popups: PopupManager::default(),
seat,
- output: None,
pointer_buffer,
- redraw_queued: false,
+ }
+ }
+
+ pub fn add_output(&mut self, output: Output) {
+ let x = self
+ .global_space
+ .outputs()
+ .map(|output| self.global_space.output_geometry(output).unwrap())
+ .map(|geom| geom.loc.x + geom.size.w)
+ .max()
+ .unwrap_or(0);
+
+ self.global_space.map_output(&output, (x, 0));
+ self.monitor_set.add_output(output.clone());
+
+ let state = OutputState {
+ global: output.create_global::<Niri>(&self.display_handle),
+ queued_redraw: None,
waiting_for_vblank: false,
+ };
+ let rv = self.output_state.insert(output, state);
+ assert!(rv.is_none(), "output was already tracked");
+ }
+
+ pub fn remove_output(&mut self, output: &Output) {
+ let mut state = self.output_state.remove(output).unwrap();
+ self.display_handle.remove_global::<Niri>(state.global);
+
+ if let Some(idle) = state.queued_redraw.take() {
+ idle.cancel();
}
+
+ self.monitor_set.remove_output(output);
+ self.global_space.unmap_output(output);
+ // FIXME: reposition outputs so they are adjacent.
+ }
+
+ pub fn output_resized(&mut self, output: Output) {
+ // FIXME resize windows etc
+ self.queue_redraw(output);
+ }
+
+ pub fn output_under(&self, pos: Point<f64, Logical>) -> Option<(&Output, Point<f64, Logical>)> {
+ let output = self.global_space.output_under(pos).next()?;
+ let pos_within_output = pos
+ - self
+ .global_space
+ .output_geometry(output)
+ .unwrap()
+ .loc
+ .to_f64();
+
+ Some((output, pos_within_output))
+ }
+
+ pub fn window_under(
+ &mut self,
+ pos: Point<f64, Logical>,
+ ) -> Option<(&mut Space<Window>, Window, Point<i32, Logical>)> {
+ let (output, pos_within_output) = self.output_under(pos)?;
+ let output = output.clone();
+
+ let space = &mut self.monitor_set.workspace_for_output(&output)?.space;
+ let output_pos = space.output_geometry(&output).unwrap().loc.to_f64();
+
+ let pos_within_space = pos_within_output + output_pos;
+ let (window, loc) = space.element_under(pos_within_space)?;
+ let window = window.clone();
+ Some((space, window, loc))
}
pub fn surface_under(
- &self,
+ &mut self,
pos: Point<f64, Logical>,
) -> Option<(WlSurface, Point<i32, Logical>)> {
- self.space
- .element_under(pos)
+ let (output, pos_within_output) = self.output_under(pos)?;
+ let output = output.clone();
+
+ let space = &self.monitor_set.workspace_for_output(&output)?.space;
+ let output_pos = space.output_geometry(&output).unwrap().loc.to_f64();
+
+ let pos_within_space = pos_within_output + output_pos;
+ space
+ .element_under(pos_within_space)
.and_then(|(window, location)| {
window
- .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL)
+ .surface_under(pos_within_space - location.to_f64(), WindowSurfaceType::ALL)
.map(|(s, p)| (s, p + location))
})
}
+ /// Returns the surface under cursor and its position in the global space.
+ ///
+ /// Pointer needs location in global space, and focused window location compatible with that
+ /// global space. We don't have a global space for all windows, but this function converts the
+ /// window location temporarily to the current global space.
+ pub fn surface_under_and_global_space(
+ &mut self,
+ pos: Point<f64, Logical>,
+ ) -> Option<(WlSurface, Point<i32, Logical>)> {
+ let (output, pos_within_output) = self.output_under(pos)?;
+ let output = output.clone();
+
+ let workspace = &self.monitor_set.workspace_for_output(&output)?;
+ let space = &workspace.space;
+ let output_pos_in_local_space = space.output_geometry(&output).unwrap().loc;
+ let pos_within_space = pos_within_output + output_pos_in_local_space.to_f64();
+
+ let (surface, surface_loc_in_local_space) = space
+ .element_under(pos_within_space)
+ .and_then(|(window, location)| {
+ window
+ .surface_under(pos_within_space - location.to_f64(), WindowSurfaceType::ALL)
+ .map(|(s, p)| (s, p + location))
+ })?;
+ let output_pos_in_global_space = self.global_space.output_geometry(&output).unwrap().loc;
+ let surface_loc_in_global_space =
+ surface_loc_in_local_space - output_pos_in_local_space + output_pos_in_global_space;
+
+ Some((surface, surface_loc_in_global_space))
+ }
+
+ pub fn output_under_cursor(&self) -> Option<Output> {
+ let pos = self.seat.get_pointer().unwrap().current_location();
+ self.global_space.output_under(pos).next().cloned()
+ }
+
+ pub fn update_focus(&mut self) {
+ let focus = self
+ .monitor_set
+ .focus()
+ .map(|win| win.toplevel().wl_surface().clone());
+ let keyboard = self.seat.get_keyboard().unwrap();
+ if keyboard.current_focus() != focus {
+ keyboard.set_focus(self, focus, SERIAL_COUNTER.next_serial());
+ // FIXME: can be more granular.
+ self.queue_redraw_all();
+ }
+ }
+
+ /// Schedules an immediate redraw on all outputs if one is not already scheduled.
+ pub fn queue_redraw_all(&mut self) {
+ let outputs: Vec<_> = self.output_state.keys().cloned().collect();
+ for output in outputs {
+ self.queue_redraw(output);
+ }
+ }
+
/// Schedules an immediate redraw if one is not already scheduled.
- pub fn queue_redraw(&mut self) {
- if self.redraw_queued || self.waiting_for_vblank {
+ pub fn queue_redraw(&mut self, output: Output) {
+ let state = self.output_state.get_mut(&output).unwrap();
+
+ if state.queued_redraw.is_some() || state.waiting_for_vblank {
return;
}
- self.redraw_queued = true;
-
// Timer::immediate() adds a millisecond of delay for some reason.
// This should be fixed in calloop v0.11: https://github.com/Smithay/calloop/issues/142
- self.event_loop.insert_idle(|data| {
+ let idle = self.event_loop.insert_idle(move |data| {
let backend: &mut dyn Backend = if let Some(tty) = &mut data.tty {
tty
} else {
data.winit.as_mut().unwrap()
};
- data.niri.redraw(backend);
+ data.niri.redraw(backend, &output);
});
+ state.queued_redraw = Some(idle);
}
- fn redraw(&mut self, backend: &mut dyn Backend) {
+ fn redraw(&mut self, backend: &mut dyn Backend, output: &Output) {
let _span = tracy_client::span!("redraw");
+ let state = self.output_state.get_mut(output).unwrap();
- assert!(self.redraw_queued);
- assert!(!self.waiting_for_vblank);
- self.redraw_queued = false;
+ assert!(state.queued_redraw.take().is_some());
+ assert!(!state.waiting_for_vblank);
- let elements = space_render_elements(
- backend.renderer(),
- [&self.space],
- self.output.as_ref().unwrap(),
- 1.,
- )
- .unwrap();
+ let space = &self.monitor_set.workspace_for_output(output).unwrap().space;
+ let elements = space_render_elements(backend.renderer(), [space], output, 1.).unwrap();
+
+ let output_pos = self.global_space.output_geometry(output).unwrap().loc;
+ let pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64();
let mut elements: Vec<_> = elements
.into_iter()
@@ -204,20 +352,16 @@ impl Niri {
0,
OutputRenderElements::Pointer(SolidColorRenderElement::from_buffer(
&self.pointer_buffer,
- self.seat
- .get_pointer()
- .unwrap()
- .current_location()
- .to_physical_precise_round(1.),
+ pointer_pos.to_physical_precise_round(1.),
1.,
1.,
)),
);
- backend.render(self, &elements);
+ backend.render(self, output, &elements);
- let output = self.output.as_ref().unwrap();
- self.space.elements().for_each(|window| {
+ let space = &self.monitor_set.workspace_for_output(output).unwrap().space;
+ space.elements().for_each(|window| {
window.send_frame(
output,
self.start_time.elapsed(),
@@ -234,6 +378,23 @@ render_elements! {
Pointer = SolidColorRenderElement,
}
+impl<R: ImportAll, E: RenderElement<R>> std::fmt::Debug for OutputRenderElements<R, E> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ OutputRenderElements::Space(_) => {
+ f.debug_tuple("OutputRenderElements::Space").finish()?
+ }
+ OutputRenderElements::Pointer(element) => f
+ .debug_tuple("OutputRenderElements::Pointer")
+ .field(element)
+ .finish()?,
+ _ => (),
+ }
+
+ Ok(())
+ }
+}
+
#[derive(Default)]
pub struct ClientState {
pub compositor_state: CompositorClientState,