aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--niri-config/src/lib.rs12
-rw-r--r--niri-visual-tests/src/cases/layout.rs1
-rw-r--r--src/layout/mod.rs2
-rw-r--r--src/layout/monitor.rs147
-rw-r--r--src/niri.rs156
5 files changed, 232 insertions, 86 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index d622bfcd..34a3b6fa 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -23,7 +23,8 @@ use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
use smithay::input::keyboard::{Keysym, XkbConfig};
use smithay::reexports::input;
-pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.2, 0.2, 0.2, 1.]);
+pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.25, 0.25, 0.25, 1.]);
+pub const DEFAULT_BACKDROP_COLOR: Color = Color::from_array_unpremul([0.15, 0.15, 0.15, 1.]);
pub mod layer_rule;
@@ -444,6 +445,8 @@ pub struct Output {
pub focus_at_startup: bool,
#[knuffel(child, default = DEFAULT_BACKGROUND_COLOR)]
pub background_color: Color,
+ #[knuffel(child, default = DEFAULT_BACKDROP_COLOR)]
+ pub backdrop_color: Color,
}
impl Output {
@@ -472,6 +475,7 @@ impl Default for Output {
mode: None,
variable_refresh_rate: None,
background_color: DEFAULT_BACKGROUND_COLOR,
+ backdrop_color: DEFAULT_BACKDROP_COLOR,
}
}
}
@@ -4127,6 +4131,12 @@ mod tests {
b: 0.4,
a: 1.0,
},
+ backdrop_color: Color {
+ r: 0.15,
+ g: 0.15,
+ b: 0.15,
+ a: 1.0,
+ },
},
],
),
diff --git a/niri-visual-tests/src/cases/layout.rs b/niri-visual-tests/src/cases/layout.rs
index 23f2bc8d..81748e8c 100644
--- a/niri-visual-tests/src/cases/layout.rs
+++ b/niri-visual-tests/src/cases/layout.rs
@@ -266,6 +266,7 @@ impl TestCase for Layout {
.monitor_for_output(&self.output)
.unwrap()
.render_elements(renderer, RenderTarget::Output, true)
+ .flat_map(|(_, iter)| iter)
.map(|elem| Box::new(elem) as _)
.collect()
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 040a5cb4..cdb96113 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -2279,7 +2279,7 @@ impl<W: LayoutElement> Layout<W> {
) -> Option<(&W, HitType)> {
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.output == *output {
- let tile_pos = move_.tile_render_location(1.);
+ let tile_pos = move_.tile_render_location();
HitType::hit_tile(&move_.tile, tile_pos, pos_within_output)
} else {
None
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index 4c20d533..ab5384a8 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -25,7 +25,9 @@ use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::RenderTarget;
use crate::rubber_band::RubberBand;
use crate::utils::transaction::Transaction;
-use crate::utils::{output_size, ResizeEdge};
+use crate::utils::{
+ output_size, round_logical_in_physical, round_logical_in_physical_max1, ResizeEdge,
+};
/// Amount of touchpad movement to scroll the height of one workspace.
const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.;
@@ -943,6 +945,23 @@ impl<W: LayoutElement> Monitor<W> {
self.active_workspace_ref().active_tile_visual_rectangle()
}
+ fn workspace_size(&self, zoom: f64) -> Size<f64, Logical> {
+ let ws_size = self.view_size.upscale(zoom);
+ let scale = self.scale.fractional_scale();
+ ws_size.to_physical_precise_ceil(scale).to_logical(scale)
+ }
+
+ fn workspace_gap(&self, zoom: f64) -> f64 {
+ let scale = self.scale.fractional_scale();
+ let gap = self.view_size.h * 0.1 * zoom;
+ round_logical_in_physical_max1(scale, gap)
+ }
+
+ fn workspace_size_with_gap(&self, zoom: f64) -> Size<f64, Logical> {
+ let gap = self.workspace_gap(zoom);
+ self.workspace_size(zoom) + Size::from((0., gap))
+ }
+
pub fn workspace_render_idx(&self) -> f64 {
if let Some(switch) = &self.workspace_switch {
switch.current_idx()
@@ -953,17 +972,19 @@ impl<W: LayoutElement> Monitor<W> {
pub fn workspaces_render_geo(&self) -> impl Iterator<Item = Rectangle<f64, Logical>> {
let scale = self.scale.fractional_scale();
- let size = self.view_size;
+ let zoom = 1.;
- // Ceil the workspace size in physical pixels.
- let ws_size = size.to_physical_precise_ceil(scale).to_logical(scale);
+ let ws_size = self.workspace_size(zoom);
+ let gap = self.workspace_gap(zoom);
+ let ws_height_with_gap = ws_size.h + gap;
- let first_ws_y = -self.workspace_render_idx() * ws_size.h;
+ let first_ws_y = -self.workspace_render_idx() * ws_height_with_gap;
+ let first_ws_y = round_logical_in_physical(scale, first_ws_y);
- (0..self.workspaces.len()).map(move |idx| {
- let y = first_ws_y + idx as f64 * ws_size.h;
+ // Return position for one-past-last workspace too.
+ (0..=self.workspaces.len()).map(move |idx| {
+ let y = first_ws_y + idx as f64 * ws_height_with_gap;
let loc = Point::from((0., y));
- let loc = loc.to_physical_precise_round(scale).to_logical(scale);
Rectangle::new(loc, ws_size)
})
}
@@ -1031,7 +1052,12 @@ impl<W: LayoutElement> Monitor<W> {
renderer: &'a mut R,
target: RenderTarget,
focus_ring: bool,
- ) -> impl Iterator<Item = MonitorRenderElement<R>> + 'a {
+ ) -> impl Iterator<
+ Item = (
+ Rectangle<f64, Logical>,
+ impl Iterator<Item = MonitorRenderElement<R>> + 'a,
+ ),
+ > {
let _span = tracy_client::span!("Monitor::render_elements");
let scale = self.scale.fractional_scale();
@@ -1072,45 +1098,45 @@ impl<W: LayoutElement> Monitor<W> {
}
}
- self.workspaces_with_render_geo()
- .flat_map(move |(ws, geo)| {
- let map_ws_contents = move |elem: WorkspaceRenderElement<R>| {
+ self.workspaces_with_render_geo().map(move |(ws, geo)| {
+ let map_ws_contents = move |elem: WorkspaceRenderElement<R>| {
+ let elem = CropRenderElement::from_element(elem, scale, crop_bounds)?;
+ let elem = MonitorInnerRenderElement::Workspace(elem);
+ Some(elem)
+ };
+
+ let (floating, scrolling) = ws.render_elements(renderer, target, focus_ring);
+ let floating = floating.filter_map(map_ws_contents);
+ let scrolling = scrolling.filter_map(map_ws_contents);
+
+ let hint = if matches!(insert_hint, Some((hint_ws_id, _)) if hint_ws_id == ws.id()) {
+ let iter = insert_hint.take().unwrap().1;
+ let iter = iter.filter_map(move |elem| {
let elem = CropRenderElement::from_element(elem, scale, crop_bounds)?;
- let elem = MonitorInnerRenderElement::Workspace(elem);
+ let elem = MonitorInnerRenderElement::InsertHint(elem);
Some(elem)
- };
-
- let (floating, scrolling) = ws.render_elements(renderer, target, focus_ring);
- let floating = floating.filter_map(map_ws_contents);
- let scrolling = scrolling.filter_map(map_ws_contents);
-
- let hint = if matches!(insert_hint, Some((hint_ws_id, _)) if hint_ws_id == ws.id())
- {
- let iter = insert_hint.take().unwrap().1;
- let iter = iter.filter_map(move |elem| {
- let elem = CropRenderElement::from_element(elem, scale, crop_bounds)?;
- let elem = MonitorInnerRenderElement::InsertHint(elem);
- Some(elem)
- });
- Some(iter)
- } else {
- None
- };
- let hint = hint.into_iter().flatten();
-
- let iter = floating.chain(hint).chain(scrolling);
-
- iter.map(move |elem| {
- RelocateRenderElement::from_element(
- elem,
- // The offset we get from workspaces_with_render_positions() is already
- // rounded to physical pixels, but it's in the logical coordinate
- // space, so we need to convert it to physical.
- geo.loc.to_physical_precise_round(scale),
- Relocate::Relative,
- )
- })
- })
+ });
+ Some(iter)
+ } else {
+ None
+ };
+ let hint = hint.into_iter().flatten();
+
+ let iter = floating.chain(hint).chain(scrolling);
+
+ let iter = iter.map(move |elem| {
+ RelocateRenderElement::from_element(
+ elem,
+ // The offset we get from workspaces_with_render_positions() is already
+ // rounded to physical pixels, but it's in the logical coordinate
+ // space, so we need to convert it to physical.
+ geo.loc.to_physical_precise_round(scale),
+ Relocate::Relative,
+ )
+ });
+
+ (geo, iter)
+ })
}
pub fn workspace_switch_gesture_begin(&mut self, is_touchpad: bool) {
@@ -1133,7 +1159,7 @@ impl<W: LayoutElement> Monitor<W> {
timestamp: Duration,
is_touchpad: bool,
) -> Option<bool> {
- let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
+ let Some(WorkspaceSwitch::Gesture(gesture)) = &self.workspace_switch else {
return None;
};
@@ -1141,13 +1167,18 @@ impl<W: LayoutElement> Monitor<W> {
return None;
}
- gesture.tracker.push(delta_y, timestamp);
-
let total_height = if gesture.is_touchpad {
WORKSPACE_GESTURE_MOVEMENT
} else {
- self.workspaces[0].view_size().h
+ self.workspace_size_with_gap(1.).h
+ };
+
+ let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
+ return None;
};
+
+ gesture.tracker.push(delta_y, timestamp);
+
let pos = gesture.tracker.pos() / total_height;
let (min, max) = gesture.min_max(self.workspaces.len());
@@ -1163,7 +1194,7 @@ impl<W: LayoutElement> Monitor<W> {
}
pub fn workspace_switch_gesture_end(&mut self, is_touchpad: Option<bool>) -> bool {
- let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
+ let Some(WorkspaceSwitch::Gesture(gesture)) = &self.workspace_switch else {
return false;
};
@@ -1171,16 +1202,20 @@ impl<W: LayoutElement> Monitor<W> {
return false;
}
- // Take into account any idle time between the last event and now.
- let now = self.clock.now_unadjusted();
- gesture.tracker.push(0., now);
-
let total_height = if gesture.is_touchpad {
WORKSPACE_GESTURE_MOVEMENT
} else {
- self.workspaces[0].view_size().h
+ self.workspace_size_with_gap(1.).h
+ };
+
+ let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
+ return false;
};
+ // Take into account any idle time between the last event and now.
+ let now = self.clock.now_unadjusted();
+ gesture.tracker.push(0., now);
+
let mut velocity = gesture.tracker.velocity() / total_height;
let current_pos = gesture.tracker.pos() / total_height;
let pos = gesture.tracker.projected_end_pos() / total_height;
diff --git a/src/niri.rs b/src/niri.rs
index 2543ddb3..de88da71 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -15,7 +15,7 @@ use anyhow::{bail, ensure, Context};
use calloop::futures::Scheduler;
use niri_config::{
Config, FloatOrInt, Key, Modifiers, OutputName, PreviewRender, TrackLayout,
- WarpMouseToFocusMode, WorkspaceReference, DEFAULT_BACKGROUND_COLOR,
+ WarpMouseToFocusMode, WorkspaceReference, DEFAULT_BACKDROP_COLOR, DEFAULT_BACKGROUND_COLOR,
};
use smithay::backend::allocator::Fourcc;
use smithay::backend::input::Keycode;
@@ -26,10 +26,12 @@ use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
};
use smithay::backend::renderer::element::utils::{
- select_dmabuf_feedback, Relocate, RelocateRenderElement,
+ select_dmabuf_feedback, CropRenderElement, Relocate, RelocateRenderElement,
+ RescaleRenderElement,
};
use smithay::backend::renderer::element::{
- default_primary_scanout_output_compare, Id, Kind, PrimaryScanoutOutput, RenderElementStates,
+ default_primary_scanout_output_compare, Element, Id, Kind, PrimaryScanoutOutput,
+ RenderElementStates,
};
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::sync::SyncPoint;
@@ -428,6 +430,7 @@ pub struct OutputState {
/// Solid color buffer for the background that we use instead of clearing to avoid damage
/// tracking issues and make screenshots easier.
pub background_buffer: SolidColorBuffer,
+ pub backdrop_buffer: SolidColorBuffer,
pub lock_render_state: LockRenderState,
pub lock_surface: Option<LockSurface>,
pub lock_color_buffer: SolidColorBuffer,
@@ -1465,11 +1468,22 @@ impl State {
background_color[3] = 1.;
let background_color = Color32F::from(background_color);
+ let mut backdrop_color = config
+ .map(|c| c.backdrop_color)
+ .unwrap_or(DEFAULT_BACKDROP_COLOR)
+ .to_array_unpremul();
+ backdrop_color[3] = 1.;
+ let backdrop_color = Color32F::from(backdrop_color);
+
if let Some(state) = self.niri.output_state.get_mut(output) {
if state.background_buffer.color() != background_color {
state.background_buffer.set_color(background_color);
recolored_outputs.push(output.clone());
}
+ if state.backdrop_buffer.color() != backdrop_color {
+ state.backdrop_buffer.set_color(backdrop_color);
+ recolored_outputs.push(output.clone());
+ }
}
}
@@ -2640,6 +2654,12 @@ impl Niri {
.to_array_unpremul();
background_color[3] = 1.;
+ let mut backdrop_color = c
+ .map(|c| c.backdrop_color)
+ .unwrap_or(DEFAULT_BACKDROP_COLOR)
+ .to_array_unpremul();
+ backdrop_color[3] = 1.;
+
// FIXME: fix winit damage on other transforms.
if name.connector == "winit" {
transform = Transform::Flipped180;
@@ -2673,6 +2693,7 @@ impl Niri {
last_drm_sequence: None,
frame_callback_sequence: 0,
background_buffer: SolidColorBuffer::new(size, background_color),
+ backdrop_buffer: SolidColorBuffer::new(size, backdrop_color),
lock_render_state,
lock_surface: None,
lock_color_buffer: SolidColorBuffer::new(size, CLEAR_COLOR_LOCKED),
@@ -2777,6 +2798,7 @@ impl Niri {
if let Some(state) = self.output_state.get_mut(output) {
state.background_buffer.resize(output_size);
+ state.backdrop_buffer.resize(output_size);
state.lock_color_buffer.resize(output_size);
if let Some(lock_surface) = &state.lock_surface {
@@ -2894,11 +2916,18 @@ impl Niri {
layers
.layers_on(layer)
.rev()
- .find_map(|layer| {
- let layer_pos_within_output =
- layers.layer_geometry(layer).unwrap().loc.to_f64();
+ .find_map(|layer_surface| {
+ let mut layer_pos_within_output =
+ layers.layer_geometry(layer_surface).unwrap().loc.to_f64();
+
+ // Background and bottom layers move together with the workspaces.
+ let mon = self.layout.monitor_for_output(output)?;
+ let (_, geo) = mon.workspace_under(pos_within_output)?;
+ layer_pos_within_output += geo.loc;
+
let surface_type = WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE;
- layer.surface_under(pos_within_output - layer_pos_within_output, surface_type)
+ layer_surface
+ .surface_under(pos_within_output - layer_pos_within_output, surface_type)
})
.is_some()
};
@@ -3001,20 +3030,29 @@ impl Niri {
layers
.layers_on(layer)
.rev()
- .find_map(|layer| {
- let layer_pos_within_output =
- layers.layer_geometry(layer).unwrap().loc.to_f64();
+ .find_map(|layer_surface| {
+ let mut layer_pos_within_output =
+ layers.layer_geometry(layer_surface).unwrap().loc.to_f64();
+
+ // Background and bottom layers move together with the workspaces.
+ if matches!(layer, Layer::Background | Layer::Bottom) {
+ let mon = self.layout.monitor_for_output(output)?;
+ let (_, geo) = mon.workspace_under(pos_within_output)?;
+ layer_pos_within_output += geo.loc;
+ }
+
let surface_type = if popup {
WindowSurfaceType::POPUP
} else {
WindowSurfaceType::TOPLEVEL
} | WindowSurfaceType::SUBSURFACE;
- layer
+
+ layer_surface
.surface_under(pos_within_output - layer_pos_within_output, surface_type)
.map(|(surface, pos_within_layer)| {
(
(surface, pos_within_layer.to_f64() + layer_pos_within_output),
- layer,
+ layer_surface,
)
})
})
@@ -3791,10 +3829,18 @@ impl Niri {
return elements;
}
- // Prepare the background element.
+ // Prepare the background elements.
let state = self.output_state.get(output).unwrap();
+ let background_buffer = state.background_buffer.clone();
let background = SolidColorRenderElement::from_buffer(
- &state.background_buffer,
+ &background_buffer,
+ (0, 0),
+ output_scale,
+ 1.,
+ Kind::Unspecified,
+ );
+ let backdrop = SolidColorRenderElement::from_buffer(
+ &state.backdrop_buffer,
(0, 0),
output_scale,
1.,
@@ -3811,8 +3857,8 @@ impl Niri {
.map(OutputRenderElements::from),
);
- // Add the background for outputs that were connected while the screenshot UI was open.
- elements.push(background);
+ // Add the backdrop for outputs that were connected while the screenshot UI was open.
+ elements.push(backdrop);
if self.debug_draw_opaque_regions {
draw_opaque_regions(&mut elements, output_scale);
@@ -3831,7 +3877,11 @@ impl Niri {
// Get monitor elements.
let mon = self.layout.monitor_for_output(output).unwrap();
- let monitor_elements: Vec<_> = mon.render_elements(renderer, target, focus_ring).collect();
+ let zoom = 1.;
+ let monitor_elements = Vec::from_iter(
+ mon.render_elements(renderer, target, focus_ring)
+ .map(|(geo, iter)| (geo, Vec::from_iter(iter))),
+ );
let int_move_elements: Vec<_> = self
.layout
.render_interactive_move_for_output(renderer, output, target)
@@ -3854,24 +3904,31 @@ impl Niri {
extend_from_layer(&mut layer_elems, Layer::Top);
let top_layer = layer_elems;
- // Collect all other layer-shell elements.
- let mut layer_elems = SplitElements::default();
- extend_from_layer(&mut layer_elems, Layer::Bottom);
- extend_from_layer(&mut layer_elems, Layer::Background);
-
// When rendering above the top layer, we put the regular monitor elements first.
// Otherwise, we will render all layer-shell pop-ups and the top layer on top.
if mon.render_above_top_layer() {
+ // Collect all other layer-shell elements.
+ let mut layer_elems = SplitElements::default();
+ extend_from_layer(&mut layer_elems, Layer::Bottom);
+ extend_from_layer(&mut layer_elems, Layer::Background);
+
elements.extend(
int_move_elements
.into_iter()
.map(OutputRenderElements::from),
);
- elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
+ elements.extend(
+ monitor_elements
+ .into_iter()
+ .flat_map(|(_ws_geo, iter)| iter)
+ .map(OutputRenderElements::from),
+ );
elements.extend(top_layer.into_iter().map(OutputRenderElements::from));
elements.extend(layer_elems.popups.drain(..).map(OutputRenderElements::from));
elements.extend(layer_elems.normal.drain(..).map(OutputRenderElements::from));
+
+ elements.push(OutputRenderElements::from(background));
} else {
elements.extend(top_layer.into_iter().map(OutputRenderElements::from));
@@ -3881,15 +3938,40 @@ impl Niri {
.map(OutputRenderElements::from),
);
- elements.extend(layer_elems.popups.drain(..).map(OutputRenderElements::from));
+ for (ws_geo, ws_elements) in monitor_elements {
+ // Collect all other layer-shell elements.
+ let mut layer_elems = SplitElements::default();
+ extend_from_layer(&mut layer_elems, Layer::Bottom);
+ extend_from_layer(&mut layer_elems, Layer::Background);
+
+ elements.extend(
+ layer_elems
+ .popups
+ .into_iter()
+ .filter_map(|elem| scale_relocate_crop(elem, output_scale, zoom, ws_geo))
+ .map(OutputRenderElements::from),
+ );
- elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
+ elements.extend(ws_elements.into_iter().map(OutputRenderElements::from));
- elements.extend(layer_elems.normal.drain(..).map(OutputRenderElements::from));
+ elements.extend(
+ layer_elems
+ .normal
+ .into_iter()
+ .filter_map(|elem| scale_relocate_crop(elem, output_scale, zoom, ws_geo))
+ .map(OutputRenderElements::from),
+ );
+
+ if let Some(elem) =
+ scale_relocate_crop(background.clone(), output_scale, zoom, ws_geo)
+ {
+ elements.push(OutputRenderElements::from(elem));
+ }
+ }
}
- // Then the background.
- elements.push(background);
+ // Then the backdrop.
+ elements.push(backdrop);
if self.debug_draw_opaque_regions {
draw_opaque_regions(&mut elements, output_scale);
@@ -5712,14 +5794,32 @@ impl ClientData for ClientState {
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
}
+fn scale_relocate_crop<E: Element>(
+ elem: E,
+ output_scale: Scale<f64>,
+ zoom: f64,
+ ws_geo: Rectangle<f64, Logical>,
+) -> Option<CropRenderElement<RelocateRenderElement<RescaleRenderElement<E>>>> {
+ let ws_geo = ws_geo.to_physical_precise_round(output_scale);
+ let elem = RescaleRenderElement::from_element(elem, Point::from((0, 0)), zoom);
+ let elem = RelocateRenderElement::from_element(elem, ws_geo.loc, Relocate::Relative);
+ CropRenderElement::from_element(elem, output_scale, ws_geo)
+}
+
niri_render_elements! {
OutputRenderElements<R> => {
Monitor = MonitorRenderElement<R>,
Tile = TileRenderElement<R>,
LayerSurface = LayerSurfaceRenderElement<R>,
+ RelocatedLayerSurface = CropRenderElement<RelocateRenderElement<RescaleRenderElement<
+ LayerSurfaceRenderElement<R>
+ >>>,
Wayland = WaylandSurfaceRenderElement<R>,
NamedPointer = MemoryRenderBufferRenderElement<R>,
SolidColor = SolidColorRenderElement,
+ RelocatedSolidColor = CropRenderElement<RelocateRenderElement<RescaleRenderElement<
+ SolidColorRenderElement
+ >>>,
ScreenshotUi = ScreenshotUiRenderElement,
Texture = PrimaryGpuTextureRenderElement,
// Used for the CPU-rendered panels.