From f1f51e171431e020fa91cfff59198b3bdf28c84d Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 22 Apr 2022 15:18:28 +0200 Subject: [PATCH] shell: Handle fullscreen surfaces --- config.ron | 2 + src/backend/render/mod.rs | 171 +++++++++++++++++++++++++-- src/config.rs | 1 + src/input/mod.rs | 207 +++++++++++++++++++++------------ src/shell/handler.rs | 25 ++++ src/shell/layout/tiling/mod.rs | 3 + src/shell/workspace.rs | 93 ++++++++++++++- 7 files changed, 411 insertions(+), 91 deletions(-) diff --git a/config.ron b/config.ron index f645f455..a0ef7208 100644 --- a/config.ron +++ b/config.ron @@ -34,12 +34,14 @@ //TODO: automatic orientation with Logo+o toggling (modifiers: [Logo], key: "v"): Orientation(Vertical), (modifiers: [Logo], key: "o"): Orientation(Horizontal), + (modifiers: [Logo, Shift], key: "f"): Fullscreen, //TODO: ability to select default web browser (modifiers: [Logo], key: "b"): Spawn("firefox"), //TODO: ability to select default file browser (modifiers: [Logo], key: "f"): Spawn("nautilus"), //TODO: ability to select default terminal (modifiers: [Logo], key: "t"): Spawn("gnome-terminal"), + (modifiers: [Ctrl], key: "t"): Spawn("wofi --show drun"), }, workspace_mode: OutputBound, ) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 667d76d6..c87fe816 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -14,12 +14,18 @@ use smithay::{ renderer::{ gles2::{Gles2Renderbuffer, Gles2Renderer, Gles2Texture}, multigpu::{egl::EglGlesBackend, Error as MultiError, MultiFrame, MultiRenderer}, - ImportAll, Renderer, TextureFilter, + ImportAll, Renderer, Frame, TextureFilter, }, }, - desktop::space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree}, - utils::{Logical, Point, Rectangle}, - wayland::output::Output, + desktop::{ + space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree}, + draw_window, draw_layer_surface, Window, layer_map_for_output, utils::damage_from_surface_tree, + }, + utils::{Logical, Point, Rectangle, Transform}, + wayland::{ + shell::wlr_layer::Layer as WlrLayer, + output::Output, + }, }; mod cursor; @@ -29,6 +35,8 @@ pub type GlMultiRenderer<'a> = MultiRenderer<'a, 'a, EglGlesBackend, EglGlesBackend, Gles2Renderbuffer>; pub type GlMultiFrame = MultiFrame; +static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; + smithay::custom_elements! { pub CustomElem<=Gles2Renderer>; SurfaceTree=SurfaceTree, @@ -97,7 +105,7 @@ impl AsGles2Renderer for GlMultiRenderer<'_> { } pub fn render_output( - _gpu: Option<&DrmNode>, + gpu: Option<&DrmNode>, renderer: &mut R, age: u8, state: &mut Common, @@ -117,7 +125,50 @@ where fps.start(); } - #[allow(unused_mut)] + let workspace = state.shell.active_space(output); + let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned(); + let res = if let Some(window) = maybe_fullscreen_window { + #[cfg(not(feature = "debug"))] + { + render_fullscreen(gpu, renderer, window, state, output, hardware_cursor) + } + #[cfg(feature = "debug")] + { + render_fullscreen(gpu, renderer, window, state, output, hardware_cursor, fps) + } + } else { + #[cfg(not(feature = "debug"))] + { + render_desktop(gpu, renderer, age, state, output, hardware_cursor) + } + #[cfg(feature = "debug")] + { + render_desktop(gpu, renderer, age, state, output, hardware_cursor, fps) + } + }; + + #[cfg(feature = "debug")] + { + fps.end(); + } + + res +} + +fn render_desktop( + _gpu: Option<&DrmNode>, + renderer: &mut R, + age: u8, + state: &mut Common, + output: &Output, + hardware_cursor: bool, + #[cfg(feature = "debug")] fps: &mut Fps, +) -> Result>>, RenderError> +where + R: Renderer + ImportAll + AsGles2Renderer, + ::TextureId: Clone + 'static, + CustomElem: RenderElement, +{ let mut custom_elements = Vec::::new(); #[cfg(feature = "debug")] @@ -162,18 +213,116 @@ where } } - let res = state.shell.active_space_mut(output).space.render_output( + state.shell.active_space_mut(output).space.render_output( renderer, &output, age as usize, - [0.153, 0.161, 0.165, 1.0], + CLEAR_COLOR, &*custom_elements, - ); + ) +} + +fn render_fullscreen( + _gpu: Option<&DrmNode>, + renderer: &mut R, + window: Window, + state: &mut Common, + output: &Output, + hardware_cursor: bool, + #[cfg(feature = "debug")] fps: &mut Fps, +) -> Result>>, RenderError> +where + R: Renderer + ImportAll + AsGles2Renderer, + ::TextureId: Clone + 'static, + CustomElem: RenderElement, +{ + let transform = Transform::from(output.current_transform()); + let mode = output.current_mode().unwrap(); + let scale = output.current_scale().fractional_scale(); + let output_geo = state.shell.output_geometry(output); + + let mut custom_elements = Vec::::new(); #[cfg(feature = "debug")] { - fps.end(); + let fps_overlay = fps_ui(_gpu, state, fps, Rectangle::from_loc_and_size((0, 0), output_geo.size), scale); + custom_elements.push(fps_overlay.into()); + } + + for seat in &state.seats { + let pointer = match seat.get_pointer() { + Some(ptr) => ptr, + None => continue, + }; + let location = state + .shell + .space_relative_output_geometry(pointer.current_location().to_i32_round(), output); + + if let Some(cursor) = cursor::draw_cursor( + renderer.as_gles2(), + seat, + location, + &state.start_time, + !hardware_cursor, + ) { + custom_elements.push(cursor) + } } - res + renderer + .render(mode.size, transform, |renderer, frame| { + let mut damage = window.accumulated_damage(None); + frame.clear( + CLEAR_COLOR, + &[Rectangle::from_loc_and_size((0, 0), mode.size).to_f64()], + )?; + draw_window( + renderer, + frame, + &window, + scale, + (0, 0), + &[Rectangle::from_loc_and_size( + (0, 0), + mode.size.to_f64().to_logical(scale).to_i32_round(), + )], + &slog_scope::logger(), + )?; + let layer_map = layer_map_for_output(output); + for layer_surface in layer_map.layers_on(WlrLayer::Overlay) { + let geo = layer_map.layer_geometry(&layer_surface).unwrap(); + draw_layer_surface( + renderer, + frame, + layer_surface, + scale, + geo.loc, + &[Rectangle::from_loc_and_size((0, 0), geo.size)], + &slog_scope::logger(), + )?; + if let Some(surface) = layer_surface.get_surface() { + damage.extend(damage_from_surface_tree(surface, geo.loc, None)); + } + } + for elem in custom_elements { + let geo = elem.geometry(); + let location = geo.loc - output_geo.loc; + let elem_damage = elem.accumulated_damage(None); + elem.draw( + renderer, + frame, + scale, + location, + &[Rectangle::from_loc_and_size((0, 0), geo.size)], + &slog_scope::logger(), + )?; + damage.extend(elem_damage.into_iter().map(|mut rect| { + rect.loc += geo.loc; + rect + })) + } + Ok(Some(damage)) + }) + .and_then(std::convert::identity) + .map_err(RenderError::::Rendering) } diff --git a/src/config.rs b/src/config.rs index 63862f8a..e9a07476 100644 --- a/src/config.rs +++ b/src/config.rs @@ -471,5 +471,6 @@ pub enum Action { MoveToWorkspace(u8), Focus(FocusDirection), Orientation(crate::shell::layout::Orientation), + Fullscreen, Spawn(String), } diff --git a/src/input/mod.rs b/src/input/mod.rs index c62ecf53..37956bfc 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{config::Action, state::Common}; +use crate::{config::Action, state::Common, shell::Workspace}; use smithay::{ backend::input::{Device, DeviceCapability, InputBackend, InputEvent, KeyState}, desktop::{layer_map_for_output, Kind, Space, WindowSurfaceType}, @@ -304,6 +304,14 @@ impl Common { self.seats.iter(), ); } + Action::Fullscreen => { + let current_output = active_output(seat, &self); + let workspace = self.shell.active_space_mut(¤t_output); + let focused_window = workspace.focus_stack(seat).last(); + if let Some(window) = focused_window { + workspace.fullscreen_toggle(&window, ¤t_output); + } + } Action::Orientation(orientation) => { let output = active_output(seat, &self); self.shell.set_orientation(&seat, &output, *orientation); @@ -369,7 +377,7 @@ impl Common { relative_pos, &output, output_geometry, - &workspace.space, + &workspace, ); handle_window_movement( under.as_ref().map(|(s, _)| s), @@ -413,7 +421,7 @@ impl Common { relative_pos, &output, geometry, - &workspace.space, + &workspace, ); handle_window_movement( under.as_ref().map(|(s, _)| s), @@ -488,49 +496,73 @@ impl Common { let layers = layer_map_for_output(&output); let mut under = None; - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) - { - if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer - .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, _)| s); - } - } else if let Some(window) = - workspace.space.window_under(relative_pos).cloned() - { - let window_loc = - workspace.space.window_location(&window).unwrap(); - under = window - .surface_under( - relative_pos - window_loc.to_f64(), + if let Some(window) = workspace.get_fullscreen(&output) { + if let Some(layer) = layers + .layer_under(WlrLayer::Overlay, relative_pos) + { + if layer.can_receive_keyboard_focus() { + let layer_loc = + layers.layer_geometry(layer).unwrap().loc; + under = layer + .surface_under( + pos - output_geo.loc.to_f64() + - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .map(|(s, _)| s); + } + } else { + under = window.surface_under( + pos - output_geo.loc.to_f64(), WindowSurfaceType::TOPLEVEL - | WindowSurfaceType::SUBSURFACE, - ) - .map(|(s, _)| s); - } else if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, pos) - .or_else(|| layers.layer_under(WlrLayer::Background, pos)) - { - if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer + | WindowSurfaceType::SUBSURFACE + ).map(|(s, _)| s); + } + } else { + if let Some(layer) = layers + .layer_under(WlrLayer::Overlay, relative_pos) + .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) + { + if layer.can_receive_keyboard_focus() { + let layer_loc = + layers.layer_geometry(layer).unwrap().loc; + under = layer + .surface_under( + pos - output_geo.loc.to_f64() + - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .map(|(s, _)| s); + } + } else if let Some(window) = + workspace.space.window_under(relative_pos).cloned() + { + let window_loc = + workspace.space.window_location(&window).unwrap(); + under = window .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), - WindowSurfaceType::ALL, + relative_pos - window_loc.to_f64(), + WindowSurfaceType::TOPLEVEL + | WindowSurfaceType::SUBSURFACE, ) .map(|(s, _)| s); - } - }; + } else if let Some(layer) = layers + .layer_under(WlrLayer::Bottom, pos) + .or_else(|| layers.layer_under(WlrLayer::Background, pos)) + { + if layer.can_receive_keyboard_focus() { + let layer_loc = + layers.layer_geometry(layer).unwrap().loc; + under = layer + .surface_under( + pos - output_geo.loc.to_f64() + - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .map(|(s, _)| s); + } + }; + } self.set_focus(under.as_ref(), seat, Some(serial)); } @@ -645,44 +677,67 @@ impl Common { relative_pos: Point, output: &Output, output_geo: Rectangle, - space: &Space, + workspace: &Workspace, ) -> Option<(WlSurface, Point)> { let layers = layer_map_for_output(output); - - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) - } else if let Some(window) = space.window_under(relative_pos) { - let window_loc = space.window_location(window).unwrap(); - window - .surface_under(relative_pos - window_loc.to_f64(), WindowSurfaceType::ALL) - .map(|(s, loc)| { - ( - s, - loc + window_loc - (relative_pos - global_pos).to_i32_round(), + if let Some(window) = workspace.get_fullscreen(output) { + if let Some(layer) = layers + .layer_under(WlrLayer::Overlay, relative_pos) + .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) + { + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + layer + .surface_under( + global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), + WindowSurfaceType::ALL, ) - }) - } else if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, relative_pos) - .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos)) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) + .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) + } else { + window + .surface_under(global_pos - output_geo.loc.to_f64(), WindowSurfaceType::ALL) + .map(|(s, loc)| { + ( + s, + loc + output_geo.loc, + ) + }) + } } else { - None + if let Some(layer) = layers + .layer_under(WlrLayer::Overlay, relative_pos) + .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) + { + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + layer + .surface_under( + global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) + } else if let Some(window) = workspace.space.window_under(relative_pos) { + let window_loc = workspace.space.window_location(window).unwrap(); + window + .surface_under(relative_pos - window_loc.to_f64(), WindowSurfaceType::ALL) + .map(|(s, loc)| { + ( + s, + loc + window_loc - (relative_pos - global_pos).to_i32_round(), + ) + }) + } else if let Some(layer) = layers + .layer_under(WlrLayer::Bottom, relative_pos) + .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos)) + { + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + layer + .surface_under( + global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) + } else { + None + } } } } diff --git a/src/shell/handler.rs b/src/shell/handler.rs index 8fd17a6d..33007c9d 100644 --- a/src/shell/handler.rs +++ b/src/shell/handler.rs @@ -213,6 +213,31 @@ pub fn init_shell(config: &Config, display: &mut Display) -> super::Shell { .set(Some(grab)); } } + XdgRequest::Fullscreen { surface, output } => { + let output = output + .as_ref() + .and_then(Output::from_resource) + .unwrap_or_else(|| { + let seat = &state.last_active_seat; + active_output(seat, &*state) + }); + if let Some(surface) = surface.get_surface() { + if let Some(workspace) = state.shell.space_for_surface_mut(surface) { + let window = + workspace.space.window_for_surface(surface).unwrap().clone(); + workspace.fullscreen_request(&window, &output) + } + } + } + XdgRequest::UnFullscreen { surface } => { + if let Some(surface) = surface.get_surface() { + if let Some(workspace) = state.shell.space_for_surface_mut(surface) { + let window = + workspace.space.window_for_surface(surface).unwrap().clone(); + workspace.unfullscreen_request(&window) + } + } + } _ => { /*TODO*/ } } }, diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index a93e028f..1385b25c 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -529,6 +529,9 @@ impl TilingLayout { if let Some(geo) = geo { #[allow(irrefutable_let_patterns)] if let Kind::Xdg(xdg) = &window.toplevel() { + if xdg.current_state().map(|state| state.states.contains(XdgState::Fullscreen)).unwrap_or(false) { + continue; + } let ret = xdg.with_pending_state(|state| { state.size = Some( (geo.size.w - inner * 2, geo.size.h - inner * 2) diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index deb32817..c698dcbe 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,8 +1,8 @@ use super::{layout, Layout}; use indexmap::IndexSet; use smithay::{ - desktop::{LayerSurface, Space, Window}, - reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::ResizeEdge, + desktop::{LayerSurface, Space, Window, Kind}, + reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::{self, ResizeEdge}, wayland::{ output::Output, seat::{PointerGrabStartData, Seat}, @@ -53,6 +53,7 @@ pub struct Workspace { pub(super) layout: Box, pub(super) pending_windows: Vec<(Window, Seat)>, pub(super) pending_layers: Vec<(LayerSurface, Output, Seat)>, + pub fullscreen: HashMap, } impl Workspace { @@ -63,6 +64,7 @@ impl Workspace { layout: layout::new_default_layout(), pending_windows: Vec::new(), pending_layers: Vec::new(), + fullscreen: HashMap::new(), } } @@ -90,9 +92,10 @@ impl Workspace { pub fn pending_window(&mut self, window: Window, seat: &Seat) { self.pending_windows.push((window, seat.clone())); } - + pub fn pending_layer(&mut self, layer: LayerSurface, output: &Output, seat: &Seat) { - self.pending_layers.push((layer, output.clone(), seat.clone())); + self.pending_layers + .push((layer, output.clone(), seat.clone())); } pub(super) fn map_window(&mut self, window: &Window, seat: &Seat) { @@ -114,6 +117,18 @@ impl Workspace { } pub fn refresh(&mut self) { + let outputs = self.space.outputs().collect::>(); + let dead_output_windows = self.fullscreen + .iter() + .filter(|(name, _)| + !outputs.iter().any(|o| o.name() == **name) + ) + .map(|(_, w)| w) + .cloned() + .collect::>(); + for window in dead_output_windows { + self.unfullscreen_request(&window); + } self.layout.refresh(&mut self.space); self.space.refresh(); } @@ -147,6 +162,9 @@ impl Workspace { } pub fn maximize_request(&mut self, window: &Window, output: &Output) { + if self.fullscreen.values().any(|w| w == window) { + return; + } self.layout .maximize_request(&mut self.space, window, output) } @@ -158,6 +176,9 @@ impl Workspace { serial: Serial, start_data: PointerGrabStartData, ) { + if self.fullscreen.values().any(|w| w == window) { + return; + } self.layout .move_request(&mut self.space, window, seat, serial, start_data) } @@ -170,7 +191,71 @@ impl Workspace { start_data: PointerGrabStartData, edges: ResizeEdge, ) { + if self.fullscreen.values().any(|w| w == window) { + return; + } self.layout .resize_request(&mut self.space, window, seat, serial, start_data, edges) } + + pub fn fullscreen_request(&mut self, window: &Window, output: &Output) { + if self.fullscreen.contains_key(&output.name()) { + return; + } + + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + if xdg.get_surface().is_some() { + let ret = xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Fullscreen); + state.size = Some( + output + .current_mode() + .map(|m| m.size) + .unwrap_or((0, 0).into()) + .to_logical(output.current_scale().integer_scale()), + ); + }); + + if ret.is_ok() { + xdg.send_configure(); + self.fullscreen.insert(output.name(), window.clone()); + } + } + } + } + + pub fn unfullscreen_request(&mut self, window: &Window) { + if self.fullscreen.values().any(|w| w == window) { + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + if xdg.get_surface().is_some() { + let ret = xdg.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Fullscreen); + state.size = None; + }); + if ret.is_ok() { + self.layout.refresh(&mut self.space); + xdg.send_configure(); + } + } + } + self.fullscreen.retain(|_, w| w != window); + } + } + + pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) { + if self.fullscreen.contains_key(&output.name()) { + self.unfullscreen_request(window) + } else { + self.fullscreen_request(window, output) + } + } + + pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> { + if !self.space.outputs().any(|o| o == output) { + return None; + } + self.fullscreen.get(&output.name()).filter(|w| w.toplevel().get_surface().is_some()) + } }