shell: implement minimize

This commit is contained in:
Victoria Brekenfeld 2024-02-23 17:25:40 +01:00 committed by Victoria Brekenfeld
parent fffae1491d
commit 3eb7e5f82e
20 changed files with 1185 additions and 307 deletions

View file

@ -31,19 +31,19 @@ impl Window for CosmicSurface {
}
fn is_activated(&self) -> bool {
CosmicSurface::is_activated(self, false)
!self.is_minimized() && CosmicSurface::is_activated(self, false)
}
fn is_maximized(&self) -> bool {
CosmicSurface::is_maximized(self, false)
!self.is_minimized() && CosmicSurface::is_maximized(self, false)
}
fn is_fullscreen(&self) -> bool {
CosmicSurface::is_fullscreen(self, false)
!self.is_minimized() && CosmicSurface::is_fullscreen(self, false)
}
fn is_minimized(&self) -> bool {
false // TODO
CosmicSurface::is_minimized(self)
}
fn user_data(&self) -> &UserDataMap {

View file

@ -1,7 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1;
use smithay::{input::Seat, output::Output, reexports::wayland_server::DisplayHandle};
use smithay::{
desktop::{layer_map_for_output, WindowSurfaceType},
input::Seat,
output::Output,
reexports::wayland_server::DisplayHandle,
utils::{Point, Rectangle, Size},
};
use crate::{
shell::{CosmicSurface, Shell},
@ -22,10 +28,11 @@ impl ToplevelManagementHandler for State {
fn activate(
&mut self,
_dh: &DisplayHandle,
dh: &DisplayHandle,
window: &<Self as ToplevelInfoHandler>::Window,
seat: Option<Seat<Self>>,
) {
self.unminimize(dh, window);
for output in self
.common
.shell
@ -40,7 +47,11 @@ impl ToplevelManagementHandler for State {
.workspaces
.spaces_for_output(output)
.enumerate()
.find(|(_, w)| w.windows().any(|w| &w == window));
.find(|(_, w)| {
w.mapped()
.flat_map(|m| m.windows().map(|(s, _)| s))
.any(|w| &w == window)
});
if let Some((idx, workspace)) = maybe {
let seat = seat.unwrap_or(self.common.last_active_seat().clone());
let mapped = workspace
@ -77,12 +88,11 @@ impl ToplevelManagementHandler for State {
return;
};
let from_workspace = self
.common
.shell
.workspaces
.spaces()
.find(|w| w.windows().any(|w| &w == window));
let from_workspace = self.common.shell.workspaces.spaces().find(|w| {
w.mapped()
.flat_map(|m| m.windows().map(|(s, _)| s))
.any(|w| &w == window)
});
if let Some(from_workspace) = from_workspace {
let mapped = from_workspace
.mapped()
@ -101,12 +111,33 @@ impl ToplevelManagementHandler for State {
window: &<Self as ToplevelInfoHandler>::Window,
output: Option<Output>,
) {
let seat = self.common.last_active_seat().clone();
if let Some(mapped) = self.common.shell.element_for_surface(window).cloned() {
if let Some(output) = output {
let from = self
.common
.shell
.toplevel_management_state
.minimize_rectangle(&output, window);
let workspace = self.common.shell.workspaces.active_mut(&output);
workspace.fullscreen_request(window, None);
} else if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
workspace.fullscreen_request(window, None);
workspace.fullscreen_request(window, None, from, &seat);
} else if let Some((output, handle)) = self
.common
.shell
.space_for(&mapped)
.map(|workspace| (workspace.output.clone(), workspace.handle.clone()))
{
let from = self
.common
.shell
.toplevel_management_state
.minimize_rectangle(&output, window);
self.common
.shell
.workspaces
.space_for_handle_mut(&handle)
.unwrap()
.fullscreen_request(window, None, from, &seat);
}
}
}
@ -118,15 +149,32 @@ impl ToplevelManagementHandler for State {
) {
if let Some(mapped) = self.common.shell.element_for_surface(window).cloned() {
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let previous = workspace.unfullscreen_request(window);
assert!(previous.is_none());
if let Some((layer, previous_workspace)) = workspace.unfullscreen_request(window) {
let old_handle = workspace.handle.clone();
let new_workspace_handle = self
.common
.shell
.workspaces
.space_for_handle(&previous_workspace)
.is_some()
.then_some(previous_workspace)
.unwrap_or(old_handle); // if the workspace doesn't exist anymore, we can still remap on the right layer
self.common.shell.remap_unfullscreened_window(
mapped,
&old_handle,
&new_workspace_handle,
layer,
);
}
}
}
}
fn maximize(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) {
if let Some(mapped) = self.common.shell.element_for_surface(window).cloned() {
self.common.shell.maximize_request(&mapped);
let seat = self.common.last_active_seat().clone();
self.common.shell.maximize_request(&mapped, &seat);
}
}
@ -144,7 +192,8 @@ impl ToplevelManagementHandler for State {
fn unminimize(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) {
if let Some(mapped) = self.common.shell.element_for_surface(window).cloned() {
self.common.shell.unminimize_request(&mapped);
let seat = self.common.last_active_seat().clone();
self.common.shell.unminimize_request(&mapped, &seat);
}
}
}
@ -155,4 +204,43 @@ impl ManagementWindow for CosmicSurface {
}
}
pub trait ToplevelManagementExt {
fn minimize_rectangle(
&mut self,
output: &Output,
window: &CosmicSurface,
) -> Rectangle<i32, Local>;
}
impl ToplevelManagementExt for ToplevelManagementState {
fn minimize_rectangle(
&mut self,
output: &Output,
window: &CosmicSurface,
) -> Rectangle<i32, Local> {
self.rectangle_for(window)
.find_map(|(surface, relative)| {
let map = layer_map_for_output(output);
let layer = map.layer_for_surface(&surface, WindowSurfaceType::ALL);
layer.and_then(|s| map.layer_geometry(s)).map(|local| {
Rectangle::from_loc_and_size(
Point::from((local.loc.x + relative.loc.x, local.loc.y + relative.loc.y)),
relative.size,
)
})
})
.unwrap_or_else(|| {
let output_size = output.geometry().size;
Rectangle::from_loc_and_size(
Point::from((
(output_size.w / 2) - 100,
output_size.h - (output_size.h / 3) - 50,
)),
Size::from((200, 100)),
)
})
.as_local()
}
}
delegate_toplevel_management!(State);

View file

@ -145,14 +145,16 @@ impl XdgActivationHandler for State {
}
}
let target = element.into();
if workspace == &current_workspace.handle && in_current_workspace {
let target = element.into();
Shell::set_focus(self, Some(&target), &seat, None);
} else if let Some((w, _)) = target
.toplevel()
.and_then(|t| self.common.shell.workspace_for_surface(&t))
} else if let Some(w) = self
.common
.shell
.space_for(&element)
.map(|w| w.handle.clone())
{
Shell::append_focus_stack(self, Some(&target), &seat);
Shell::append_focus_stack(self, &element, &seat);
self.common.shell.set_urgent(&w);
}
}

View file

@ -30,7 +30,10 @@ use smithay::{
use std::cell::Cell;
use tracing::warn;
use super::{compositor::client_compositor_state, screencopy::PendingScreencopyBuffers};
use super::{
compositor::client_compositor_state, screencopy::PendingScreencopyBuffers,
toplevel_management::ToplevelManagementExt,
};
pub mod popup;
@ -180,7 +183,8 @@ impl XdgShellHandler for State {
.element_for_wl_surface(surface.wl_surface())
.cloned()
{
self.common.shell.maximize_request(&mapped)
let seat = self.common.last_active_seat().clone();
self.common.shell.maximize_request(&mapped, &seat)
}
}
@ -196,10 +200,8 @@ impl XdgShellHandler for State {
}
fn fullscreen_request(&mut self, surface: ToplevelSurface, output: Option<WlOutput>) {
let active_output = {
let seat = self.common.last_active_seat();
seat.active_output()
};
let seat = self.common.last_active_seat().clone();
let active_output = seat.active_output();
let output = output
.as_ref()
.and_then(Output::from_resource)
@ -211,6 +213,12 @@ impl XdgShellHandler for State {
.element_for_wl_surface(surface.wl_surface())
.cloned()
{
let from = self
.common
.shell
.toplevel_management_state
.minimize_rectangle(&output, &mapped.active_window());
if let Some(set) = self
.common
.shell
@ -254,9 +262,12 @@ impl XdgShellHandler for State {
let workspace = self.common.shell.active_space_mut(&output);
workspace.floating_layer.map(mapped.clone(), None);
workspace.fullscreen_request(
&mapped.active_window(),
Some((ManagedLayer::Sticky, workspace_handle)),
from,
&seat,
);
} else if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
if workspace.output != output {
@ -303,13 +314,19 @@ impl XdgShellHandler for State {
let workspace = self.common.shell.active_space_mut(&output);
workspace.floating_layer.map(mapped.clone(), None);
workspace.fullscreen_request(&mapped.active_window(), Some((layer, handle)));
workspace.fullscreen_request(
&mapped.active_window(),
Some((layer, handle)),
from,
&seat,
);
} else {
let (window, _) = mapped
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface()))
.unwrap();
workspace.fullscreen_request(&window, None)
workspace.fullscreen_request(&window, None, from, &seat)
}
}
} else {

View file

@ -32,11 +32,11 @@ impl Shell {
if let Some(elem) = self.element_for_wl_surface(&parent) {
let (mut element_geo, output, is_tiled) =
if let Some(workspace) = self.space_for(elem) {
let Some(elem_geo) = workspace.element_geometry(elem) else {
return;
};
(
workspace
.element_geometry(elem)
.unwrap()
.to_global(workspace.output()),
elem_geo.to_global(workspace.output()),
workspace.output.clone(),
workspace.is_tiled(elem),
)

View file

@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::{collections::HashMap, sync::Mutex};
use std::sync::Mutex;
use smithay::{
output::Output,
reexports::wayland_server::{
backend::{ClientId, GlobalId},
protocol::wl_surface::WlSurface,
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak,
},
utils::{user_data::UserDataMap, IsAlive, Logical, Rectangle},
};
@ -53,7 +53,7 @@ pub(super) struct ToplevelStateInner {
instances: Vec<ZcosmicToplevelHandleV1>,
outputs: Vec<Output>,
workspaces: Vec<WorkspaceHandle>,
pub(super) rectangles: HashMap<ClientId, (WlSurface, Rectangle<i32, Logical>)>,
pub(super) rectangles: Vec<(Weak<WlSurface>, Rectangle<i32, Logical>)>,
}
pub(super) type ToplevelState = Mutex<ToplevelStateInner>;
@ -255,7 +255,9 @@ where
.unwrap()
.lock()
.unwrap();
state.rectangles.retain(|_, (surface, _)| surface.alive());
state
.rectangles
.retain(|(surface, _)| surface.upgrade().is_ok());
if window.alive() {
std::mem::drop(state);
for instance in &self.instances {

View file

@ -105,12 +105,24 @@ impl ToplevelManagementState {
pub fn rectangle_for(
&mut self,
window: &impl ManagementWindow,
client: &ClientId,
) -> Option<(WlSurface, Rectangle<i32, Logical>)> {
) -> impl Iterator<Item = (WlSurface, Rectangle<i32, Logical>)> {
if let Some(state) = window.user_data().get::<ToplevelState>() {
state.lock().unwrap().rectangles.get(client).cloned()
let mut state = state.lock().unwrap();
state
.rectangles
.retain(|(surface, _)| surface.upgrade().is_ok());
Some(
state
.rectangles
.iter()
.map(|(surface, rect)| (surface.upgrade().unwrap(), *rect))
.collect::<Vec<_>>()
.into_iter(),
)
.into_iter()
.flatten()
} else {
None
None.into_iter().flatten()
}
}
@ -217,18 +229,15 @@ where
window_from_handle::<<D as ToplevelInfoHandler>::Window>(toplevel).unwrap();
if let Some(toplevel_state) = window.user_data().get::<ToplevelState>() {
let mut toplevel_state = toplevel_state.lock().unwrap();
if let Some(client) = surface.client() {
if width == 0 && height == 0 {
toplevel_state.rectangles.remove(&client.id());
} else {
toplevel_state.rectangles.insert(
client.id(),
(
surface,
Rectangle::from_loc_and_size((x, y), (width, height)),
),
);
}
if width == 0 && height == 0 {
toplevel_state
.rectangles
.retain(|(s, _)| s.id() != surface.id());
} else {
toplevel_state.rectangles.push((
surface.downgrade(),
Rectangle::from_loc_and_size((x, y), (width, height)),
));
}
}
}
@ -256,7 +265,13 @@ where
{
for toplevel in state.toplevel_info_state_mut().toplevels.iter() {
if let Some(toplevel_state) = toplevel.user_data().get::<ToplevelState>() {
toplevel_state.lock().unwrap().rectangles.remove(&client);
toplevel_state.lock().unwrap().rectangles.retain(|(s, _)| {
s.upgrade()
.ok()
.and_then(|s| s.client())
.map(|c| c.id() != client)
.unwrap_or(false)
});
}
}
}