Commit bc51c38a6fd9
tarball9 files changed+301-12
@@ -28,6 +28,15 @@ pub enum ExpandScope {
2828
All,2929
}3030
31
+/// Space taken from an output's edges by bars and other panels.32
+#[derive(Clone, Copy, Debug, PartialEq, Default)]33
+pub struct Insets {34
+ pub top: f64,35
+ pub right: f64,36
+ pub bottom: f64,37
+ pub left: f64,38
+}39
+3140
#[derive(Clone, Copy, Debug, PartialEq)]3241
pub struct Gaps {3342
pub inner: f64,@@ -65,6 +74,7 @@ pub struct Tag {
6574
pub struct Output {6675
pub name: String,6776
pub area: Rect,77
+ pub reserved: Insets,6878
pub tree: Tree<WindowId>,6979
/// Viewed tags besides the homeless tag, which is always viewed.7080
pub viewed: Vec<TagId>,@@ -216,6 +226,7 @@ impl Layout {
216226
let id = self.outputs.insert(Output {217227
name: name.into(),218228
area,229
+ reserved: Insets::default(),219230
tree: Tree::default(),220231
viewed: Vec::new(),221232
floating: Vec::new(),@@ -331,6 +342,17 @@ impl Layout {
331342
}332343
}333344
345
+ /// Returns whether anything changed.346
+ pub fn set_reserved(&mut self, o: OutputId, reserved: Insets) -> bool {347
+ match self.outputs.get_mut(o) {348
+ Some(out) if out.reserved != reserved => {349
+ out.reserved = reserved;350
+ true351
+ }352
+ _ => false,353
+ }354
+ }355
+334356
pub fn is_viewed(&self, o: OutputId, tag: TagId) -> bool {335357
tag == self.homeless336358
|| self@@ -821,7 +843,14 @@ impl Layout {
821843
let Some(out) = self.outputs.get(o) else {822844
return Arrangement::default();823845
};824
- let area = inset(out.area, self.gaps.outer);846
+ let r = out.reserved;847
+ let usable = Rect::new(848
+ out.area.x + r.left,849
+ out.area.y + r.top,850
+ (out.area.w - r.left - r.right).max(0.0),851
+ (out.area.h - r.top - r.bottom).max(0.0),852
+ );853
+ let area = inset(usable, self.gaps.outer);825854
let shown = self.vis_on(o);826855
let vis = &|w: WindowId| shown(w) && !hide.contains(&w);827856
let tiled = out@@ -646,6 +646,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
646646
now,647647
&config.decoration,648648
borders,649
+ Some(&surface.output),649650
));650651
let scene_ns = scene_began.elapsed().as_nanos() as u64;651652
let animating = anim.active(now);@@ -694,6 +695,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
694695
Some(output.clone())695696
});696697
}698
+ crate::layers::send_frames(&output, elapsed);697699
animating && !surface.queued698700
}699701
@@ -94,6 +94,7 @@ impl CompositorHandler for State {
9494
{9595
let _ = popup.send_configure();9696
}97
+ self.layer_commit(surface);9798
self.needs_redraw = true;9899
}99100
}@@ -9,6 +9,7 @@ use smithay::backend::session::Session;
99
use smithay::input::keyboard::{FilterResult, Keycode, xkb};1010
use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};1111
use smithay::utils::{Logical, Point, SERIAL_COUNTER};12
+use smithay::wayland::shell::wlr_layer::Layer;1213
1314
impl State {1415
pub fn pointer_to(&mut self, pos: Point<f64, Logical>, time: InputTime) {@@ -107,6 +108,7 @@ impl State {
107108
);108109
match action {109110
Some(Some(Action::Run(cmd))) => {111
+ self.layer_focus = None;110112
if let Err(e) = self.run(cmd) {111113
tracing::warn!("bind failed: {e}");112114
}@@ -130,6 +132,17 @@ enum Action {
130132
131133
fn focus_under_pointer(state: &mut State) {132134
let pos = state.pointer_pos;135
+ let layers = [Layer::Overlay, Layer::Top, Layer::Bottom, Layer::Background];136
+ if let Some((layer, surface, _)) = state.layer_under(pos, &layers)137
+ && layer.can_receive_keyboard_focus()138
+ {139
+ state.layer_focus = Some(surface);140
+ state.sync_focus();141
+ return;142
+ }143
+ if state.layer_focus.take().is_some() {144
+ state.sync_focus();145
+ }133146
let id = state134147
.space135148
.element_under(pos)@@ -0,0 +1,195 @@
1
+//! wlr-layer-shell, for bars, launchers, wallpapers and notifications.2
+3
+use crate::scene::Draw;4
+use crate::state::State;5
+use monolith_layout::{Insets, OutputId};6
+use monolith_render_vk::VulkanRenderer;7
+use smithay::backend::renderer::element::AsRenderElements;8
+use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;9
+use smithay::desktop::{LayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output};10
+use smithay::output::Output;11
+use smithay::reexports::wayland_server::Resource;12
+use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;13
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;14
+use smithay::utils::{Logical, Point, Scale};15
+use smithay::wayland::compositor::with_states;16
+use smithay::wayland::shell::wlr_layer::{17
+ KeyboardInteractivity, Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData,18
+ WlrLayerShellHandler, WlrLayerShellState,19
+};20
+use smithay::wayland::shell::xdg::PopupSurface;21
+use std::time::Duration;22
+23
+impl WlrLayerShellHandler for State {24
+ fn shell_state(&mut self) -> &mut WlrLayerShellState {25
+ &mut self.layer_shell_state26
+ }27
+28
+ fn new_layer_surface(29
+ &mut self,30
+ surface: WlrLayerSurface,31
+ output: Option<WlOutput>,32
+ _layer: Layer,33
+ namespace: String,34
+ ) {35
+ let output = output36
+ .as_ref()37
+ .and_then(Output::from_resource)38
+ .or_else(|| self.space.output_under(self.pointer_pos).next().cloned())39
+ .or_else(|| self.space.outputs().next().cloned());40
+ let Some(output) = output else {41
+ surface.send_close();42
+ return;43
+ };44
+ let mut map = layer_map_for_output(&output);45
+ if let Err(e) = map.map_layer(&LayerSurface::new(surface, namespace)) {46
+ tracing::warn!("cannot map layer surface: {e}");47
+ }48
+ }49
+50
+ fn new_popup(&mut self, _parent: WlrLayerSurface, popup: PopupSurface) {51
+ let _ = self.popups.track_popup(PopupKind::Xdg(popup));52
+ }53
+54
+ fn layer_destroyed(&mut self, surface: WlrLayerSurface) {55
+ let outputs: Vec<Output> = self.space.outputs().cloned().collect();56
+ for output in outputs {57
+ let mut map = layer_map_for_output(&output);58
+ let gone = map59
+ .layers()60
+ .find(|l| l.layer_surface() == &surface)61
+ .cloned();62
+ if let Some(layer) = gone {63
+ map.unmap_layer(&layer);64
+ }65
+ }66
+ if self.layer_focus.as_ref() == Some(surface.wl_surface()) {67
+ self.layer_focus = None;68
+ }69
+ self.reserve_layers();70
+ }71
+}72
+73
+impl State {74
+ /// Sends a layer surface its first configure once it has committed its75
+ /// anchors and size, and picks up exclusive zone changes after that.76
+ /// Returns false for surfaces that are not layer surfaces.77
+ pub fn layer_commit(&mut self, surface: &WlSurface) -> bool {78
+ let Some(output) = self79
+ .space80
+ .outputs()81
+ .find(|o| {82
+ layer_map_for_output(o)83
+ .layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)84
+ .is_some()85
+ })86
+ .cloned()87
+ else {88
+ return false;89
+ };90
+ let configured = with_states(surface, |s| {91
+ s.data_map92
+ .get::<LayerSurfaceData>()93
+ .and_then(|d| d.lock().ok())94
+ .is_some_and(|d| d.initial_configure_sent)95
+ });96
+ {97
+ let mut map = layer_map_for_output(&output);98
+ map.arrange();99
+ if !configured100
+ && let Some(layer) = map.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)101
+ {102
+ layer.layer_surface().send_configure();103
+ }104
+ }105
+ self.reserve_layers();106
+ true107
+ }108
+109
+ /// Keeps tiled windows out of the space bars reserve.110
+ pub fn reserve_layers(&mut self) {111
+ let outputs: Vec<OutputId> = self.layout.outputs().map(|(o, _)| o).collect();112
+ let mut changed = false;113
+ for o in outputs {114
+ let Some(output) = self.smithay_output(o) else {115
+ continue;116
+ };117
+ let Some(area) = self.layout.output(o).map(|out| out.area) else {118
+ continue;119
+ };120
+ let mut map = layer_map_for_output(&output);121
+ map.arrange();122
+ let zone = map.non_exclusive_zone();123
+ let reserved = Insets {124
+ top: f64::from(zone.loc.y),125
+ left: f64::from(zone.loc.x),126
+ bottom: (area.h - f64::from(zone.loc.y + zone.size.h)).max(0.0),127
+ right: (area.w - f64::from(zone.loc.x + zone.size.w)).max(0.0),128
+ };129
+ drop(map);130
+ changed |= self.layout.set_reserved(o, reserved);131
+ }132
+ if changed {133
+ self.apply_layout();134
+ } else {135
+ self.sync_focus();136
+ }137
+ }138
+139
+ /// The layer surface that should hold the keyboard, if any. One asking140
+ /// for exclusive focus above the windows wins over everything.141
+ pub fn layer_keyboard_focus(&self) -> Option<WlSurface> {142
+ for output in self.space.outputs() {143
+ let map = layer_map_for_output(output);144
+ let exclusive = map145
+ .layers_on(Layer::Overlay)146
+ .chain(map.layers_on(Layer::Top))147
+ .find(|l| {148
+ l.cached_state().keyboard_interactivity == KeyboardInteractivity::Exclusive149
+ });150
+ if let Some(l) = exclusive {151
+ return Some(l.wl_surface().clone());152
+ }153
+ }154
+ self.layer_focus.clone().filter(|s| s.is_alive())155
+ }156
+157
+ /// The layer surface under `pos` on `layers`, with its surface origin.158
+ pub fn layer_under(159
+ &self,160
+ pos: Point<f64, Logical>,161
+ layers: &[Layer],162
+ ) -> Option<(LayerSurface, WlSurface, Point<f64, Logical>)> {163
+ let output = self.space.output_under(pos).next()?;164
+ let origin = self.space.output_geometry(output)?.loc;165
+ let local = pos - origin.to_f64();166
+ let map = layer_map_for_output(output);167
+ layers.iter().find_map(|&layer| {168
+ let l = map.layer_under(layer, local)?;169
+ let geo = map.layer_geometry(l)?;170
+ let (s, p) = l.surface_under(local - geo.loc.to_f64(), WindowSurfaceType::ALL)?;171
+ Some((l.clone(), s, (p + geo.loc + origin).to_f64()))172
+ })173
+ }174
+}175
+176
+/// Draws an output's layer surfaces on `layer`, front to back.177
+pub fn elements(out: &mut Vec<Draw>, renderer: &mut VulkanRenderer, output: &Output, layer: Layer) {178
+ let map = layer_map_for_output(output);179
+ for l in map.layers_on(layer).rev() {180
+ let Some(geo) = map.layer_geometry(l) else {181
+ continue;182
+ };183
+ let elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>> =184
+ l.render_elements(renderer, geo.loc.to_physical(1), Scale::from(1.0), 1.0);185
+ out.extend(elems.into_iter().map(Draw::Surface));186
+ }187
+}188
+189
+pub fn send_frames(output: &Output, elapsed: Duration) {190
+ for l in layer_map_for_output(output).layers() {191
+ l.send_frame(output, elapsed, Some(Duration::ZERO), |_, _| {192
+ Some(output.clone())193
+ });194
+ }195
+}@@ -6,6 +6,7 @@ mod handlers;
66
mod input;77
mod ipc;88
mod launch;9
+mod layers;910
mod nested;1011
mod scene;1112
mod state;@@ -232,6 +232,7 @@ pub fn render(state: &mut State) {
232232
now,233233
&config.decoration,234234
borders,235
+ Some(&host.output),235236
);236237
match host237238
.damage@@ -257,6 +258,7 @@ pub fn render(state: &mut State) {
257258
Some(host.output.clone())258259
});259260
}261
+ crate::layers::send_frames(&host.output, elapsed);260262
let Some(damage) = damage else {261263
if *needs_redraw {262264
host.surface.frame(&host.qh, ());@@ -15,8 +15,10 @@ use smithay::backend::renderer::utils::CommitCounter;
1515
use smithay::backend::renderer::utils::with_renderer_surface_state;1616
use smithay::backend::renderer::{ContextId, Renderer, Texture};1717
use smithay::desktop::Window;18
+use smithay::output::Output;1819
use smithay::utils::user_data::UserDataMap;1920
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};21
+use smithay::wayland::shell::wlr_layer::Layer;2022
use std::collections::HashMap;2123
2224
smithay::backend::renderer::element::render_elements! {@@ -47,6 +49,7 @@ pub fn build(
4749
now: u64,4850
deco: &Decoration,4951
borders: &mut Borders,52
+ output: Option<&Output>,5053
) -> Vec<Draw> {5154
let Some(area) = layout.output(o).map(|out| out.area) else {5255
return Vec::new();@@ -87,6 +90,12 @@ pub fn build(
8790
borders.0.retain(|id, _| windows.contains_key(id));8891
let focused = layout.focused();8992
let mut out = Vec::new();93
+ if let Some(output) = output {94
+ crate::layers::elements(&mut out, renderer, output, Layer::Overlay);95
+ if !fullscreen_at_rest {96
+ crate::layers::elements(&mut out, renderer, output, Layer::Top);97
+ }98
+ }9099
for (id, r, framed, gliding) in tiled.into_iter().chain(above).rev() {91100
let Some(window) = windows.get(&id) else {92101
continue;@@ -121,6 +130,10 @@ pub fn build(
121130
anim.ghost_effect(g, now),122131
)));123132
}133
+ if let Some(output) = output {134
+ crate::layers::elements(&mut out, renderer, output, Layer::Bottom);135
+ crate::layers::elements(&mut out, renderer, output, Layer::Background);136
+ }124137
out125138
}126139
@@ -26,6 +26,7 @@ use smithay::wayland::compositor::{CompositorClientState, CompositorState, with_
2626
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufState};2727
use smithay::wayland::output::OutputManagerState;2828
use smithay::wayland::selection::data_device::DataDeviceState;29
+use smithay::wayland::shell::wlr_layer::{Layer, WlrLayerShellState};2930
use smithay::wayland::shell::xdg::decoration::XdgDecorationState;3031
use smithay::wayland::shell::xdg::{XdgShellState, XdgToplevelSurfaceData};3132
use smithay::wayland::shm::ShmState;@@ -76,10 +77,13 @@ pub struct State {
7677
/// switches between tiled, floating and fullscreen.7778
pub last_kind: HashMap<WindowId, (Kind, Rect, OutputId)>,7879
pub awaiting: HashSet<WindowId>,80
+ /// A layer surface given the keyboard by a click, like a launcher.81
+ pub layer_focus: Option<WlSurface>,7982
pub handle: LoopHandle<'static, State>,8083
8184
pub compositor_state: CompositorState,8285
pub xdg_shell_state: XdgShellState,86
+ pub layer_shell_state: WlrLayerShellState,8387
pub _xdg_decoration_state: XdgDecorationState,8488
pub shm_state: ShmState,8589
pub dmabuf_state: DmabufState,@@ -171,9 +175,11 @@ impl State {
171175
borders: Default::default(),172176
last_kind: HashMap::new(),173177
awaiting: HashSet::new(),178
+ layer_focus: None,174179
handle: event_loop.handle(),175180
compositor_state: CompositorState::new::<State>(&dh),176181
xdg_shell_state: XdgShellState::new::<State>(&dh),182
+ layer_shell_state: WlrLayerShellState::new::<State>(&dh),177183
_xdg_decoration_state: XdgDecorationState::new::<State>(&dh),178184
shm_state: ShmState::new::<State>(&dh, vec![]),179185
dmabuf_state,@@ -201,6 +207,15 @@ impl State {
201207
if let Some(o) = self.output_id {202208
let area = Rect::new(0.0, 0.0, f64::from(size.w), f64::from(size.h));203209
self.layout.set_output_area(o, area);210
+ if let Some(host) = &self.host {211
+ let mode = smithay::output::Mode {212
+ size: (size.w, size.h).into(),213
+ refresh: 60_000,214
+ };215
+ host.output216
+ .change_current_state(Some(mode), None, None, None);217
+ }218
+ self.reserve_layers();204219
self.apply_layout();205220
}206221
}@@ -455,7 +470,7 @@ impl State {
455470
self.needs_redraw = true;456471
}457472
458
- fn sync_focus(&mut self) {473
+ pub fn sync_focus(&mut self) {459474
let focused = self.layout.focused();460475
for (id, window) in &self.windows {461476
if window.set_activated(Some(*id) == focused)@@ -465,10 +480,12 @@ impl State {
465480
t.send_pending_configure();466481
}467482
}468
- let target = focused469
- .and_then(|id| self.windows.get(&id))470
- .and_then(|w| w.toplevel())471
- .map(|t| t.wl_surface().clone());483
+ let target = self.layer_keyboard_focus().or_else(|| {484
+ focused485
+ .and_then(|id| self.windows.get(&id))486
+ .and_then(|w| w.toplevel())487
+ .map(|t| t.wl_surface().clone())488
+ });472489
if let Some(keyboard) = self.seat.get_keyboard()473490
&& keyboard.current_focus() != target474491
{@@ -526,13 +543,29 @@ impl State {
526543
&self,527544
pos: Point<f64, Logical>,528545
) -> Option<(WlSurface, Point<f64, Logical>)> {529
- self.space530
- .element_under(pos)531
- .and_then(|(window, location)| {532
- window533
- .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL)534
- .map(|(s, p)| (s, (p + location).to_f64()))546
+ let fullscreen = self.layout.outputs().any(|(o, out)| {547
+ let a = out.area;548
+ (a.x..a.right()).contains(&pos.x)549
+ && (a.y..a.bottom()).contains(&pos.y)550
+ && self.layout.arrange(o).fullscreen.is_some()551
+ });552
+ let above: &[Layer] = if fullscreen {553
+ &[Layer::Overlay]554
+ } else {555
+ &[Layer::Overlay, Layer::Top]556
+ };557
+ let layer = |layers| self.layer_under(pos, layers).map(|(_, s, p)| (s, p));558
+ layer(above)559
+ .or_else(|| {560
+ self.space561
+ .element_under(pos)562
+ .and_then(|(window, location)| {563
+ window564
+ .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL)565
+ .map(|(s, p)| (s, (p + location).to_f64()))566
+ })535567
})568
+ .or_else(|| layer(&[Layer::Bottom, Layer::Background]))536569
}537570
}