2023-08-13 12:46:53 +04:00
|
|
|
use std::collections::HashMap;
|
2023-08-09 11:03:38 +04:00
|
|
|
use std::os::unix::io::AsRawFd;
|
|
|
|
|
use std::sync::Arc;
|
2023-08-14 15:53:24 +04:00
|
|
|
use std::time::Duration;
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
use smithay::backend::renderer::element::render_elements;
|
2023-08-10 14:27:09 +04:00
|
|
|
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
2023-08-10 09:57:13 +04:00
|
|
|
use smithay::backend::renderer::ImportAll;
|
2023-08-11 08:22:34 +04:00
|
|
|
use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::input::keyboard::XkbConfig;
|
|
|
|
|
use smithay::input::{Seat, SeatState};
|
|
|
|
|
use smithay::output::Output;
|
|
|
|
|
use smithay::reexports::calloop::generic::Generic;
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::reexports::calloop::{Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction};
|
2023-08-10 17:17:17 +04:00
|
|
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::reexports::wayland_server::backend::{
|
|
|
|
|
ClientData, ClientId, DisconnectReason, GlobalId,
|
|
|
|
|
};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
|
|
|
|
use smithay::reexports::wayland_server::{Display, DisplayHandle};
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::utils::{Logical, Point, SERIAL_COUNTER};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::wayland::compositor::{CompositorClientState, CompositorState};
|
|
|
|
|
use smithay::wayland::data_device::DataDeviceState;
|
|
|
|
|
use smithay::wayland::output::OutputManagerState;
|
|
|
|
|
use smithay::wayland::shell::xdg::XdgShellState;
|
|
|
|
|
use smithay::wayland::shm::ShmState;
|
|
|
|
|
use smithay::wayland::socket::ListeningSocketSource;
|
|
|
|
|
|
|
|
|
|
use crate::backend::Backend;
|
2023-08-14 15:53:24 +04:00
|
|
|
use crate::frame_clock::FrameClock;
|
2023-08-14 16:19:43 +04:00
|
|
|
use crate::layout::{MonitorRenderElement, MonitorSet};
|
2023-08-09 11:03:38 +04:00
|
|
|
use crate::LoopData;
|
|
|
|
|
|
|
|
|
|
pub struct Niri {
|
|
|
|
|
pub start_time: std::time::Instant,
|
|
|
|
|
pub event_loop: LoopHandle<'static, LoopData>,
|
|
|
|
|
pub stop_signal: LoopSignal,
|
|
|
|
|
pub display_handle: DisplayHandle,
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
// 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).
|
2023-08-13 19:55:37 +04:00
|
|
|
pub monitor_set: MonitorSet<Window>,
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
// 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>,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
// Smithay state.
|
|
|
|
|
pub compositor_state: CompositorState,
|
|
|
|
|
pub xdg_shell_state: XdgShellState,
|
|
|
|
|
pub shm_state: ShmState,
|
|
|
|
|
pub output_manager_state: OutputManagerState,
|
|
|
|
|
pub seat_state: SeatState<Self>,
|
|
|
|
|
pub data_device_state: DataDeviceState,
|
2023-08-11 08:22:34 +04:00
|
|
|
pub popups: PopupManager,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
pub seat: Seat<Self>,
|
2023-08-10 09:58:26 +04:00
|
|
|
|
2023-08-10 14:27:09 +04:00
|
|
|
pub pointer_buffer: SolidColorBuffer,
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-10 14:27:09 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
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.
|
2023-08-10 09:58:26 +04:00
|
|
|
pub waiting_for_vblank: bool,
|
2023-08-14 15:53:24 +04:00
|
|
|
pub frame_clock: FrameClock,
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Niri {
|
|
|
|
|
pub fn new(
|
|
|
|
|
event_loop: LoopHandle<'static, LoopData>,
|
|
|
|
|
stop_signal: LoopSignal,
|
|
|
|
|
display: &mut Display<Self>,
|
|
|
|
|
seat_name: String,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let start_time = std::time::Instant::now();
|
|
|
|
|
|
|
|
|
|
let display_handle = display.handle();
|
|
|
|
|
|
|
|
|
|
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
2023-08-10 17:17:17 +04:00
|
|
|
let xdg_shell_state = XdgShellState::new_with_capabilities::<Self>(
|
|
|
|
|
&display_handle,
|
|
|
|
|
[
|
|
|
|
|
WmCapabilities::Fullscreen,
|
|
|
|
|
WmCapabilities::Maximize,
|
|
|
|
|
WmCapabilities::WindowMenu,
|
|
|
|
|
],
|
|
|
|
|
);
|
2023-08-09 11:03:38 +04:00
|
|
|
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
|
|
|
|
|
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle);
|
|
|
|
|
let mut seat_state = SeatState::new();
|
|
|
|
|
let data_device_state = DataDeviceState::new::<Self>(&display_handle);
|
|
|
|
|
|
|
|
|
|
let mut seat: Seat<Self> = seat_state.new_wl_seat(&display_handle, seat_name);
|
|
|
|
|
// FIXME: get Xkb and repeat interval from GNOME dconf.
|
2023-08-10 18:14:11 +04:00
|
|
|
let xkb = XkbConfig {
|
|
|
|
|
layout: "us,ru",
|
|
|
|
|
options: Some("grp:win_space_toggle".to_owned()),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
seat.add_keyboard(xkb, 400, 30).unwrap();
|
2023-08-09 11:03:38 +04:00
|
|
|
seat.add_pointer();
|
|
|
|
|
|
|
|
|
|
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
|
|
|
|
let socket_name = socket_source.socket_name().to_os_string();
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(socket_source, move |client, _, data| {
|
|
|
|
|
if let Err(err) = data
|
|
|
|
|
.display_handle
|
|
|
|
|
.insert_client(client, Arc::new(ClientState::default()))
|
|
|
|
|
{
|
|
|
|
|
error!("error inserting client: {err}");
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
std::env::set_var("WAYLAND_DISPLAY", &socket_name);
|
|
|
|
|
info!(
|
|
|
|
|
"listening on Wayland socket: {}",
|
|
|
|
|
socket_name.to_string_lossy()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let display_source = Generic::new(
|
|
|
|
|
display.backend().poll_fd().as_raw_fd(),
|
|
|
|
|
Interest::READ,
|
|
|
|
|
Mode::Level,
|
|
|
|
|
);
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(display_source, |_, _, data| {
|
|
|
|
|
data.display.dispatch_clients(&mut data.niri).unwrap();
|
|
|
|
|
Ok(PostAction::Continue)
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-08-10 14:27:09 +04:00
|
|
|
let pointer_buffer = SolidColorBuffer::new((16, 16), [1., 0.8, 0., 1.]);
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
Self {
|
|
|
|
|
start_time,
|
|
|
|
|
event_loop,
|
|
|
|
|
stop_signal,
|
|
|
|
|
display_handle,
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
monitor_set: MonitorSet::new(),
|
|
|
|
|
global_space: Space::default(),
|
|
|
|
|
output_state: HashMap::new(),
|
|
|
|
|
unmapped_windows: HashMap::new(),
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
compositor_state,
|
|
|
|
|
xdg_shell_state,
|
|
|
|
|
shm_state,
|
|
|
|
|
output_manager_state,
|
|
|
|
|
seat_state,
|
|
|
|
|
data_device_state,
|
2023-08-11 08:22:34 +04:00
|
|
|
popups: PopupManager::default(),
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
seat,
|
2023-08-10 14:27:09 +04:00
|
|
|
pointer_buffer,
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 15:53:24 +04:00
|
|
|
pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) {
|
2023-08-13 12:46:53 +04:00
|
|
|
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,
|
2023-08-10 09:58:26 +04:00
|
|
|
waiting_for_vblank: false,
|
2023-08-14 15:53:24 +04:00
|
|
|
frame_clock: FrameClock::new(refresh_interval),
|
2023-08-13 12:46:53 +04:00
|
|
|
};
|
|
|
|
|
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();
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
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) {
|
2023-08-13 19:55:37 +04:00
|
|
|
self.monitor_set.update_output(&output);
|
2023-08-13 12:46:53 +04:00
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn window_under_cursor(&self) -> Option<&Window> {
|
|
|
|
|
let pos = self.seat.get_pointer().unwrap().current_location();
|
2023-08-13 12:46:53 +04:00
|
|
|
let (output, pos_within_output) = self.output_under(pos)?;
|
2023-08-13 19:55:37 +04:00
|
|
|
let (window, _loc) = self.monitor_set.window_under(output, pos_within_output)?;
|
|
|
|
|
Some(window)
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
/// 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)?;
|
2023-08-13 19:55:37 +04:00
|
|
|
let (window, win_pos_within_output) =
|
|
|
|
|
self.monitor_set.window_under(output, pos_within_output)?;
|
|
|
|
|
|
|
|
|
|
let (surface, surface_pos_within_output) = window
|
|
|
|
|
.surface_under(
|
|
|
|
|
pos_within_output - win_pos_within_output.to_f64(),
|
|
|
|
|
WindowSurfaceType::ALL,
|
|
|
|
|
)
|
|
|
|
|
.map(|(s, pos_within_window)| (s, pos_within_window + win_pos_within_output))?;
|
|
|
|
|
let output_pos_in_global_space = self.global_space.output_geometry(output).unwrap().loc;
|
|
|
|
|
let surface_loc_in_global_space = surface_pos_within_output + output_pos_in_global_space;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-10 09:58:26 +04:00
|
|
|
/// Schedules an immediate redraw if one is not already scheduled.
|
2023-08-13 12:46:53 +04:00
|
|
|
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 {
|
2023-08-10 09:58:26 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-10 14:06:32 +04:00
|
|
|
// 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
|
2023-08-13 12:46:53 +04:00
|
|
|
let idle = self.event_loop.insert_idle(move |data| {
|
2023-08-10 14:06:32 +04:00
|
|
|
let backend: &mut dyn Backend = if let Some(tty) = &mut data.tty {
|
|
|
|
|
tty
|
|
|
|
|
} else {
|
|
|
|
|
data.winit.as_mut().unwrap()
|
|
|
|
|
};
|
2023-08-13 12:46:53 +04:00
|
|
|
data.niri.redraw(backend, &output);
|
2023-08-10 14:06:32 +04:00
|
|
|
});
|
2023-08-13 12:46:53 +04:00
|
|
|
state.queued_redraw = Some(idle);
|
2023-08-10 09:58:26 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
fn redraw(&mut self, backend: &mut dyn Backend, output: &Output) {
|
2023-08-10 14:12:20 +04:00
|
|
|
let _span = tracy_client::span!("redraw");
|
2023-08-13 12:46:53 +04:00
|
|
|
let state = self.output_state.get_mut(output).unwrap();
|
2023-08-14 15:54:11 +04:00
|
|
|
let presentation_time = state.frame_clock.next_presentation_time();
|
2023-08-10 14:12:20 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
assert!(state.queued_redraw.take().is_some());
|
|
|
|
|
assert!(!state.waiting_for_vblank);
|
2023-08-10 09:58:26 +04:00
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
let mon = self.monitor_set.monitor_for_output_mut(output).unwrap();
|
|
|
|
|
mon.advance_animations(presentation_time);
|
|
|
|
|
let elements = mon.render_elements(backend.renderer());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
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();
|
2023-08-10 09:57:13 +04:00
|
|
|
|
|
|
|
|
let mut elements: Vec<_> = elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from)
|
|
|
|
|
.collect();
|
|
|
|
|
elements.insert(
|
|
|
|
|
0,
|
2023-08-10 14:27:09 +04:00
|
|
|
OutputRenderElements::Pointer(SolidColorRenderElement::from_buffer(
|
|
|
|
|
&self.pointer_buffer,
|
2023-08-13 12:46:53 +04:00
|
|
|
pointer_pos.to_physical_precise_round(1.),
|
2023-08-10 14:27:09 +04:00
|
|
|
1.,
|
|
|
|
|
1.,
|
2023-08-10 09:57:13 +04:00
|
|
|
)),
|
|
|
|
|
);
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
backend.render(self, output, &elements);
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
self.monitor_set
|
|
|
|
|
.send_frame(output, self.start_time.elapsed());
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-10 09:57:13 +04:00
|
|
|
render_elements! {
|
2023-08-13 19:55:37 +04:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub OutputRenderElements<R> where R: ImportAll;
|
2023-08-14 16:19:43 +04:00
|
|
|
Wayland = MonitorRenderElement<R>,
|
2023-08-10 09:57:13 +04:00
|
|
|
Pointer = SolidColorRenderElement,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct ClientState {
|
|
|
|
|
pub compositor_state: CompositorClientState,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ClientData for ClientState {
|
|
|
|
|
fn initialized(&self, _client_id: ClientId) {}
|
|
|
|
|
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
|
|
|
|
}
|