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]
|
[package]
|
||||||
name = "niri"
|
name = "niri"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A Wayland compositor"
|
description = "A scrollable-tiling Wayland compositor"
|
||||||
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
65
README.md
65
README.md
@ -1,12 +1,71 @@
|
|||||||
# niri
|
# 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
|
## Running
|
||||||
|
|
||||||
`cargo run -- -- alacritty`
|
`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::element::surface::WaylandSurfaceRenderElement;
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
|
use smithay::output::Output;
|
||||||
|
|
||||||
use crate::niri::OutputRenderElements;
|
use crate::niri::OutputRenderElements;
|
||||||
use crate::Niri;
|
use crate::Niri;
|
||||||
@ -10,6 +11,7 @@ pub trait Backend {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
niri: &mut Niri,
|
niri: &mut Niri,
|
||||||
|
output: &Output,
|
||||||
elements: &[OutputRenderElements<
|
elements: &[OutputRenderElements<
|
||||||
GlesRenderer,
|
GlesRenderer,
|
||||||
WaylandSurfaceRenderElement<GlesRenderer>,
|
WaylandSurfaceRenderElement<GlesRenderer>,
|
||||||
|
@ -5,6 +5,7 @@ use smithay::input::pointer::{
|
|||||||
};
|
};
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::utils::{Logical, Point};
|
use smithay::utils::{Logical, Point};
|
||||||
|
use smithay::wayland::seat::WaylandFocus;
|
||||||
|
|
||||||
use crate::Niri;
|
use crate::Niri;
|
||||||
|
|
||||||
@ -25,10 +26,13 @@ impl PointerGrab<Niri> for MoveSurfaceGrab {
|
|||||||
// While the grab is active, no client has pointer focus
|
// While the grab is active, no client has pointer focus
|
||||||
handle.motion(data, None, event);
|
handle.motion(data, None, event);
|
||||||
|
|
||||||
let delta = event.location - self.start_data.location;
|
// let delta = event.location - self.start_data.location;
|
||||||
let new_location = self.initial_window_location.to_f64() + delta;
|
// let new_location = self.initial_window_location.to_f64() + delta;
|
||||||
data.space
|
// let (window, space) = data
|
||||||
.map_element(self.window.clone(), new_location.to_i32_round(), true);
|
// .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(
|
fn relative_motion(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use smithay::desktop::{Space, Window};
|
use smithay::desktop::Window;
|
||||||
use smithay::input::pointer::{
|
use smithay::input::pointer::{
|
||||||
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
||||||
PointerInnerHandle, RelativeMotionEvent,
|
PointerInnerHandle, RelativeMotionEvent,
|
||||||
@ -241,48 +241,48 @@ impl ResizeSurfaceState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should be called on `WlSurface::commit`
|
pub fn handle_commit(window: &Window) -> Option<()> {
|
||||||
pub fn handle_commit(space: &mut Space<Window>, surface: &WlSurface) -> Option<()> {
|
// FIXME
|
||||||
let window = space
|
let surface = window.toplevel().wl_surface();
|
||||||
.elements()
|
ResizeSurfaceState::with(surface, |state| {
|
||||||
.find(|w| w.toplevel().wl_surface() == surface)
|
state.commit();
|
||||||
.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()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(new_x) = new_loc.x {
|
// let mut window_loc = space.element_location(&window)?;
|
||||||
window_loc.x = new_x;
|
// let geometry = window.geometry();
|
||||||
}
|
|
||||||
if let Some(new_y) = new_loc.y {
|
|
||||||
window_loc.y = new_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_loc.x.is_some() || new_loc.y.is_some() {
|
// let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with(surface, |state| {
|
||||||
// If TOP or LEFT side of the window got resized, we have to move it
|
// state
|
||||||
space.map_element(window, window_loc, false);
|
// .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(())
|
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_buffer;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::reexports::wayland_server::Client;
|
use smithay::reexports::wayland_server::Client;
|
||||||
@ -9,6 +12,7 @@ use smithay::wayland::compositor::{
|
|||||||
use smithay::wayland::shm::{ShmHandler, ShmState};
|
use smithay::wayland::shm::{ShmHandler, ShmState};
|
||||||
use smithay::{delegate_compositor, delegate_shm};
|
use smithay::{delegate_compositor, delegate_shm};
|
||||||
|
|
||||||
|
use super::xdg_shell;
|
||||||
use crate::grabs::resize_grab;
|
use crate::grabs::resize_grab;
|
||||||
use crate::niri::ClientState;
|
use crate::niri::ClientState;
|
||||||
use crate::Niri;
|
use crate::Niri;
|
||||||
@ -28,24 +32,95 @@ impl CompositorHandler for Niri {
|
|||||||
.message("client commit", 0);
|
.message("client commit", 0);
|
||||||
|
|
||||||
on_commit_buffer_handler::<Self>(surface);
|
on_commit_buffer_handler::<Self>(surface);
|
||||||
if !is_sync_subsurface(surface) {
|
|
||||||
let mut root = surface.clone();
|
if is_sync_subsurface(surface) {
|
||||||
while let Some(parent) = get_parent(&root) {
|
return;
|
||||||
root = parent;
|
}
|
||||||
|
|
||||||
|
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
|
// This is a commit of a previously-mapped root or a non-toplevel root.
|
||||||
.elements()
|
if let Some((window, space)) = self.monitor_set.find_window_and_space(surface) {
|
||||||
.find(|w| w.toplevel().wl_surface() == &root)
|
// This is a commit of a previously-mapped toplevel.
|
||||||
{
|
let output = space.outputs().next().unwrap().clone();
|
||||||
|
|
||||||
window.on_commit();
|
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);
|
// This is a commit of a non-toplevel root.
|
||||||
resize_grab::handle_commit(&mut self.space, surface);
|
}
|
||||||
|
|
||||||
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::desktop::{PopupKind, Window};
|
||||||
use smithay::input::pointer::{Focus, GrabStartData as PointerGrabStartData};
|
use smithay::input::pointer::{Focus, GrabStartData as PointerGrabStartData};
|
||||||
use smithay::input::Seat;
|
use smithay::input::Seat;
|
||||||
use smithay::output::Output;
|
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
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_surface::WlSurface;
|
||||||
use smithay::reexports::wayland_server::protocol::{wl_output, wl_seat};
|
use smithay::reexports::wayland_server::protocol::{wl_output, wl_seat};
|
||||||
use smithay::reexports::wayland_server::Resource;
|
use smithay::reexports::wayland_server::Resource;
|
||||||
use smithay::utils::{Rectangle, Serial};
|
use smithay::utils::{Rectangle, Serial};
|
||||||
use smithay::wayland::compositor::with_states;
|
use smithay::wayland::compositor::with_states;
|
||||||
use smithay::wayland::seat::WaylandFocus;
|
|
||||||
use smithay::wayland::shell::xdg::{
|
use smithay::wayland::shell::xdg::{
|
||||||
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
|
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
|
||||||
XdgShellState, XdgToplevelSurfaceData,
|
XdgShellState, XdgToplevelSurfaceData,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::grabs::{MoveSurfaceGrab, ResizeSurfaceGrab};
|
use crate::grabs::{MoveSurfaceGrab, ResizeSurfaceGrab};
|
||||||
|
use crate::layout::MonitorSet;
|
||||||
use crate::Niri;
|
use crate::Niri;
|
||||||
|
|
||||||
impl XdgShellHandler for Niri {
|
impl XdgShellHandler for Niri {
|
||||||
@ -24,8 +23,16 @@ impl XdgShellHandler for Niri {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||||
|
let wl_surface = surface.wl_surface().clone();
|
||||||
let window = Window::new(surface);
|
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) {
|
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) {
|
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let pointer = seat.get_pointer().unwrap();
|
||||||
|
|
||||||
let window = self
|
let (window, space) = self.monitor_set.find_window_and_space(wl_surface).unwrap();
|
||||||
.space
|
let initial_window_location = space.element_location(&window).unwrap();
|
||||||
.elements()
|
|
||||||
.find(|w| w.toplevel().wl_surface() == wl_surface)
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
let initial_window_location = self.space.element_location(&window).unwrap();
|
|
||||||
|
|
||||||
let grab = MoveSurfaceGrab {
|
let grab = MoveSurfaceGrab {
|
||||||
start_data,
|
start_data,
|
||||||
window,
|
window: window.clone(),
|
||||||
initial_window_location,
|
initial_window_location,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,13 +83,8 @@ impl XdgShellHandler for Niri {
|
|||||||
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
|
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let pointer = seat.get_pointer().unwrap();
|
||||||
|
|
||||||
let window = self
|
let (window, space) = self.monitor_set.find_window_and_space(wl_surface).unwrap();
|
||||||
.space
|
let initial_window_location = space.element_location(&window).unwrap();
|
||||||
.elements()
|
|
||||||
.find(|w| w.toplevel().wl_surface() == wl_surface)
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
let initial_window_location = self.space.element_location(&window).unwrap();
|
|
||||||
let initial_window_size = window.geometry().size;
|
let initial_window_size = window.geometry().size;
|
||||||
|
|
||||||
surface.with_pending_state(|state| {
|
surface.with_pending_state(|state| {
|
||||||
@ -98,7 +95,7 @@ impl XdgShellHandler for Niri {
|
|||||||
|
|
||||||
let grab = ResizeSurfaceGrab::start(
|
let grab = ResizeSurfaceGrab::start(
|
||||||
start_data,
|
start_data,
|
||||||
window,
|
window.clone(),
|
||||||
edges.into(),
|
edges.into(),
|
||||||
Rectangle::from_loc_and_size(initial_window_location, initial_window_size),
|
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) {
|
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) {
|
fn maximize_request(&mut self, surface: ToplevelSurface) {
|
||||||
@ -135,23 +132,17 @@ impl XdgShellHandler for Niri {
|
|||||||
.capabilities
|
.capabilities
|
||||||
.contains(xdg_toplevel::WmCapabilities::Maximize)
|
.contains(xdg_toplevel::WmCapabilities::Maximize)
|
||||||
{
|
{
|
||||||
let wl_surface = surface.wl_surface();
|
// let wl_surface = surface.wl_surface();
|
||||||
let window = self
|
// let (window, space) = self.monitor_set.find_window_and_space(wl_surface).unwrap();
|
||||||
.space
|
// let geometry = space
|
||||||
.elements()
|
// .output_geometry(space.outputs().next().unwrap())
|
||||||
.find(|w| w.toplevel().wl_surface() == wl_surface)
|
// .unwrap();
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
let geometry = self
|
|
||||||
.space
|
|
||||||
.output_geometry(self.output.as_ref().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
surface.with_pending_state(|state| {
|
// surface.with_pending_state(|state| {
|
||||||
state.states.set(xdg_toplevel::State::Maximized);
|
// state.states.set(xdg_toplevel::State::Maximized);
|
||||||
state.size = Some(geometry.size);
|
// state.size = Some(geometry.size);
|
||||||
});
|
// });
|
||||||
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,
|
// The protocol demands us to always reply with a configure,
|
||||||
@ -185,44 +176,32 @@ impl XdgShellHandler for Niri {
|
|||||||
.capabilities
|
.capabilities
|
||||||
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
|
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
|
||||||
{
|
{
|
||||||
// NOTE: This is only one part of the solution. We can set the
|
// // 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
|
// // location and configure size here, but the surface should be rendered fullscreen
|
||||||
// independently from its buffer size
|
// // independently from its buffer size
|
||||||
let wl_surface = surface.wl_surface();
|
// let wl_surface = surface.wl_surface();
|
||||||
|
|
||||||
let output = wl_output
|
// let output = wl_output
|
||||||
.as_ref()
|
// .as_ref()
|
||||||
.and_then(Output::from_resource)
|
// .and_then(Output::from_resource)
|
||||||
.or_else(|| {
|
// .or_else(|| {
|
||||||
let w = self
|
// self.monitor_set
|
||||||
.space
|
// .find_window_and_space(wl_surface)
|
||||||
.elements()
|
// .and_then(|(_window, space)| space.outputs().next().cloned())
|
||||||
.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())
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(output) = output {
|
// if let Some(output) = output {
|
||||||
let geometry = self.space.output_geometry(&output).unwrap();
|
// 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| {
|
// surface.with_pending_state(|state| {
|
||||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
// state.states.set(xdg_toplevel::State::Fullscreen);
|
||||||
state.size = Some(geometry.size);
|
// state.size = Some(geometry.size);
|
||||||
});
|
// });
|
||||||
|
|
||||||
let window = self
|
// space.map_element(window.clone(), geometry.loc, true);
|
||||||
.space
|
// }
|
||||||
.elements()
|
|
||||||
.find(|w| w.toplevel().wl_surface() == wl_surface)
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
self.space.map_element(window, geometry.loc, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The protocol demands us to always reply with a configure,
|
// The protocol demands us to always reply with a configure,
|
||||||
@ -247,12 +226,25 @@ impl XdgShellHandler for Niri {
|
|||||||
surface.send_pending_configure();
|
surface.send_pending_configure();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toplevel_destroyed(&mut self, _surface: ToplevelSurface) {
|
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
|
||||||
self.queue_redraw();
|
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) {
|
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)
|
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 {
|
impl Niri {
|
||||||
/// Should be called on `WlSurface::commit`
|
/// 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);
|
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) {
|
if let Some(popup) = self.popups.find_popup(surface) {
|
||||||
let PopupKind::Xdg(ref popup) = popup;
|
let PopupKind::Xdg(ref popup) = popup;
|
||||||
let initial_configure_sent = with_states(surface, |states| {
|
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,
|
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
|
||||||
KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
|
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::input::pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent};
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
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::utils::SERIAL_COUNTER;
|
||||||
use smithay::wayland::shell::xdg::XdgShellHandler;
|
use smithay::wayland::shell::xdg::XdgShellHandler;
|
||||||
|
|
||||||
use crate::niri::Niri;
|
use crate::niri::Niri;
|
||||||
|
|
||||||
enum InputAction {
|
enum Action {
|
||||||
|
None,
|
||||||
Quit,
|
Quit,
|
||||||
ChangeVt(i32),
|
ChangeVt(i32),
|
||||||
SpawnTerminal,
|
SpawnTerminal,
|
||||||
CloseWindow,
|
CloseWindow,
|
||||||
ToggleFullscreen,
|
ToggleFullscreen,
|
||||||
|
FocusLeft,
|
||||||
|
FocusRight,
|
||||||
|
FocusDown,
|
||||||
|
FocusUp,
|
||||||
|
MoveLeft,
|
||||||
|
MoveRight,
|
||||||
|
MoveDown,
|
||||||
|
MoveUp,
|
||||||
|
ConsumeIntoColumn,
|
||||||
|
ExpelFromColumn,
|
||||||
|
SwitchWorkspaceDown,
|
||||||
|
SwitchWorkspaceUp,
|
||||||
|
MoveToWorkspaceDown,
|
||||||
|
MoveToWorkspaceUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CompositorMod {
|
pub enum CompositorMod {
|
||||||
@ -27,11 +41,64 @@ pub enum CompositorMod {
|
|||||||
Alt,
|
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 {
|
impl Niri {
|
||||||
pub fn process_input_event<I: InputBackend>(
|
pub fn process_input_event<I: InputBackend>(
|
||||||
&mut self,
|
&mut self,
|
||||||
change_vt: &mut dyn FnMut(i32),
|
change_vt: &mut dyn FnMut(i32),
|
||||||
compositor_mod: CompositorMod,
|
comp_mod: CompositorMod,
|
||||||
event: InputEvent<I>,
|
event: InputEvent<I>,
|
||||||
) {
|
) {
|
||||||
let _span = tracy_client::span!("process_input_event");
|
let _span = tracy_client::span!("process_input_event");
|
||||||
@ -50,33 +117,7 @@ impl Niri {
|
|||||||
time,
|
time,
|
||||||
|_, mods, keysym| {
|
|_, mods, keysym| {
|
||||||
if event.state() == KeyState::Pressed {
|
if event.state() == KeyState::Pressed {
|
||||||
let mod_down = match compositor_mod {
|
action(comp_mod, keysym, *mods).into()
|
||||||
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,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
FilterResult::Forward
|
FilterResult::Forward
|
||||||
}
|
}
|
||||||
@ -85,22 +126,27 @@ impl Niri {
|
|||||||
|
|
||||||
if let Some(action) = action {
|
if let Some(action) = action {
|
||||||
match action {
|
match action {
|
||||||
InputAction::Quit => {
|
Action::None => unreachable!(),
|
||||||
|
Action::Quit => {
|
||||||
info!("quitting because quit bind was pressed");
|
info!("quitting because quit bind was pressed");
|
||||||
self.stop_signal.stop()
|
self.stop_signal.stop()
|
||||||
}
|
}
|
||||||
InputAction::ChangeVt(vt) => {
|
Action::ChangeVt(vt) => {
|
||||||
(*change_vt)(vt);
|
(*change_vt)(vt);
|
||||||
}
|
}
|
||||||
InputAction::SpawnTerminal => {
|
Action::SpawnTerminal => {
|
||||||
if let Err(err) = Command::new("alacritty").spawn() {
|
if let Err(err) = Command::new("alacritty").spawn() {
|
||||||
warn!("error spawning alacritty: {err}");
|
warn!("error spawning alacritty: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputAction::CloseWindow => {
|
Action::CloseWindow => {
|
||||||
if let Some(focus) = self.seat.get_keyboard().unwrap().current_focus() {
|
if let Some(focus) = self.seat.get_keyboard().unwrap().current_focus() {
|
||||||
// FIXME: is there a better way of doing this?
|
// 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);
|
let found = Cell::new(false);
|
||||||
window.with_surfaces(|surface, _| {
|
window.with_surfaces(|surface, _| {
|
||||||
if surface == &focus {
|
if surface == &focus {
|
||||||
@ -114,18 +160,22 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputAction::ToggleFullscreen => {
|
Action::ToggleFullscreen => {
|
||||||
if let Some(focus) = self.seat.get_keyboard().unwrap().current_focus() {
|
if let Some(focus) = self.seat.get_keyboard().unwrap().current_focus() {
|
||||||
// FIXME: is there a better way of doing this?
|
// FIXME: is there a better way of doing this?
|
||||||
let window = self.space.elements().find(|window| {
|
let window = self
|
||||||
let found = Cell::new(false);
|
.monitor_set
|
||||||
window.with_surfaces(|surface, _| {
|
.workspaces()
|
||||||
if surface == &focus {
|
.flat_map(|workspace| workspace.space.elements())
|
||||||
found.set(true);
|
.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 {
|
if let Some(window) = window {
|
||||||
let toplevel = window.toplevel().clone();
|
let toplevel = window.toplevel().clone();
|
||||||
if toplevel
|
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 serial = SERIAL_COUNTER.next_serial();
|
||||||
|
|
||||||
let pointer = self.seat.get_pointer().unwrap();
|
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 mut min_x = i32::MAX;
|
||||||
let output_geo = self.space.output_geometry(output).unwrap();
|
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);
|
pos.x = pos.x.clamp(min_x as f64, max_x as f64);
|
||||||
pointer_location.y = pointer_location.y.clamp(0., output_geo.size.h 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(
|
pointer.motion(
|
||||||
self,
|
self,
|
||||||
under.clone(),
|
under.clone(),
|
||||||
&MotionEvent {
|
&MotionEvent {
|
||||||
location: pointer_location,
|
location: pos,
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
@ -179,12 +312,14 @@ impl Niri {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Redraw to update the cursor position.
|
// Redraw to update the cursor position.
|
||||||
self.queue_redraw();
|
// FIXME: redraw only outputs overlapping the cursor.
|
||||||
|
self.queue_redraw_all();
|
||||||
}
|
}
|
||||||
InputEvent::PointerMotionAbsolute { event, .. } => {
|
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();
|
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 pointer = self.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
let under = self.surface_under(pos);
|
let under = self.surface_under_and_global_space(pos);
|
||||||
|
|
||||||
pointer.motion(
|
pointer.motion(
|
||||||
self,
|
self,
|
||||||
@ -205,11 +340,11 @@ impl Niri {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Redraw to update the cursor position.
|
// Redraw to update the cursor position.
|
||||||
self.queue_redraw();
|
// FIXME: redraw only outputs overlapping the cursor.
|
||||||
|
self.queue_redraw_all();
|
||||||
}
|
}
|
||||||
InputEvent::PointerButton { event, .. } => {
|
InputEvent::PointerButton { event, .. } => {
|
||||||
let pointer = self.seat.get_pointer().unwrap();
|
let pointer = self.seat.get_pointer().unwrap();
|
||||||
let keyboard = self.seat.get_keyboard().unwrap();
|
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
|
||||||
@ -218,27 +353,16 @@ impl Niri {
|
|||||||
let button_state = event.state();
|
let button_state = event.state();
|
||||||
|
|
||||||
if ButtonState::Pressed == button_state && !pointer.is_grabbed() {
|
if ButtonState::Pressed == button_state && !pointer.is_grabbed() {
|
||||||
if let Some((window, _loc)) = self
|
if let Some((_space, window, _loc)) =
|
||||||
.space
|
self.window_under(pointer.current_location())
|
||||||
.element_under(pointer.current_location())
|
|
||||||
.map(|(w, l)| (w.clone(), l))
|
|
||||||
{
|
{
|
||||||
self.space.raise_element(&window, true);
|
self.monitor_set.activate_window(&window);
|
||||||
keyboard.set_focus(
|
|
||||||
self,
|
|
||||||
Some(window.toplevel().wl_surface().clone()),
|
|
||||||
serial,
|
|
||||||
);
|
|
||||||
self.space.elements().for_each(|window| {
|
|
||||||
window.toplevel().send_pending_configure();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
self.space.elements().for_each(|window| {
|
let output = self.output_under_cursor().unwrap();
|
||||||
window.set_activated(false);
|
self.monitor_set.activate_output(&output);
|
||||||
window.toplevel().send_pending_configure();
|
|
||||||
});
|
|
||||||
keyboard.set_focus(self, Option::<WlSurface>::None, serial);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.update_focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
pointer.button(
|
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 backend;
|
||||||
mod grabs;
|
mod grabs;
|
||||||
mod input;
|
mod input;
|
||||||
|
mod layout;
|
||||||
mod niri;
|
mod niri;
|
||||||
mod tty;
|
mod tty;
|
||||||
mod winit;
|
mod winit;
|
||||||
@ -108,7 +109,7 @@ fn main() {
|
|||||||
let _span = tracy_client::span!("loop callback");
|
let _span = tracy_client::span!("loop callback");
|
||||||
|
|
||||||
// These should be called periodically, before flushing the clients.
|
// These should be called periodically, before flushing the clients.
|
||||||
data.niri.space.refresh();
|
data.niri.monitor_set.refresh();
|
||||||
data.niri.popups.cleanup();
|
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::os::unix::io::AsRawFd;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use smithay::backend::renderer::element::render_elements;
|
|
||||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||||
|
use smithay::backend::renderer::element::{render_elements, RenderElement};
|
||||||
use smithay::backend::renderer::ImportAll;
|
use smithay::backend::renderer::ImportAll;
|
||||||
use smithay::desktop::space::{space_render_elements, SpaceRenderElements};
|
use smithay::desktop::space::{space_render_elements, SpaceRenderElements};
|
||||||
use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};
|
use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};
|
||||||
@ -11,12 +12,14 @@ use smithay::input::keyboard::XkbConfig;
|
|||||||
use smithay::input::{Seat, SeatState};
|
use smithay::input::{Seat, SeatState};
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
use smithay::reexports::calloop::generic::Generic;
|
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_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::protocol::wl_surface::WlSurface;
|
||||||
use smithay::reexports::wayland_server::{Display, DisplayHandle};
|
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::compositor::{CompositorClientState, CompositorState};
|
||||||
use smithay::wayland::data_device::DataDeviceState;
|
use smithay::wayland::data_device::DataDeviceState;
|
||||||
use smithay::wayland::output::OutputManagerState;
|
use smithay::wayland::output::OutputManagerState;
|
||||||
@ -25,6 +28,7 @@ use smithay::wayland::shm::ShmState;
|
|||||||
use smithay::wayland::socket::ListeningSocketSource;
|
use smithay::wayland::socket::ListeningSocketSource;
|
||||||
|
|
||||||
use crate::backend::Backend;
|
use crate::backend::Backend;
|
||||||
|
use crate::layout::MonitorSet;
|
||||||
use crate::LoopData;
|
use crate::LoopData;
|
||||||
|
|
||||||
pub struct Niri {
|
pub struct Niri {
|
||||||
@ -33,7 +37,18 @@ pub struct Niri {
|
|||||||
pub stop_signal: LoopSignal,
|
pub stop_signal: LoopSignal,
|
||||||
pub display_handle: DisplayHandle,
|
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.
|
// Smithay state.
|
||||||
pub compositor_state: CompositorState,
|
pub compositor_state: CompositorState,
|
||||||
@ -45,13 +60,17 @@ pub struct Niri {
|
|||||||
pub popups: PopupManager,
|
pub popups: PopupManager,
|
||||||
|
|
||||||
pub seat: Seat<Self>,
|
pub seat: Seat<Self>,
|
||||||
pub output: Option<Output>,
|
|
||||||
|
|
||||||
pub pointer_buffer: SolidColorBuffer,
|
pub pointer_buffer: SolidColorBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
// Set to `true` if there's a redraw queued on the event loop. Reset to `false` in redraw()
|
pub struct OutputState {
|
||||||
// which means that you cannot queue more than one redraw at once.
|
pub global: GlobalId,
|
||||||
pub redraw_queued: bool,
|
// 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,
|
pub waiting_for_vblank: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,8 +109,6 @@ impl Niri {
|
|||||||
seat.add_keyboard(xkb, 400, 30).unwrap();
|
seat.add_keyboard(xkb, 400, 30).unwrap();
|
||||||
seat.add_pointer();
|
seat.add_pointer();
|
||||||
|
|
||||||
let space = Space::default();
|
|
||||||
|
|
||||||
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
||||||
let socket_name = socket_source.socket_name().to_os_string();
|
let socket_name = socket_source.socket_name().to_os_string();
|
||||||
event_loop
|
event_loop
|
||||||
@ -130,7 +147,10 @@ impl Niri {
|
|||||||
stop_signal,
|
stop_signal,
|
||||||
display_handle,
|
display_handle,
|
||||||
|
|
||||||
space,
|
monitor_set: MonitorSet::new(),
|
||||||
|
global_space: Space::default(),
|
||||||
|
output_state: HashMap::new(),
|
||||||
|
unmapped_windows: HashMap::new(),
|
||||||
|
|
||||||
compositor_state,
|
compositor_state,
|
||||||
xdg_shell_state,
|
xdg_shell_state,
|
||||||
@ -141,60 +161,188 @@ impl Niri {
|
|||||||
popups: PopupManager::default(),
|
popups: PopupManager::default(),
|
||||||
|
|
||||||
seat,
|
seat,
|
||||||
output: None,
|
|
||||||
pointer_buffer,
|
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(
|
pub fn surface_under(
|
||||||
&self,
|
&mut self,
|
||||||
pos: Point<f64, Logical>,
|
pos: Point<f64, Logical>,
|
||||||
) -> Option<(WlSurface, Point<i32, Logical>)> {
|
) -> Option<(WlSurface, Point<i32, Logical>)> {
|
||||||
self.space
|
let (output, pos_within_output) = self.output_under(pos)?;
|
||||||
.element_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)| {
|
.and_then(|(window, location)| {
|
||||||
window
|
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))
|
.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.
|
/// Schedules an immediate redraw if one is not already scheduled.
|
||||||
pub fn queue_redraw(&mut self) {
|
pub fn queue_redraw(&mut self, output: Output) {
|
||||||
if self.redraw_queued || self.waiting_for_vblank {
|
let state = self.output_state.get_mut(&output).unwrap();
|
||||||
|
|
||||||
|
if state.queued_redraw.is_some() || state.waiting_for_vblank {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.redraw_queued = true;
|
|
||||||
|
|
||||||
// Timer::immediate() adds a millisecond of delay for some reason.
|
// 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
|
// 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 {
|
let backend: &mut dyn Backend = if let Some(tty) = &mut data.tty {
|
||||||
tty
|
tty
|
||||||
} else {
|
} else {
|
||||||
data.winit.as_mut().unwrap()
|
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 _span = tracy_client::span!("redraw");
|
||||||
|
let state = self.output_state.get_mut(output).unwrap();
|
||||||
|
|
||||||
assert!(self.redraw_queued);
|
assert!(state.queued_redraw.take().is_some());
|
||||||
assert!(!self.waiting_for_vblank);
|
assert!(!state.waiting_for_vblank);
|
||||||
self.redraw_queued = false;
|
|
||||||
|
|
||||||
let elements = space_render_elements(
|
let space = &self.monitor_set.workspace_for_output(output).unwrap().space;
|
||||||
backend.renderer(),
|
let elements = space_render_elements(backend.renderer(), [space], output, 1.).unwrap();
|
||||||
[&self.space],
|
|
||||||
self.output.as_ref().unwrap(),
|
let output_pos = self.global_space.output_geometry(output).unwrap().loc;
|
||||||
1.,
|
let pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64();
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut elements: Vec<_> = elements
|
let mut elements: Vec<_> = elements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -204,20 +352,16 @@ impl Niri {
|
|||||||
0,
|
0,
|
||||||
OutputRenderElements::Pointer(SolidColorRenderElement::from_buffer(
|
OutputRenderElements::Pointer(SolidColorRenderElement::from_buffer(
|
||||||
&self.pointer_buffer,
|
&self.pointer_buffer,
|
||||||
self.seat
|
pointer_pos.to_physical_precise_round(1.),
|
||||||
.get_pointer()
|
|
||||||
.unwrap()
|
|
||||||
.current_location()
|
|
||||||
.to_physical_precise_round(1.),
|
|
||||||
1.,
|
1.,
|
||||||
1.,
|
1.,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
backend.render(self, &elements);
|
backend.render(self, output, &elements);
|
||||||
|
|
||||||
let output = self.output.as_ref().unwrap();
|
let space = &self.monitor_set.workspace_for_output(output).unwrap().space;
|
||||||
self.space.elements().for_each(|window| {
|
space.elements().for_each(|window| {
|
||||||
window.send_frame(
|
window.send_frame(
|
||||||
output,
|
output,
|
||||||
self.start_time.elapsed(),
|
self.start_time.elapsed(),
|
||||||
@ -234,6 +378,23 @@ render_elements! {
|
|||||||
Pointer = SolidColorRenderElement,
|
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)]
|
#[derive(Default)]
|
||||||
pub struct ClientState {
|
pub struct ClientState {
|
||||||
pub compositor_state: CompositorClientState,
|
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::os::fd::FromRawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||||
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
|
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::compositor::DrmCompositor;
|
||||||
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent};
|
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent};
|
||||||
use smithay::backend::egl::{EGLContext, EGLDisplay};
|
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::backend::udev::{self, UdevBackend, UdevEvent};
|
||||||
use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel};
|
use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel};
|
||||||
use smithay::reexports::calloop::{LoopHandle, RegistrationToken};
|
use smithay::reexports::calloop::{LoopHandle, RegistrationToken};
|
||||||
use smithay::reexports::drm::control::connector::{
|
use smithay::reexports::drm::control::{connector, crtc, ModeTypeFlags};
|
||||||
Interface as ConnectorInterface, State as ConnectorState,
|
|
||||||
};
|
|
||||||
use smithay::reexports::drm::control::{Device, ModeTypeFlags};
|
|
||||||
use smithay::reexports::input::Libinput;
|
use smithay::reexports::input::Libinput;
|
||||||
use smithay::reexports::nix::fcntl::OFlag;
|
use smithay::reexports::nix::fcntl::OFlag;
|
||||||
use smithay::reexports::nix::libc::dev_t;
|
use smithay::reexports::nix::libc::dev_t;
|
||||||
use smithay::utils::DeviceFd;
|
use smithay::utils::DeviceFd;
|
||||||
|
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
|
||||||
use smithay_drm_extras::edid::EdidInfo;
|
use smithay_drm_extras::edid::EdidInfo;
|
||||||
|
|
||||||
use crate::backend::Backend;
|
use crate::backend::Backend;
|
||||||
@ -46,11 +45,19 @@ type GbmDrmCompositor =
|
|||||||
|
|
||||||
struct OutputDevice {
|
struct OutputDevice {
|
||||||
id: dev_t,
|
id: dev_t,
|
||||||
path: PathBuf,
|
|
||||||
token: RegistrationToken,
|
token: RegistrationToken,
|
||||||
drm: DrmDevice,
|
drm: DrmDevice,
|
||||||
|
gbm: GbmDevice<DrmDeviceFd>,
|
||||||
gles: GlesRenderer,
|
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 {
|
impl Backend for Tty {
|
||||||
@ -65,6 +72,7 @@ impl Backend for Tty {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
niri: &mut Niri,
|
niri: &mut Niri,
|
||||||
|
output: &Output,
|
||||||
elements: &[OutputRenderElements<
|
elements: &[OutputRenderElements<
|
||||||
GlesRenderer,
|
GlesRenderer,
|
||||||
WaylandSurfaceRenderElement<GlesRenderer>,
|
WaylandSurfaceRenderElement<GlesRenderer>,
|
||||||
@ -72,19 +80,26 @@ impl Backend for Tty {
|
|||||||
) {
|
) {
|
||||||
let _span = tracy_client::span!("Tty::render");
|
let _span = tracy_client::span!("Tty::render");
|
||||||
|
|
||||||
let output_device = self.output_device.as_mut().unwrap();
|
let device = self.output_device.as_mut().unwrap();
|
||||||
let drm_compositor = &mut output_device.drm_compositor;
|
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>(
|
match drm_compositor.render_frame::<_, _, GlesTexture>(
|
||||||
&mut output_device.gles,
|
&mut device.gles,
|
||||||
elements,
|
elements,
|
||||||
BACKGROUND_COLOR,
|
BACKGROUND_COLOR,
|
||||||
) {
|
) {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
assert!(!res.needs_sync());
|
assert!(!res.needs_sync());
|
||||||
|
// debug!("{:?}", res);
|
||||||
if res.damage.is_some() {
|
if res.damage.is_some() {
|
||||||
match output_device.drm_compositor.queue_frame(()) {
|
match drm_compositor.queue_frame(()) {
|
||||||
Ok(()) => niri.waiting_for_vblank = true,
|
Ok(()) => {
|
||||||
|
niri.output_state
|
||||||
|
.get_mut(output)
|
||||||
|
.unwrap()
|
||||||
|
.waiting_for_vblank = true
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("error queueing frame: {err}");
|
error!("error queueing frame: {err}");
|
||||||
}
|
}
|
||||||
@ -142,13 +157,15 @@ impl Tty {
|
|||||||
if let Some(output_device) = &mut tty.output_device {
|
if let Some(output_device) = &mut tty.output_device {
|
||||||
output_device.drm.activate();
|
output_device.drm.activate();
|
||||||
|
|
||||||
if let Err(err) = output_device.drm_compositor.surface().reset_state() {
|
for drm_compositor in output_device.surfaces.values_mut() {
|
||||||
warn!("error resetting DRM surface state: {err}");
|
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) {
|
pub fn init(&mut self, niri: &mut Niri) {
|
||||||
let backend = UdevBackend::new(&self.session.seat()).unwrap();
|
let backend = UdevBackend::new(&self.session.seat()).unwrap();
|
||||||
for (device_id, path) in backend.device_list() {
|
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:?}");
|
warn!("error adding device: {err:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,24 +195,21 @@ impl Tty {
|
|||||||
|
|
||||||
match event {
|
match event {
|
||||||
UdevEvent::Added { device_id, path } => {
|
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:?}");
|
warn!("error adding device: {err:?}");
|
||||||
}
|
}
|
||||||
niri.queue_redraw();
|
|
||||||
}
|
}
|
||||||
UdevEvent::Changed { device_id } => tty.device_changed(device_id, niri),
|
UdevEvent::Changed { device_id } => tty.device_changed(device_id, niri),
|
||||||
UdevEvent::Removed { device_id } => tty.device_removed(device_id, niri),
|
UdevEvent::Removed { device_id } => tty.device_removed(device_id, niri),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
niri.queue_redraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_added(
|
fn device_added(
|
||||||
&mut self,
|
&mut self,
|
||||||
device_id: dev_t,
|
device_id: dev_t,
|
||||||
path: PathBuf,
|
path: &Path,
|
||||||
niri: &mut Niri,
|
niri: &mut Niri,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if path != self.primary_gpu_path {
|
if path != self.primary_gpu_path {
|
||||||
@ -207,7 +221,7 @@ impl Tty {
|
|||||||
assert!(self.output_device.is_none());
|
assert!(self.output_device.is_none());
|
||||||
|
|
||||||
let open_flags = OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
|
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 device_fd = unsafe { DrmDeviceFd::new(DeviceFd::from_raw_fd(fd)) };
|
||||||
|
|
||||||
let (drm, drm_notifier) = DrmDevice::new(device_fd.clone(), true)?;
|
let (drm, drm_notifier) = DrmDevice::new(device_fd.clone(), true)?;
|
||||||
@ -219,23 +233,22 @@ impl Tty {
|
|||||||
let mut gles = unsafe { GlesRenderer::new(egl_context)? };
|
let mut gles = unsafe { GlesRenderer::new(egl_context)? };
|
||||||
gles.bind_wl_display(&niri.display_handle)?;
|
gles.bind_wl_display(&niri.display_handle)?;
|
||||||
|
|
||||||
let drm_compositor = self.create_drm_compositor(&drm, &gbm, &gles, niri)?;
|
|
||||||
|
|
||||||
let token = niri
|
let token = niri
|
||||||
.event_loop
|
.event_loop
|
||||||
.insert_source(drm_notifier, move |event, metadata, data| {
|
.insert_source(drm_notifier, move |event, metadata, data| {
|
||||||
let tty = data.tty.as_mut().unwrap();
|
let tty = data.tty.as_mut().unwrap();
|
||||||
match event {
|
match event {
|
||||||
DrmEvent::VBlank(_crtc) => {
|
DrmEvent::VBlank(crtc) => {
|
||||||
tracy_client::Client::running()
|
tracy_client::Client::running()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.message("vblank", 0);
|
.message("vblank", 0);
|
||||||
trace!("vblank {metadata:?}");
|
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.
|
// 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}");
|
error!("error marking frame as submitted: {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,103 +257,111 @@ impl Tty {
|
|||||||
// .windows
|
// .windows
|
||||||
// .mark_presented(&output_device.last_render_states, metadata);
|
// .mark_presented(&output_device.last_render_states, metadata);
|
||||||
|
|
||||||
data.niri.waiting_for_vblank = false;
|
let output = data
|
||||||
data.niri.queue_redraw();
|
.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}"),
|
DrmEvent::Error(error) => error!("DRM error: {error}"),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let formats = Bind::<Dmabuf>::supported_formats(&gles).unwrap_or_default();
|
||||||
|
|
||||||
self.output_device = Some(OutputDevice {
|
self.output_device = Some(OutputDevice {
|
||||||
id: device_id,
|
id: device_id,
|
||||||
path,
|
|
||||||
token,
|
token,
|
||||||
drm,
|
drm,
|
||||||
|
gbm,
|
||||||
gles,
|
gles,
|
||||||
drm_compositor,
|
formats,
|
||||||
|
drm_scanner: DrmScanner::new(),
|
||||||
|
surfaces: HashMap::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.device_changed(device_id, niri);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) {
|
fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) {
|
||||||
if let Some(output_device) = &self.output_device {
|
let Some(device) = &mut self.output_device else {
|
||||||
if output_device.id == device_id {
|
return;
|
||||||
debug!("output device changed");
|
};
|
||||||
|
if device.id != device_id {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug!("output device changed");
|
||||||
|
|
||||||
let path = output_device.path.clone();
|
for event in device.drm_scanner.scan_connectors(&device.drm) {
|
||||||
self.device_removed(device_id, niri);
|
match event {
|
||||||
if let Err(err) = self.device_added(device_id, path, niri) {
|
DrmScanEvent::Connected {
|
||||||
warn!("error adding device: {err:?}");
|
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) {
|
fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) {
|
||||||
if let Some(mut output_device) = self.output_device.take() {
|
let Some(device) = self.output_device.take() else {
|
||||||
if output_device.id != device_id {
|
return;
|
||||||
self.output_device = Some(output_device);
|
};
|
||||||
return;
|
if device.id != device_id {
|
||||||
}
|
// It wasn't the output device, put it back in.
|
||||||
|
self.output_device = Some(device);
|
||||||
// FIXME: remove wl_output.
|
return;
|
||||||
niri.event_loop.remove(output_device.token);
|
|
||||||
niri.output = None;
|
|
||||||
output_device.gles.unbind_wl_display();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
&mut self,
|
||||||
drm: &DrmDevice,
|
|
||||||
gbm: &GbmDevice<DrmDeviceFd>,
|
|
||||||
gles: &GlesRenderer,
|
|
||||||
niri: &mut Niri,
|
niri: &mut Niri,
|
||||||
) -> anyhow::Result<GbmDrmCompositor> {
|
connector: connector::Info,
|
||||||
let formats = Bind::<Dmabuf>::supported_formats(gles)
|
crtc: crtc::Handle,
|
||||||
.ok_or_else(|| anyhow!("no supported formats"))?;
|
) -> anyhow::Result<()> {
|
||||||
let resources = drm.resource_handles()?;
|
let output_name = format!(
|
||||||
|
"{}-{}",
|
||||||
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.interface().as_str(),
|
connector.interface().as_str(),
|
||||||
connector.interface_id(),
|
connector.interface_id(),
|
||||||
);
|
);
|
||||||
|
debug!("connecting connector: {output_name}");
|
||||||
|
|
||||||
|
let device = self.output_device.as_mut().unwrap();
|
||||||
|
|
||||||
let mut mode = connector.modes().get(0);
|
let mut mode = connector.modes().get(0);
|
||||||
connector.modes().iter().for_each(|m| {
|
connector.modes().iter().for_each(|m| {
|
||||||
@ -357,57 +378,20 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mode = mode.ok_or_else(|| anyhow!("no mode"))?;
|
let mode = mode.ok_or_else(|| anyhow!("no mode"))?;
|
||||||
info!("picking mode: {mode:?}");
|
debug!("picking mode: {mode:?}");
|
||||||
|
|
||||||
let surface = connector
|
let surface = device
|
||||||
.encoders()
|
.drm
|
||||||
.iter()
|
.create_surface(crtc, *mode, &[connector.handle()])?;
|
||||||
.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"))?;
|
|
||||||
|
|
||||||
// Create GBM allocator.
|
// Create GBM allocator.
|
||||||
let gbm_flags = GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT;
|
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.
|
// Update the output mode.
|
||||||
let (physical_width, physical_height) = connector.size().unwrap_or((0, 0));
|
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))
|
.map(|info| (info.manufacturer, info.model))
|
||||||
.unwrap_or_else(|| ("Unknown".into(), "Unknown".into()));
|
.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.change_current_state(Some(wl_mode), None, None, Some((0, 0).into()));
|
||||||
output.set_preferred(wl_mode);
|
output.set_preferred(wl_mode);
|
||||||
|
|
||||||
// FIXME: store this somewhere to remove on disconnect, etc.
|
output.user_data().insert_if_missing(|| TtyOutputState {
|
||||||
let _global = output.create_global::<Niri>(&niri.display_handle);
|
device_id: device.id,
|
||||||
niri.space.map_output(&output, (0, 0));
|
crtc,
|
||||||
niri.output = Some(output.clone());
|
});
|
||||||
// windows.set_output();
|
|
||||||
|
|
||||||
// Create the compositor.
|
// Create the compositor.
|
||||||
let compositor = DrmCompositor::new(
|
let compositor = DrmCompositor::new(
|
||||||
OutputModeSource::Auto(output),
|
OutputModeSource::Auto(output.clone()),
|
||||||
surface,
|
surface,
|
||||||
None,
|
None,
|
||||||
allocator,
|
allocator,
|
||||||
gbm.clone(),
|
device.gbm.clone(),
|
||||||
SUPPORTED_COLOR_FORMATS,
|
SUPPORTED_COLOR_FORMATS,
|
||||||
formats,
|
device.formats.clone(),
|
||||||
drm.cursor_size(),
|
device.drm.cursor_size(),
|
||||||
Some(gbm.clone()),
|
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) {
|
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::output::{Mode, Output, PhysicalProperties, Subpixel};
|
||||||
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
||||||
use smithay::reexports::calloop::LoopHandle;
|
use smithay::reexports::calloop::LoopHandle;
|
||||||
|
use smithay::reexports::winit::dpi::LogicalSize;
|
||||||
|
use smithay::reexports::winit::window::WindowBuilder;
|
||||||
use smithay::utils::{Rectangle, Transform};
|
use smithay::utils::{Rectangle, Transform};
|
||||||
|
|
||||||
use crate::backend::Backend;
|
use crate::backend::Backend;
|
||||||
@ -34,6 +36,7 @@ impl Backend for Winit {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
_niri: &mut Niri,
|
_niri: &mut Niri,
|
||||||
|
_output: &Output,
|
||||||
elements: &[OutputRenderElements<
|
elements: &[OutputRenderElements<
|
||||||
GlesRenderer,
|
GlesRenderer,
|
||||||
WaylandSurfaceRenderElement<GlesRenderer>,
|
WaylandSurfaceRenderElement<GlesRenderer>,
|
||||||
@ -54,7 +57,11 @@ impl Backend for Winit {
|
|||||||
|
|
||||||
impl Winit {
|
impl Winit {
|
||||||
pub fn new(event_loop: LoopHandle<LoopData>) -> Self {
|
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 {
|
let mode = Mode {
|
||||||
size: backend.window_size().physical_size,
|
size: backend.window_size().physical_size,
|
||||||
@ -98,9 +105,6 @@ impl Winit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, niri: &mut Niri) {
|
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
|
if let Err(err) = self
|
||||||
.backend
|
.backend
|
||||||
.renderer()
|
.renderer()
|
||||||
@ -108,6 +112,7 @@ impl Winit {
|
|||||||
{
|
{
|
||||||
warn!("error binding renderer wl_display: {err}");
|
warn!("error binding renderer wl_display: {err}");
|
||||||
}
|
}
|
||||||
|
niri.add_output(self.output.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch(&mut self, niri: &mut Niri) {
|
fn dispatch(&mut self, niri: &mut Niri) {
|
||||||
@ -115,7 +120,7 @@ impl Winit {
|
|||||||
.winit_event_loop
|
.winit_event_loop
|
||||||
.dispatch_new_events(|event| match event {
|
.dispatch_new_events(|event| match event {
|
||||||
WinitEvent::Resized { size, .. } => {
|
WinitEvent::Resized { size, .. } => {
|
||||||
niri.output.as_ref().unwrap().change_current_state(
|
self.output.change_current_state(
|
||||||
Some(Mode {
|
Some(Mode {
|
||||||
size,
|
size,
|
||||||
refresh: 60_000,
|
refresh: 60_000,
|
||||||
@ -124,12 +129,13 @@ impl Winit {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
niri.output_resized(self.output.clone());
|
||||||
}
|
}
|
||||||
WinitEvent::Input(event) => {
|
WinitEvent::Input(event) => {
|
||||||
niri.process_input_event(&mut |_| (), CompositorMod::Alt, event)
|
niri.process_input_event(&mut |_| (), CompositorMod::Alt, event)
|
||||||
}
|
}
|
||||||
WinitEvent::Focus(_) => (),
|
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.
|
// I want this to stop compiling if more errors are added.
|
||||||
@ -137,6 +143,7 @@ impl Winit {
|
|||||||
match res {
|
match res {
|
||||||
Err(WinitError::WindowClosed) => {
|
Err(WinitError::WindowClosed) => {
|
||||||
niri.stop_signal.stop();
|
niri.stop_signal.stop();
|
||||||
|
niri.remove_output(&self.output);
|
||||||
}
|
}
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user