Refactor everything, add initial tiling code

This commit is contained in:
Ivan Molodetskikh
2023-08-13 12:46:53 +04:00
parent e02e35f9c6
commit 95c810c855
13 changed files with 1969 additions and 433 deletions
+2
View File
@@ -1,5 +1,6 @@
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::output::Output;
use crate::niri::OutputRenderElements;
use crate::Niri;
@@ -10,6 +11,7 @@ pub trait Backend {
fn render(
&mut self,
niri: &mut Niri,
output: &Output,
elements: &[OutputRenderElements<
GlesRenderer,
WaylandSurfaceRenderElement<GlesRenderer>,
+8 -4
View File
@@ -5,6 +5,7 @@ use smithay::input::pointer::{
};
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::{Logical, Point};
use smithay::wayland::seat::WaylandFocus;
use crate::Niri;
@@ -25,10 +26,13 @@ impl PointerGrab<Niri> for MoveSurfaceGrab {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
let delta = event.location - self.start_data.location;
let new_location = self.initial_window_location.to_f64() + delta;
data.space
.map_element(self.window.clone(), new_location.to_i32_round(), true);
// let delta = event.location - self.start_data.location;
// let new_location = self.initial_window_location.to_f64() + delta;
// let (window, space) = data
// .monitor_set
// .find_window_and_space(self.window.wl_surface().as_ref().unwrap())
// .unwrap();
// space.map_element(window.clone(), new_location.to_i32_round(), true);
}
fn relative_motion(
+40 -40
View File
@@ -1,6 +1,6 @@
use std::cell::RefCell;
use smithay::desktop::{Space, Window};
use smithay::desktop::Window;
use smithay::input::pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle, RelativeMotionEvent,
@@ -241,48 +241,48 @@ impl ResizeSurfaceState {
}
}
/// Should be called on `WlSurface::commit`
pub fn handle_commit(space: &mut Space<Window>, surface: &WlSurface) -> Option<()> {
let window = space
.elements()
.find(|w| w.toplevel().wl_surface() == surface)
.cloned()?;
let mut window_loc = space.element_location(&window)?;
let geometry = window.geometry();
let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with(surface, |state| {
state
.commit()
.and_then(|(edges, initial_rect)| {
// If the window is being resized by top or left, its location must be adjusted
// accordingly.
edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
let new_x = edges
.intersects(ResizeEdge::LEFT)
.then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w));
let new_y = edges
.intersects(ResizeEdge::TOP)
.then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h));
(new_x, new_y).into()
})
})
.unwrap_or_default()
pub fn handle_commit(window: &Window) -> Option<()> {
// FIXME
let surface = window.toplevel().wl_surface();
ResizeSurfaceState::with(surface, |state| {
state.commit();
});
if let Some(new_x) = new_loc.x {
window_loc.x = new_x;
}
if let Some(new_y) = new_loc.y {
window_loc.y = new_y;
}
// let mut window_loc = space.element_location(&window)?;
// let geometry = window.geometry();
if new_loc.x.is_some() || new_loc.y.is_some() {
// If TOP or LEFT side of the window got resized, we have to move it
space.map_element(window, window_loc, false);
}
// let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with(surface, |state| {
// state
// .commit()
// .and_then(|(edges, initial_rect)| {
// // If the window is being resized by top or left, its location must be adjusted
// // accordingly.
// edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
// let new_x = edges
// .intersects(ResizeEdge::LEFT)
// .then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w));
// let new_y = edges
// .intersects(ResizeEdge::TOP)
// .then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h));
// (new_x, new_y).into()
// })
// })
// .unwrap_or_default()
// });
// if let Some(new_x) = new_loc.x {
// window_loc.x = new_x;
// }
// if let Some(new_y) = new_loc.y {
// window_loc.y = new_y;
// }
// if new_loc.x.is_some() || new_loc.y.is_some() {
// // If TOP or LEFT side of the window got resized, we have to move it
// space.map_element(window, window_loc, false);
// }
Some(())
}
+89 -14
View File
@@ -1,4 +1,7 @@
use smithay::backend::renderer::utils::on_commit_buffer_handler;
use std::collections::hash_map::Entry;
use smithay::backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state};
use smithay::desktop::find_popup_root_surface;
use smithay::reexports::wayland_server::protocol::wl_buffer;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::Client;
@@ -9,6 +12,7 @@ use smithay::wayland::compositor::{
use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::{delegate_compositor, delegate_shm};
use super::xdg_shell;
use crate::grabs::resize_grab;
use crate::niri::ClientState;
use crate::Niri;
@@ -28,24 +32,95 @@ impl CompositorHandler for Niri {
.message("client commit", 0);
on_commit_buffer_handler::<Self>(surface);
if !is_sync_subsurface(surface) {
let mut root = surface.clone();
while let Some(parent) = get_parent(&root) {
root = parent;
if is_sync_subsurface(surface) {
return;
}
let mut root_surface = surface.clone();
while let Some(parent) = get_parent(&root_surface) {
root_surface = parent;
}
if surface == &root_surface {
// This is a root surface commit. It might have mapped a previously-unmapped toplevel.
if let Entry::Occupied(entry) = self.unmapped_windows.entry(surface.clone()) {
let is_mapped =
with_renderer_surface_state(surface, |state| state.buffer().is_some());
if is_mapped {
// The toplevel got mapped.
let window = entry.remove();
window.on_commit();
let output = self.monitor_set.active_output().unwrap().clone();
self.monitor_set.add_window_to_output(&output, window, true);
self.update_focus();
self.queue_redraw(output);
return;
}
// The toplevel remains unmapped.
let window = entry.get();
xdg_shell::send_initial_configure_if_needed(window);
return;
}
if let Some(window) = self
.space
.elements()
.find(|w| w.toplevel().wl_surface() == &root)
{
// This is a commit of a previously-mapped root or a non-toplevel root.
if let Some((window, space)) = self.monitor_set.find_window_and_space(surface) {
// This is a commit of a previously-mapped toplevel.
let output = space.outputs().next().unwrap().clone();
window.on_commit();
// This is a commit of a previously-mapped toplevel.
let is_mapped =
with_renderer_surface_state(surface, |state| state.buffer().is_some());
if !is_mapped {
// The toplevel got unmapped.
let window = window.clone();
self.monitor_set.remove_window(&window);
self.unmapped_windows.insert(surface.clone(), window);
self.update_focus();
self.queue_redraw(output);
return;
}
// The toplevel remains mapped.
resize_grab::handle_commit(&window);
self.monitor_set.update_window(&window);
self.queue_redraw(output);
return;
}
};
self.xdg_handle_commit(surface);
resize_grab::handle_commit(&mut self.space, surface);
// This is a commit of a non-toplevel root.
}
self.queue_redraw();
// This is a commit of a non-root or a non-toplevel root.
let root_window_space = self.monitor_set.find_window_and_space(&root_surface);
if let Some((window, space)) = root_window_space {
let output = space.outputs().next().unwrap().clone();
window.on_commit();
self.monitor_set.update_window(&window);
self.queue_redraw(output);
return;
}
// This might be a popup.
self.popups_handle_commit(surface);
if let Some(popup) = self.popups.find_popup(surface) {
if let Ok(root) = find_popup_root_surface(&popup) {
let root_window_space = self.monitor_set.find_window_and_space(&root);
if let Some((_window, space)) = root_window_space {
let output = space.outputs().next().unwrap().clone();
self.queue_redraw(output);
}
}
}
}
}
+82 -95
View File
@@ -2,20 +2,19 @@ use smithay::delegate_xdg_shell;
use smithay::desktop::{PopupKind, Window};
use smithay::input::pointer::{Focus, GrabStartData as PointerGrabStartData};
use smithay::input::Seat;
use smithay::output::Output;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::protocol::{wl_output, wl_seat};
use smithay::reexports::wayland_server::Resource;
use smithay::utils::{Rectangle, Serial};
use smithay::wayland::compositor::with_states;
use smithay::wayland::seat::WaylandFocus;
use smithay::wayland::shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
XdgShellState, XdgToplevelSurfaceData,
};
use crate::grabs::{MoveSurfaceGrab, ResizeSurfaceGrab};
use crate::layout::MonitorSet;
use crate::Niri;
impl XdgShellHandler for Niri {
@@ -24,8 +23,16 @@ impl XdgShellHandler for Niri {
}
fn new_toplevel(&mut self, surface: ToplevelSurface) {
let wl_surface = surface.wl_surface().clone();
let window = Window::new(surface);
self.space.map_element(window, (0, 0), false);
// Tell the surface the preferred size and bounds for its likely output.
let output = self.monitor_set.active_output().unwrap();
MonitorSet::configure_new_window(output, &window);
// At the moment of creation, xdg toplevels must have no buffer.
let existing = self.unmapped_windows.insert(wl_surface, window);
assert!(existing.is_none());
}
fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
@@ -49,17 +56,12 @@ impl XdgShellHandler for Niri {
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
let pointer = seat.get_pointer().unwrap();
let window = self
.space
.elements()
.find(|w| w.toplevel().wl_surface() == wl_surface)
.unwrap()
.clone();
let initial_window_location = self.space.element_location(&window).unwrap();
let (window, space) = self.monitor_set.find_window_and_space(wl_surface).unwrap();
let initial_window_location = space.element_location(&window).unwrap();
let grab = MoveSurfaceGrab {
start_data,
window,
window: window.clone(),
initial_window_location,
};
@@ -81,13 +83,8 @@ impl XdgShellHandler for Niri {
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
let pointer = seat.get_pointer().unwrap();
let window = self
.space
.elements()
.find(|w| w.toplevel().wl_surface() == wl_surface)
.unwrap()
.clone();
let initial_window_location = self.space.element_location(&window).unwrap();
let (window, space) = self.monitor_set.find_window_and_space(wl_surface).unwrap();
let initial_window_location = space.element_location(&window).unwrap();
let initial_window_size = window.geometry().size;
surface.with_pending_state(|state| {
@@ -98,7 +95,7 @@ impl XdgShellHandler for Niri {
let grab = ResizeSurfaceGrab::start(
start_data,
window,
window.clone(),
edges.into(),
Rectangle::from_loc_and_size(initial_window_location, initial_window_size),
);
@@ -126,7 +123,7 @@ impl XdgShellHandler for Niri {
}
fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {
// TODO popup grabs
// FIXME popup grabs
}
fn maximize_request(&mut self, surface: ToplevelSurface) {
@@ -135,23 +132,17 @@ impl XdgShellHandler for Niri {
.capabilities
.contains(xdg_toplevel::WmCapabilities::Maximize)
{
let wl_surface = surface.wl_surface();
let window = self
.space
.elements()
.find(|w| w.toplevel().wl_surface() == wl_surface)
.unwrap()
.clone();
let geometry = self
.space
.output_geometry(self.output.as_ref().unwrap())
.unwrap();
// let wl_surface = surface.wl_surface();
// let (window, space) = self.monitor_set.find_window_and_space(wl_surface).unwrap();
// let geometry = space
// .output_geometry(space.outputs().next().unwrap())
// .unwrap();
surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Maximized);
state.size = Some(geometry.size);
});
self.space.map_element(window, geometry.loc, true);
// surface.with_pending_state(|state| {
// state.states.set(xdg_toplevel::State::Maximized);
// state.size = Some(geometry.size);
// });
// space.map_element(window.clone(), geometry.loc, true);
}
// The protocol demands us to always reply with a configure,
@@ -185,44 +176,32 @@ impl XdgShellHandler for Niri {
.capabilities
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
{
// NOTE: This is only one part of the solution. We can set the
// location and configure size here, but the surface should be rendered fullscreen
// independently from its buffer size
let wl_surface = surface.wl_surface();
// // NOTE: This is only one part of the solution. We can set the
// // location and configure size here, but the surface should be rendered fullscreen
// // independently from its buffer size
// let wl_surface = surface.wl_surface();
let output = wl_output
.as_ref()
.and_then(Output::from_resource)
.or_else(|| {
let w = self
.space
.elements()
.find(|window| {
window
.wl_surface()
.map(|s| s == *wl_surface)
.unwrap_or(false)
})
.cloned();
w.and_then(|w| self.space.outputs_for_element(&w).get(0).cloned())
});
// let output = wl_output
// .as_ref()
// .and_then(Output::from_resource)
// .or_else(|| {
// self.monitor_set
// .find_window_and_space(wl_surface)
// .and_then(|(_window, space)| space.outputs().next().cloned())
// });
if let Some(output) = output {
let geometry = self.space.output_geometry(&output).unwrap();
// if let Some(output) = output {
// let (window, space) =
// self.monitor_set.find_window_and_space(wl_surface).unwrap();
// let geometry = space.output_geometry(&output).unwrap();
surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
state.size = Some(geometry.size);
});
// surface.with_pending_state(|state| {
// state.states.set(xdg_toplevel::State::Fullscreen);
// state.size = Some(geometry.size);
// });
let window = self
.space
.elements()
.find(|w| w.toplevel().wl_surface() == wl_surface)
.unwrap()
.clone();
self.space.map_element(window, geometry.loc, true);
}
// space.map_element(window.clone(), geometry.loc, true);
// }
}
// The protocol demands us to always reply with a configure,
@@ -247,12 +226,25 @@ impl XdgShellHandler for Niri {
surface.send_pending_configure();
}
fn toplevel_destroyed(&mut self, _surface: ToplevelSurface) {
self.queue_redraw();
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
if self.unmapped_windows.remove(surface.wl_surface()).is_some() {
// An unmapped toplevel got destroyed.
return;
}
let (window, space) = self
.monitor_set
.find_window_and_space(surface.wl_surface())
.unwrap();
let output = space.outputs().next().unwrap().clone();
self.monitor_set.remove_window(&window);
self.update_focus();
self.queue_redraw(output);
}
fn popup_destroyed(&mut self, _surface: PopupSurface) {
self.queue_redraw();
// FIXME granular
self.queue_redraw_all();
}
}
@@ -282,32 +274,27 @@ fn check_grab(
Some(start_data)
}
pub fn send_initial_configure_if_needed(window: &Window) {
let initial_configure_sent = with_states(window.toplevel().wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.initial_configure_sent
});
if !initial_configure_sent {
window.toplevel().send_configure();
}
}
impl Niri {
/// Should be called on `WlSurface::commit`
pub fn xdg_handle_commit(&mut self, surface: &WlSurface) {
pub fn popups_handle_commit(&mut self, surface: &WlSurface) {
self.popups.commit(surface);
if let Some(window) = self
.space
.elements()
.find(|w| w.toplevel().wl_surface() == surface)
.cloned()
{
let initial_configure_sent = with_states(surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.initial_configure_sent
});
if !initial_configure_sent {
window.toplevel().send_configure();
}
}
if let Some(popup) = self.popups.find_popup(surface) {
let PopupKind::Xdg(ref popup) = popup;
let initial_configure_sent = with_states(surface, |states| {
+201 -77
View File
@@ -5,21 +5,35 @@ use smithay::backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
};
use smithay::input::keyboard::{keysyms, FilterResult};
use smithay::input::keyboard::{keysyms, FilterResult, KeysymHandle, ModifiersState};
use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::SERIAL_COUNTER;
use smithay::wayland::shell::xdg::XdgShellHandler;
use crate::niri::Niri;
enum InputAction {
enum Action {
None,
Quit,
ChangeVt(i32),
SpawnTerminal,
CloseWindow,
ToggleFullscreen,
FocusLeft,
FocusRight,
FocusDown,
FocusUp,
MoveLeft,
MoveRight,
MoveDown,
MoveUp,
ConsumeIntoColumn,
ExpelFromColumn,
SwitchWorkspaceDown,
SwitchWorkspaceUp,
MoveToWorkspaceDown,
MoveToWorkspaceUp,
}
pub enum CompositorMod {
@@ -27,11 +41,64 @@ pub enum CompositorMod {
Alt,
}
impl From<Action> for FilterResult<Action> {
fn from(value: Action) -> Self {
match value {
Action::None => FilterResult::Forward,
action => FilterResult::Intercept(action),
}
}
}
fn action(comp_mod: CompositorMod, keysym: KeysymHandle, mods: ModifiersState) -> Action {
use keysyms::*;
let modified = keysym.modified_sym();
if matches!(modified, KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12) {
let vt = (modified - KEY_XF86Switch_VT_1 + 1) as i32;
return Action::ChangeVt(vt);
}
let mod_down = match comp_mod {
CompositorMod::Super => mods.logo,
CompositorMod::Alt => mods.alt,
};
if !mod_down {
return Action::None;
}
// FIXME: these don't work in the Russian layout. I guess I'll need to
// find a US keymap, then map keys somehow.
#[allow(non_upper_case_globals)] // wat
match modified {
KEY_E => Action::Quit,
KEY_t => Action::SpawnTerminal,
KEY_q => Action::CloseWindow,
KEY_f => Action::ToggleFullscreen,
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,
KEY_h | KEY_Left => Action::FocusLeft,
KEY_l | KEY_Right => Action::FocusRight,
KEY_j | KEY_Down => Action::FocusDown,
KEY_k | KEY_Up => Action::FocusUp,
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,
_ => Action::None,
}
}
impl Niri {
pub fn process_input_event<I: InputBackend>(
&mut self,
change_vt: &mut dyn FnMut(i32),
compositor_mod: CompositorMod,
comp_mod: CompositorMod,
event: InputEvent<I>,
) {
let _span = tracy_client::span!("process_input_event");
@@ -50,33 +117,7 @@ impl Niri {
time,
|_, mods, keysym| {
if event.state() == KeyState::Pressed {
let mod_down = match compositor_mod {
CompositorMod::Super => mods.logo,
CompositorMod::Alt => mods.alt,
};
// FIXME: these don't work in the Russian layout. I guess I'll need to
// find a US keymap, then map keys somehow.
match keysym.modified_sym() {
keysyms::KEY_E if mod_down => {
FilterResult::Intercept(InputAction::Quit)
}
keysym @ keysyms::KEY_XF86Switch_VT_1
..=keysyms::KEY_XF86Switch_VT_12 => {
let vt = (keysym - keysyms::KEY_XF86Switch_VT_1 + 1) as i32;
FilterResult::Intercept(InputAction::ChangeVt(vt))
}
keysyms::KEY_t if mod_down => {
FilterResult::Intercept(InputAction::SpawnTerminal)
}
keysyms::KEY_q if mod_down => {
FilterResult::Intercept(InputAction::CloseWindow)
}
keysyms::KEY_f if mod_down => {
FilterResult::Intercept(InputAction::ToggleFullscreen)
}
_ => FilterResult::Forward,
}
action(comp_mod, keysym, *mods).into()
} else {
FilterResult::Forward
}
@@ -85,22 +126,27 @@ impl Niri {
if let Some(action) = action {
match action {
InputAction::Quit => {
Action::None => unreachable!(),
Action::Quit => {
info!("quitting because quit bind was pressed");
self.stop_signal.stop()
}
InputAction::ChangeVt(vt) => {
Action::ChangeVt(vt) => {
(*change_vt)(vt);
}
InputAction::SpawnTerminal => {
Action::SpawnTerminal => {
if let Err(err) = Command::new("alacritty").spawn() {
warn!("error spawning alacritty: {err}");
}
}
InputAction::CloseWindow => {
Action::CloseWindow => {
if let Some(focus) = self.seat.get_keyboard().unwrap().current_focus() {
// FIXME: is there a better way of doing this?
for window in self.space.elements() {
for window in self
.monitor_set
.workspaces()
.flat_map(|workspace| workspace.space.elements())
{
let found = Cell::new(false);
window.with_surfaces(|surface, _| {
if surface == &focus {
@@ -114,18 +160,22 @@ impl Niri {
}
}
}
InputAction::ToggleFullscreen => {
Action::ToggleFullscreen => {
if let Some(focus) = self.seat.get_keyboard().unwrap().current_focus() {
// FIXME: is there a better way of doing this?
let window = self.space.elements().find(|window| {
let found = Cell::new(false);
window.with_surfaces(|surface, _| {
if surface == &focus {
found.set(true);
}
let window = self
.monitor_set
.workspaces()
.flat_map(|workspace| workspace.space.elements())
.find(|window| {
let found = Cell::new(false);
window.with_surfaces(|surface, _| {
if surface == &focus {
found.set(true);
}
});
found.get()
});
found.get()
});
if let Some(window) = window {
let toplevel = window.toplevel().clone();
if toplevel
@@ -140,6 +190,78 @@ impl Niri {
}
}
}
Action::MoveLeft => {
self.monitor_set.move_left();
// FIXME: granular
self.queue_redraw_all();
}
Action::MoveRight => {
self.monitor_set.move_right();
// FIXME: granular
self.queue_redraw_all();
}
Action::MoveDown => {
self.monitor_set.move_down();
// FIXME: granular
self.queue_redraw_all();
}
Action::MoveUp => {
self.monitor_set.move_up();
// FIXME: granular
self.queue_redraw_all();
}
Action::FocusLeft => {
self.monitor_set.focus_left();
self.update_focus();
}
Action::FocusRight => {
self.monitor_set.focus_right();
self.update_focus();
}
Action::FocusDown => {
self.monitor_set.focus_down();
self.update_focus();
}
Action::FocusUp => {
self.monitor_set.focus_up();
self.update_focus();
}
Action::MoveToWorkspaceDown => {
self.monitor_set.move_to_workspace_down();
self.update_focus();
// FIXME: granular
self.queue_redraw_all();
}
Action::MoveToWorkspaceUp => {
self.monitor_set.move_to_workspace_up();
self.update_focus();
// FIXME: granular
self.queue_redraw_all();
}
Action::SwitchWorkspaceDown => {
self.monitor_set.switch_workspace_down();
self.update_focus();
// FIXME: granular
self.queue_redraw_all();
}
Action::SwitchWorkspaceUp => {
self.monitor_set.switch_workspace_up();
self.update_focus();
// FIXME: granular
self.queue_redraw_all();
}
Action::ConsumeIntoColumn => {
self.monitor_set.consume_into_column();
self.update_focus();
// FIXME: granular
self.queue_redraw_all();
}
Action::ExpelFromColumn => {
self.monitor_set.expel_from_column();
self.update_focus();
// FIXME: granular
self.queue_redraw_all();
}
}
}
}
@@ -147,22 +269,33 @@ impl Niri {
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.seat.get_pointer().unwrap();
let mut pointer_location = pointer.current_location();
let mut pos = pointer.current_location();
pointer_location += event.delta();
pos += event.delta();
let output = self.space.outputs().next().unwrap();
let output_geo = self.space.output_geometry(output).unwrap();
let mut min_x = i32::MAX;
let mut min_y = i32::MAX;
let mut max_x = 0;
let mut max_y = 0;
for output in self.global_space.outputs() {
// FIXME: smarter clamping.
let geom = self.global_space.output_geometry(output).unwrap();
min_x = min_x.min(geom.loc.x);
min_y = min_y.min(geom.loc.y);
max_x = max_x.max(geom.loc.x + geom.size.w);
max_y = max_y.max(geom.loc.y + geom.size.h);
}
pointer_location.x = pointer_location.x.clamp(0., output_geo.size.w as f64);
pointer_location.y = pointer_location.y.clamp(0., output_geo.size.h as f64);
pos.x = pos.x.clamp(min_x as f64, max_x as f64);
pos.y = pos.y.clamp(min_y as f64, max_y as f64);
let under = self.surface_under_and_global_space(pos);
let under = self.surface_under(pointer_location);
pointer.motion(
self,
under.clone(),
&MotionEvent {
location: pointer_location,
location: pos,
serial,
time: event.time_msec(),
},
@@ -179,12 +312,14 @@ impl Niri {
);
// Redraw to update the cursor position.
self.queue_redraw();
// FIXME: redraw only outputs overlapping the cursor.
self.queue_redraw_all();
}
InputEvent::PointerMotionAbsolute { event, .. } => {
let output = self.space.outputs().next().unwrap();
// FIXME: allow mapping tablet to different outputs.
let output = self.global_space.outputs().next().unwrap();
let output_geo = self.space.output_geometry(output).unwrap();
let output_geo = self.global_space.output_geometry(output).unwrap();
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
@@ -192,7 +327,7 @@ impl Niri {
let pointer = self.seat.get_pointer().unwrap();
let under = self.surface_under(pos);
let under = self.surface_under_and_global_space(pos);
pointer.motion(
self,
@@ -205,11 +340,11 @@ impl Niri {
);
// Redraw to update the cursor position.
self.queue_redraw();
// FIXME: redraw only outputs overlapping the cursor.
self.queue_redraw_all();
}
InputEvent::PointerButton { event, .. } => {
let pointer = self.seat.get_pointer().unwrap();
let keyboard = self.seat.get_keyboard().unwrap();
let serial = SERIAL_COUNTER.next_serial();
@@ -218,27 +353,16 @@ impl Niri {
let button_state = event.state();
if ButtonState::Pressed == button_state && !pointer.is_grabbed() {
if let Some((window, _loc)) = self
.space
.element_under(pointer.current_location())
.map(|(w, l)| (w.clone(), l))
if let Some((_space, window, _loc)) =
self.window_under(pointer.current_location())
{
self.space.raise_element(&window, true);
keyboard.set_focus(
self,
Some(window.toplevel().wl_surface().clone()),
serial,
);
self.space.elements().for_each(|window| {
window.toplevel().send_pending_configure();
});
self.monitor_set.activate_window(&window);
} else {
self.space.elements().for_each(|window| {
window.set_activated(false);
window.toplevel().send_pending_configure();
});
keyboard.set_focus(self, Option::<WlSurface>::None, serial);
let output = self.output_under_cursor().unwrap();
self.monitor_set.activate_output(&output);
}
self.update_focus();
};
pointer.button(
+1099
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -6,6 +6,7 @@ mod handlers;
mod backend;
mod grabs;
mod input;
mod layout;
mod niri;
mod tty;
mod winit;
@@ -108,7 +109,7 @@ fn main() {
let _span = tracy_client::span!("loop callback");
// These should be called periodically, before flushing the clients.
data.niri.space.refresh();
data.niri.monitor_set.refresh();
data.niri.popups.cleanup();
{
+205 -44
View File
@@ -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,
waiting_for_vblank: 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,
+165 -148
View File
@@ -1,10 +1,11 @@
use std::collections::{HashMap, HashSet};
use std::os::fd::FromRawFd;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use anyhow::anyhow;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
use smithay::backend::allocator::Fourcc;
use smithay::backend::allocator::{Format as DrmFormat, Fourcc};
use smithay::backend::drm::compositor::DrmCompositor;
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent};
use smithay::backend::egl::{EGLContext, EGLDisplay};
@@ -17,14 +18,12 @@ use smithay::backend::session::{Event as SessionEvent, Session};
use smithay::backend::udev::{self, UdevBackend, UdevEvent};
use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel};
use smithay::reexports::calloop::{LoopHandle, RegistrationToken};
use smithay::reexports::drm::control::connector::{
Interface as ConnectorInterface, State as ConnectorState,
};
use smithay::reexports::drm::control::{Device, ModeTypeFlags};
use smithay::reexports::drm::control::{connector, crtc, ModeTypeFlags};
use smithay::reexports::input::Libinput;
use smithay::reexports::nix::fcntl::OFlag;
use smithay::reexports::nix::libc::dev_t;
use smithay::utils::DeviceFd;
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
use smithay_drm_extras::edid::EdidInfo;
use crate::backend::Backend;
@@ -46,11 +45,19 @@ type GbmDrmCompositor =
struct OutputDevice {
id: dev_t,
path: PathBuf,
token: RegistrationToken,
drm: DrmDevice,
gbm: GbmDevice<DrmDeviceFd>,
gles: GlesRenderer,
drm_compositor: GbmDrmCompositor,
formats: HashSet<DrmFormat>,
drm_scanner: DrmScanner,
surfaces: HashMap<crtc::Handle, GbmDrmCompositor>,
}
#[derive(Debug, Clone, Copy)]
struct TtyOutputState {
device_id: dev_t,
crtc: crtc::Handle,
}
impl Backend for Tty {
@@ -65,6 +72,7 @@ impl Backend for Tty {
fn render(
&mut self,
niri: &mut Niri,
output: &Output,
elements: &[OutputRenderElements<
GlesRenderer,
WaylandSurfaceRenderElement<GlesRenderer>,
@@ -72,19 +80,26 @@ impl Backend for Tty {
) {
let _span = tracy_client::span!("Tty::render");
let output_device = self.output_device.as_mut().unwrap();
let drm_compositor = &mut output_device.drm_compositor;
let device = self.output_device.as_mut().unwrap();
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
let drm_compositor = device.surfaces.get_mut(&tty_state.crtc).unwrap();
match drm_compositor.render_frame::<_, _, GlesTexture>(
&mut output_device.gles,
&mut device.gles,
elements,
BACKGROUND_COLOR,
) {
Ok(res) => {
assert!(!res.needs_sync());
// debug!("{:?}", res);
if res.damage.is_some() {
match output_device.drm_compositor.queue_frame(()) {
Ok(()) => niri.waiting_for_vblank = true,
match drm_compositor.queue_frame(()) {
Ok(()) => {
niri.output_state
.get_mut(output)
.unwrap()
.waiting_for_vblank = true
}
Err(err) => {
error!("error queueing frame: {err}");
}
@@ -142,13 +157,15 @@ impl Tty {
if let Some(output_device) = &mut tty.output_device {
output_device.drm.activate();
if let Err(err) = output_device.drm_compositor.surface().reset_state() {
warn!("error resetting DRM surface state: {err}");
for drm_compositor in output_device.surfaces.values_mut() {
if let Err(err) = drm_compositor.surface().reset_state() {
warn!("error resetting DRM surface state: {err}");
}
drm_compositor.reset_buffers();
}
output_device.drm_compositor.reset_buffers();
}
niri.queue_redraw();
niri.queue_redraw_all();
}
}
})
@@ -166,7 +183,7 @@ impl Tty {
pub fn init(&mut self, niri: &mut Niri) {
let backend = UdevBackend::new(&self.session.seat()).unwrap();
for (device_id, path) in backend.device_list() {
if let Err(err) = self.device_added(device_id, path.to_owned(), niri) {
if let Err(err) = self.device_added(device_id, path, niri) {
warn!("error adding device: {err:?}");
}
}
@@ -178,24 +195,21 @@ impl Tty {
match event {
UdevEvent::Added { device_id, path } => {
if let Err(err) = tty.device_added(device_id, path, niri) {
if let Err(err) = tty.device_added(device_id, &path, niri) {
warn!("error adding device: {err:?}");
}
niri.queue_redraw();
}
UdevEvent::Changed { device_id } => tty.device_changed(device_id, niri),
UdevEvent::Removed { device_id } => tty.device_removed(device_id, niri),
}
})
.unwrap();
niri.queue_redraw();
}
fn device_added(
&mut self,
device_id: dev_t,
path: PathBuf,
path: &Path,
niri: &mut Niri,
) -> anyhow::Result<()> {
if path != self.primary_gpu_path {
@@ -207,7 +221,7 @@ impl Tty {
assert!(self.output_device.is_none());
let open_flags = OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
let fd = self.session.open(&path, open_flags)?;
let fd = self.session.open(path, open_flags)?;
let device_fd = unsafe { DrmDeviceFd::new(DeviceFd::from_raw_fd(fd)) };
let (drm, drm_notifier) = DrmDevice::new(device_fd.clone(), true)?;
@@ -219,23 +233,22 @@ impl Tty {
let mut gles = unsafe { GlesRenderer::new(egl_context)? };
gles.bind_wl_display(&niri.display_handle)?;
let drm_compositor = self.create_drm_compositor(&drm, &gbm, &gles, niri)?;
let token = niri
.event_loop
.insert_source(drm_notifier, move |event, metadata, data| {
let tty = data.tty.as_mut().unwrap();
match event {
DrmEvent::VBlank(_crtc) => {
DrmEvent::VBlank(crtc) => {
tracy_client::Client::running()
.unwrap()
.message("vblank", 0);
trace!("vblank {metadata:?}");
let output_device = tty.output_device.as_mut().unwrap();
let device = tty.output_device.as_mut().unwrap();
let drm_compositor = device.surfaces.get_mut(&crtc).unwrap();
// Mark the last frame as submitted.
if let Err(err) = output_device.drm_compositor.frame_submitted() {
if let Err(err) = drm_compositor.frame_submitted() {
error!("error marking frame as submitted: {err}");
}
@@ -244,103 +257,111 @@ impl Tty {
// .windows
// .mark_presented(&output_device.last_render_states, metadata);
data.niri.waiting_for_vblank = false;
data.niri.queue_redraw();
let output = data
.niri
.global_space
.outputs()
.find(|output| {
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
tty_state.device_id == device.id && tty_state.crtc == crtc
})
.unwrap()
.clone();
data.niri
.output_state
.get_mut(&output)
.unwrap()
.waiting_for_vblank = false;
data.niri.queue_redraw(output);
}
DrmEvent::Error(error) => error!("DRM error: {error}"),
};
})
.unwrap();
let formats = Bind::<Dmabuf>::supported_formats(&gles).unwrap_or_default();
self.output_device = Some(OutputDevice {
id: device_id,
path,
token,
drm,
gbm,
gles,
drm_compositor,
formats,
drm_scanner: DrmScanner::new(),
surfaces: HashMap::new(),
});
self.device_changed(device_id, niri);
Ok(())
}
fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) {
if let Some(output_device) = &self.output_device {
if output_device.id == device_id {
debug!("output device changed");
let Some(device) = &mut self.output_device else {
return;
};
if device.id != device_id {
return;
}
debug!("output device changed");
let path = output_device.path.clone();
self.device_removed(device_id, niri);
if let Err(err) = self.device_added(device_id, path, niri) {
warn!("error adding device: {err:?}");
for event in device.drm_scanner.scan_connectors(&device.drm) {
match event {
DrmScanEvent::Connected {
connector,
crtc: Some(crtc),
} => {
if let Err(err) = self.connector_connected(niri, connector, crtc) {
warn!("error connecting connector: {err:?}");
}
}
DrmScanEvent::Disconnected {
connector,
crtc: Some(crtc),
} => self.connector_disconnected(niri, connector, crtc),
_ => (),
}
}
}
fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) {
if let Some(mut output_device) = self.output_device.take() {
if output_device.id != device_id {
self.output_device = Some(output_device);
return;
}
// FIXME: remove wl_output.
niri.event_loop.remove(output_device.token);
niri.output = None;
output_device.gles.unbind_wl_display();
let Some(device) = self.output_device.take() else {
return;
};
if device.id != device_id {
// It wasn't the output device, put it back in.
self.output_device = Some(device);
return;
}
let crtcs: Vec<_> = device
.drm_scanner
.crtcs()
.map(|(info, crtc)| (info.clone(), crtc))
.collect();
for (connector, crtc) in crtcs {
self.connector_disconnected(niri, connector, crtc);
}
niri.event_loop.remove(device.token);
}
fn create_drm_compositor(
fn connector_connected(
&mut self,
drm: &DrmDevice,
gbm: &GbmDevice<DrmDeviceFd>,
gles: &GlesRenderer,
niri: &mut Niri,
) -> anyhow::Result<GbmDrmCompositor> {
let formats = Bind::<Dmabuf>::supported_formats(gles)
.ok_or_else(|| anyhow!("no supported formats"))?;
let resources = drm.resource_handles()?;
let mut connector = None;
let mut edp_connector = None;
resources
.connectors()
.iter()
.filter_map(|conn| match drm.get_connector(*conn, true) {
Ok(info) => Some(info),
Err(err) => {
warn!("error probing connector: {err}");
None
}
})
.inspect(|conn| {
debug!(
"connector: {}-{}, {:?}, {} modes",
conn.interface().as_str(),
conn.interface_id(),
conn.state(),
conn.modes().len(),
);
})
.filter(|conn| conn.state() == ConnectorState::Connected)
.for_each(|conn| {
connector = Some(conn.clone());
if conn.interface() == ConnectorInterface::EmbeddedDisplayPort {
edp_connector = Some(conn);
}
});
// Since we're only using one output at the moment, prefer eDP.
let connector = edp_connector
.or(connector)
.ok_or_else(|| anyhow!("no compatible connector"))?;
info!(
"picking connector: {}-{}",
connector: connector::Info,
crtc: crtc::Handle,
) -> anyhow::Result<()> {
let output_name = format!(
"{}-{}",
connector.interface().as_str(),
connector.interface_id(),
);
debug!("connecting connector: {output_name}");
let device = self.output_device.as_mut().unwrap();
let mut mode = connector.modes().get(0);
connector.modes().iter().for_each(|m| {
@@ -357,57 +378,20 @@ impl Tty {
}
});
let mode = mode.ok_or_else(|| anyhow!("no mode"))?;
info!("picking mode: {mode:?}");
debug!("picking mode: {mode:?}");
let surface = connector
.encoders()
.iter()
.filter_map(|enc| match drm.get_encoder(*enc) {
Ok(info) => Some(info),
Err(err) => {
warn!("error probing encoder: {err}");
None
}
})
.flat_map(|enc| {
// Get all CRTCs compatible with the encoder.
let mut crtcs = resources.filter_crtcs(enc.possible_crtcs());
// Sort by maximum number of overlay planes.
crtcs.sort_by_cached_key(|crtc| match drm.planes(crtc) {
Ok(planes) => -(planes.overlay.len() as isize),
Err(err) => {
warn!("error probing planes for CRTC: {err}");
0
}
});
crtcs
})
.find_map(
|crtc| match drm.create_surface(crtc, *mode, &[connector.handle()]) {
Ok(surface) => Some(surface),
Err(err) => {
warn!("error creating DRM surface: {err}");
None
}
},
);
let surface = surface.ok_or_else(|| anyhow!("no surface"))?;
let surface = device
.drm
.create_surface(crtc, *mode, &[connector.handle()])?;
// Create GBM allocator.
let gbm_flags = GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT;
let allocator = GbmAllocator::new(gbm.clone(), gbm_flags);
let allocator = GbmAllocator::new(device.gbm.clone(), gbm_flags);
// Update the output mode.
let (physical_width, physical_height) = connector.size().unwrap_or((0, 0));
let output_name = format!(
"{}-{}",
connector.interface().as_str(),
connector.interface_id(),
);
let (make, model) = EdidInfo::for_connector(drm, connector.handle())
let (make, model) = EdidInfo::for_connector(&device.drm, connector.handle())
.map(|info| (info.manufacturer, info.model))
.unwrap_or_else(|| ("Unknown".into(), "Unknown".into()));
@@ -424,25 +408,58 @@ impl Tty {
output.change_current_state(Some(wl_mode), None, None, Some((0, 0).into()));
output.set_preferred(wl_mode);
// FIXME: store this somewhere to remove on disconnect, etc.
let _global = output.create_global::<Niri>(&niri.display_handle);
niri.space.map_output(&output, (0, 0));
niri.output = Some(output.clone());
// windows.set_output();
output.user_data().insert_if_missing(|| TtyOutputState {
device_id: device.id,
crtc,
});
// Create the compositor.
let compositor = DrmCompositor::new(
OutputModeSource::Auto(output),
OutputModeSource::Auto(output.clone()),
surface,
None,
allocator,
gbm.clone(),
device.gbm.clone(),
SUPPORTED_COLOR_FORMATS,
formats,
drm.cursor_size(),
Some(gbm.clone()),
device.formats.clone(),
device.drm.cursor_size(),
Some(device.gbm.clone()),
)?;
Ok(compositor)
let res = device.surfaces.insert(crtc, compositor);
assert!(res.is_none(), "crtc must not have already existed");
niri.add_output(output.clone());
niri.queue_redraw(output);
Ok(())
}
fn connector_disconnected(
&mut self,
niri: &mut Niri,
connector: connector::Info,
crtc: crtc::Handle,
) {
debug!("disconnecting connector: {connector:?}");
let device = self.output_device.as_mut().unwrap();
if device.surfaces.remove(&crtc).is_none() {
debug!("crts wasn't enabled");
return;
}
let output = niri
.global_space
.outputs()
.find(|output| {
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
tty_state.device_id == device.id && tty_state.crtc == crtc
})
.unwrap()
.clone();
niri.remove_output(&output);
}
fn change_vt(&mut self, vt: i32) {
+13 -6
View File
@@ -8,6 +8,8 @@ use smithay::backend::winit::{self, WinitError, WinitEvent, WinitEventLoop, Wini
use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
use smithay::reexports::calloop::LoopHandle;
use smithay::reexports::winit::dpi::LogicalSize;
use smithay::reexports::winit::window::WindowBuilder;
use smithay::utils::{Rectangle, Transform};
use crate::backend::Backend;
@@ -34,6 +36,7 @@ impl Backend for Winit {
fn render(
&mut self,
_niri: &mut Niri,
_output: &Output,
elements: &[OutputRenderElements<
GlesRenderer,
WaylandSurfaceRenderElement<GlesRenderer>,
@@ -54,7 +57,11 @@ impl Backend for Winit {
impl Winit {
pub fn new(event_loop: LoopHandle<LoopData>) -> Self {
let (backend, winit_event_loop) = winit::init().unwrap();
let builder = WindowBuilder::new()
.with_inner_size(LogicalSize::new(1280.0, 800.0))
// .with_resizable(false)
.with_title("niri");
let (backend, winit_event_loop) = winit::init_from_builder(builder).unwrap();
let mode = Mode {
size: backend.window_size().physical_size,
@@ -98,9 +105,6 @@ impl Winit {
}
pub fn init(&mut self, niri: &mut Niri) {
let _global = self.output.create_global::<Niri>(&niri.display_handle);
niri.space.map_output(&self.output, (0, 0));
niri.output = Some(self.output.clone());
if let Err(err) = self
.backend
.renderer()
@@ -108,6 +112,7 @@ impl Winit {
{
warn!("error binding renderer wl_display: {err}");
}
niri.add_output(self.output.clone());
}
fn dispatch(&mut self, niri: &mut Niri) {
@@ -115,7 +120,7 @@ impl Winit {
.winit_event_loop
.dispatch_new_events(|event| match event {
WinitEvent::Resized { size, .. } => {
niri.output.as_ref().unwrap().change_current_state(
self.output.change_current_state(
Some(Mode {
size,
refresh: 60_000,
@@ -124,12 +129,13 @@ impl Winit {
None,
None,
);
niri.output_resized(self.output.clone());
}
WinitEvent::Input(event) => {
niri.process_input_event(&mut |_| (), CompositorMod::Alt, event)
}
WinitEvent::Focus(_) => (),
WinitEvent::Refresh => niri.queue_redraw(),
WinitEvent::Refresh => niri.queue_redraw(self.output.clone()),
});
// I want this to stop compiling if more errors are added.
@@ -137,6 +143,7 @@ impl Winit {
match res {
Err(WinitError::WindowClosed) => {
niri.stop_signal.stop();
niri.remove_output(&self.output);
}
Ok(()) => (),
}