mirror of
https://github.com/YaLTeR/niri.git
synced 2024-09-11 12:35:58 +03:00
Refactor everything, add initial tiling code
This commit is contained in:
parent
e02e35f9c6
commit
95c810c855
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "niri"
|
||||
version = "0.1.0"
|
||||
description = "A Wayland compositor"
|
||||
description = "A scrollable-tiling Wayland compositor"
|
||||
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
|
65
README.md
65
README.md
@ -1,12 +1,71 @@
|
||||
# niri
|
||||
|
||||
The beginnings of a Wayland compositor.
|
||||
The beginnings of a scrollable-tiling Wayland compositor.
|
||||
|
||||
## Status
|
||||
|
||||
Heavily work in progress.
|
||||
The occasional thing works, but likely is in a half-broken state.
|
||||
|
||||
## Idea
|
||||
|
||||
This section describes the goals I'm working towards.
|
||||
Many things don't work as written yet.
|
||||
|
||||
Niri implements scrollable tiling, heavily inspired by [PaperWM].
|
||||
Windows are arranged in columns on an infinite strip going to the right.
|
||||
Every column takes up as much height as possible, spread between its windows.
|
||||
|
||||
![](https://github.com/YaLTeR/niri/assets/1794388/b734da07-301a-452b-b201-d4789a3eca60)
|
||||
|
||||
With multiple monitors, every monitor has its own separate window strip.
|
||||
Windows can never "overflow" to an adjacent monitor.
|
||||
|
||||
This is one of the reasons that prompted me to try writing my own compositor.
|
||||
PaperWM is a solid implementation that I use every day, but, being a GNOME Shell extension, it has to work around Shell's global window coordinate space to prevent windows from overflowing.
|
||||
|
||||
Niri also has dynamic workspaces which work similar to GNOME Shell.
|
||||
Since windows go left-to-right horizontally, workspaces are arranged vertically.
|
||||
Every monitor has an independent set of workspaces, and there's always one empty workspace present all the way down.
|
||||
|
||||
Niri tries to preserve the workspace arrangement as much as possible upon disconnecting and connecting monitors.
|
||||
When a monitor disconnects, its workspaces will move to another monitor, but upon reconnection they will move back to the original monitor where it makes sense.
|
||||
|
||||
## Running
|
||||
|
||||
`cargo run -- -- alacritty`
|
||||
|
||||
Inside a desktop session, it will run in a window. On a TTY, it will run natively.
|
||||
Inside a desktop session, it will run in a window.
|
||||
On a TTY, it will run natively.
|
||||
|
||||
To exit when running on a TTY, press <kbd>Super</kbd>+<kbd>Shift</kbd>+<kbd>e</kbd>.
|
||||
To exit when running on a TTY, press <kbd>Super</kbd><kbd>Shift</kbd><kbd>E</kbd>.
|
||||
|
||||
## Hotkeys
|
||||
|
||||
When running on a TTY, the Mod key is <kbd>Super</kbd>.
|
||||
When running in a window, the Mod key is <kbd>Alt</kbd>.
|
||||
|
||||
The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kbd> will move the focused window or column there.
|
||||
|
||||
| Hotkey | Description |
|
||||
| ------ | ----------- |
|
||||
| <kbd>Mod</kbd><kbd>T</kbd> | Spawn `alacritty` |
|
||||
| <kbd>Mod</kbd><kbd>Q</kbd> | Close the focused window |
|
||||
| <kbd>Mod</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd>←</kbd> | Focus the window to the left |
|
||||
| <kbd>Mod</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>→</kbd> | Focus the window to the right |
|
||||
| <kbd>Mod</kbd><kbd>J</kbd> or <kbd>Mod</kbd><kbd>↓</kbd> | Focus the window below in a column |
|
||||
| <kbd>Mod</kbd><kbd>K</kbd> or <kbd>Mod</kbd><kbd>↑</kbd> | Focus the window above in a column |
|
||||
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>←</kbd> | Move the focused column to the left |
|
||||
| <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>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 |
|
||||
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>I</kbd> | Move the focused window to the workspace above |
|
||||
| <kbd>Mod</kbd><kbd>,</kbd> | Consume the window to the right into the focused column |
|
||||
| <kbd>Mod</kbd><kbd>.</kbd> | Expel the focused window into its own column |
|
||||
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>E</kbd> | Exit niri |
|
||||
|
||||
[PaperWM]: https://github.com/paperwm/PaperWM
|
||||
|
||||
|
@ -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>,
|
||||
|
@ -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(
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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| {
|
||||
|
278
src/input.rs
278
src/input.rs
@ -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
src/layout.rs
Normal file
1099
src/layout.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
||||
{
|
||||
|
249
src/niri.rs
249
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,
|
||||
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,
|
||||
|
313
src/tty.rs
313
src/tty.rs
@ -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) {
|
||||
|
19
src/winit.rs
19
src/winit.rs
@ -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(()) => (),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user