Merge pull request #46 from pop-os/feature/new-shell

new shell
This commit is contained in:
Victoria Brekenfeld 2022-11-23 16:11:37 +01:00 committed by GitHub
commit 3161aab097
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 10856 additions and 5418 deletions

775
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@ slog-stdlog = "4.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sendfd = "0.4.1"
egui = { version = "0.18.1", optional = true }
egui = { version = "0.19.0", optional = true }
edid-rs = { version = "0.1" }
png = "0.17.5"
lazy_static = "1.4.0"
@ -28,28 +28,27 @@ xkbcommon = "0.4"
indexmap = "1.8.0"
xdg = "^2.1"
ron = "0.7"
atomic_float = "0.1"
libsystemd = "0.5"
wayland-backend = "=0.1.0-beta.10"
wayland-scanner = "=0.30.0-beta.10"
wayland-backend = "=0.1.0-beta.13"
wayland-scanner = "=0.30.0-beta.13"
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] }
[dependencies.smithay]
version = "0.3"
git = "https://github.com/Smithay/smithay.git"
rev = "606d2d5c"
rev = "b297c93edc"
default-features = false
features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_gl", "renderer_multi", "wayland_frontend", "slog-stdlog"]
features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_glow", "renderer_multi", "wayland_frontend", "slog-stdlog"]
[dependencies.smithay-egui]
git = "https://github.com/Smithay/smithay-egui.git"
rev = "939febaf"
rev = "9fe1fa5e01"
features = ["svg"]
optional = true
[features]
default = []
debug = ["egui", "smithay-egui"]
experimental = []
[profile.dev]
lto = "thin"
@ -62,4 +61,4 @@ debug = true
lto = "fat"
[patch."https://github.com/Smithay/smithay.git"]
smithay = { git = "https://github.com/Smithay//smithay", rev = "625cbca5" }
smithay = { git = "https://github.com/pop-os/smithay", rev = "c8aaa059e8" }

View file

@ -1,51 +1,89 @@
(
key_bindings: {
(modifiers: [Logo, Shift], key: "Escape"): Terminate,
(modifiers: [Logo], key: "Escape"): Debug,
(modifiers: [Logo], key: "q"): Close,
(modifiers: [Logo], key: "1"): Workspace(1),
(modifiers: [Logo], key: "2"): Workspace(2),
(modifiers: [Logo], key: "3"): Workspace(3),
(modifiers: [Logo], key: "4"): Workspace(4),
(modifiers: [Logo], key: "5"): Workspace(5),
(modifiers: [Logo], key: "6"): Workspace(6),
(modifiers: [Logo], key: "7"): Workspace(7),
(modifiers: [Logo], key: "8"): Workspace(8),
(modifiers: [Logo], key: "9"): Workspace(9),
(modifiers: [Logo], key: "0"): Workspace(0),
(modifiers: [Logo, Shift], key: "1"): MoveToWorkspace(1),
(modifiers: [Logo, Shift], key: "2"): MoveToWorkspace(2),
(modifiers: [Logo, Shift], key: "3"): MoveToWorkspace(3),
(modifiers: [Logo, Shift], key: "4"): MoveToWorkspace(4),
(modifiers: [Logo, Shift], key: "5"): MoveToWorkspace(5),
(modifiers: [Logo, Shift], key: "6"): MoveToWorkspace(6),
(modifiers: [Logo, Shift], key: "7"): MoveToWorkspace(7),
(modifiers: [Logo, Shift], key: "8"): MoveToWorkspace(8),
(modifiers: [Logo, Shift], key: "9"): MoveToWorkspace(9),
(modifiers: [Logo, Shift], key: "0"): MoveToWorkspace(0),
(modifiers: [Logo], key: "Left"): Focus(Left),
(modifiers: [Logo], key: "Right"): Focus(Right),
(modifiers: [Logo], key: "Up"): Focus(Up),
(modifiers: [Logo], key: "Down"): Focus(Down),
(modifiers: [Logo], key: "h"): Focus(Left),
(modifiers: [Logo], key: "j"): Focus(Down),
(modifiers: [Logo], key: "k"): Focus(Up),
(modifiers: [Logo], key: "l"): Focus(Right),
//TODO: automatic orientation with Logo+o toggling
(modifiers: [Logo], key: "v"): Orientation(Vertical),
(modifiers: [Logo], key: "o"): Orientation(Horizontal),
(modifiers: [Logo], key: "y"): ToggleTiling,
(modifiers: [Logo], key: "g"): ToggleWindowFloating,
(modifiers: [Logo, Shift], key: "f"): Fullscreen,
(modifiers: [Logo, Shift], key: "s"): Screenshot,
(modifiers: [Super, Shift], key: "Escape"): Terminate,
(modifiers: [Super], key: "Escape"): Debug,
(modifiers: [Super], key: "q"): Close,
(modifiers: [Super], key: "1"): Workspace(1),
(modifiers: [Super], key: "2"): Workspace(2),
(modifiers: [Super], key: "3"): Workspace(3),
(modifiers: [Super], key: "4"): Workspace(4),
(modifiers: [Super], key: "5"): Workspace(5),
(modifiers: [Super], key: "6"): Workspace(6),
(modifiers: [Super], key: "7"): Workspace(7),
(modifiers: [Super], key: "8"): Workspace(8),
(modifiers: [Super], key: "9"): Workspace(9),
(modifiers: [Super], key: "0"): LastWorkspace,
(modifiers: [Super, Shift], key: "1"): MoveToWorkspace(1),
(modifiers: [Super, Shift], key: "2"): MoveToWorkspace(2),
(modifiers: [Super, Shift], key: "3"): MoveToWorkspace(3),
(modifiers: [Super, Shift], key: "4"): MoveToWorkspace(4),
(modifiers: [Super, Shift], key: "5"): MoveToWorkspace(5),
(modifiers: [Super, Shift], key: "6"): MoveToWorkspace(6),
(modifiers: [Super, Shift], key: "7"): MoveToWorkspace(7),
(modifiers: [Super, Shift], key: "8"): MoveToWorkspace(8),
(modifiers: [Super, Shift], key: "9"): MoveToWorkspace(9),
(modifiers: [Super, Shift], key: "0"): MoveToLastWorkspace,
// TODO: Depends on workspace orientation
(modifiers: [Super, Ctrl], key: "Right"): NextWorkspace,
(modifiers: [Super, Ctrl], key: "Left"): PreviousWorkspace,
(modifiers: [Super, Ctrl, Shift], key: "Right"): MoveToNextWorkspace,
(modifiers: [Super, Ctrl, Shift], key: "Left"): MoveToPreviousWorkspace,
(modifiers: [Super, Ctrl], key: "l"): NextWorkspace,
(modifiers: [Super, Ctrl], key: "h"): PreviousWorkspace,
(modifiers: [Super, Ctrl, Shift], key: "l"): MoveToNextWorkspace,
(modifiers: [Super, Ctrl, Shift], key: "h"): MoveToPreviousWorkspace,
(modifiers: [Super, Ctrl], key: "Down"): NextOutput,
(modifiers: [Super, Ctrl], key: "Up"): PreviousOutput,
(modifiers: [Super, Ctrl, Alt], key: "Down"): NextOutput,
(modifiers: [Super, Ctrl, Alt], key: "Up"): PreviousOutput,
(modifiers: [Super, Ctrl], key: "j"): NextOutput,
(modifiers: [Super, Ctrl], key: "k"): PreviousOutput,
(modifiers: [Super, Ctrl, Alt], key: "j"): NextOutput,
(modifiers: [Super, Ctrl, Alt], key: "k"): PreviousOutput,
(modifiers: [Super], key: "Period"): NextOutput,
(modifiers: [Super], key: "Comma"): PreviousOutput,
(modifiers: [Super, Shift], key: "Period"): MoveToNextOutput,
(modifiers: [Super, Shift], key: "Comma"): MoveToPreviousOutput,
(modifiers: [Super], key: "Left"): Focus(Left),
(modifiers: [Super], key: "Right"): Focus(Right),
(modifiers: [Super], key: "Up"): Focus(Up),
(modifiers: [Super], key: "Down"): Focus(Down),
(modifiers: [Super], key: "h"): Focus(Left),
(modifiers: [Super], key: "j"): Focus(Down),
(modifiers: [Super], key: "k"): Focus(Up),
(modifiers: [Super], key: "l"): Focus(Right),
(modifiers: [Super, Shift], key: "Left"): Move(Left),
(modifiers: [Super, Shift], key: "Right"): Move(Right),
(modifiers: [Super, Shift], key: "Up"): Move(Up),
(modifiers: [Super, Shift], key: "Down"): Move(Down),
(modifiers: [Super, Shift], key: "h"): Move(Left),
(modifiers: [Super, Shift], key: "j"): Move(Down),
(modifiers: [Super, Shift], key: "k"): Move(Up),
(modifiers: [Super, Shift], key: "l"): Move(Right),
(modifiers: [Super], key: "o"): ToggleOrientation,
(modifiers: [Super], key: "y"): ToggleTiling,
(modifiers: [Super], key: "g"): ToggleWindowFloating,
(modifiers: [Super], key: "m"): Maximize,
//TODO: ability to select default web browser
(modifiers: [Logo], key: "b"): Spawn("firefox"),
(modifiers: [Super], key: "b"): Spawn("firefox"),
//TODO: ability to select default file browser
(modifiers: [Logo], key: "f"): Spawn("nautilus"),
(modifiers: [Super], key: "f"): Spawn("nautilus"),
//TODO: ability to select default terminal
(modifiers: [Logo], key: "t"): Spawn("gnome-terminal"),
(modifiers: [Logo], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"),
(modifiers: [Logo], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"),
(modifiers: [Super], key: "t"): Spawn("gnome-terminal"),
(modifiers: [Super], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"),
(modifiers: [Super], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"),
(modifiers: [], key: "XF86AudioRaiseVolume"): Spawn("amixer sset Master 5%+"),
(modifiers: [], key: "XF86AudioLowerVolume"): Spawn("amixer sset Master 5%-"),
(modifiers: [], key: "XF86AudioMute"): Spawn("amixer sset Master toggle"),
@ -53,5 +91,6 @@
(modifiers: [], key: "XF86MonBrightnessDown"): Spawn("busctl --user call com.system76.CosmicSettingsDaemon /com/system76/CosmicSettingsDaemon com.system76.CosmicSettingsDaemon DecreaseDisplayBrightness"),
},
workspace_mode: OutputBound,
workspace_amount: Dynamic,
floating_default: false,
)

1
resources/icons/amd.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M0 51.413v-12.57l17.938-17.938v25.137h25.137l-17.938 17.94H0zm55.25 3.343l-8.673-8.674v-28.6h-28.6L.512.018h63.452l.007 31.412.026 31.576a.78.78 0 0 1-.027.294c-.034.096-2.3-2.123-8.72-8.543z" fill="#00a774"/></svg>

After

Width:  |  Height:  |  Size: 287 B

12
resources/icons/intel.svg Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 395.4 155.9" style="enable-background:new 0 0 395.4 155.9;" xml:space="preserve" sodipodi:docname="Intel_logo_(2020,_dark_blue).svg" inkscape:version="1.1.2 (b8e25be8, 2022-02-05)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><defs id="defs1066"/><sodipodi:namedview id="namedview1064" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" showgrid="false" inkscape:zoom="0.86470488" inkscape:cx="164.21788" inkscape:cy="-98.299434" inkscape:window-width="1701" inkscape:window-height="742" inkscape:window-x="0" inkscape:window-y="25" inkscape:window-maximized="0" inkscape:current-layer="Layer_1"/>
<style type="text/css" id="style1053">
.st0{fill:#0068B5;}
</style>
<rect x="4.7" y="5.2" class="st0" width="28.1" height="28.1" id="rect1055" style="fill:#00c7fd;fill-opacity:1"/>
<g id="g1061">
<path d="M32.1,151.6V50.4H5.5v101.2H32.1z M208.9,152.6v-24.8c-3.9,0-7.2-0.2-9.6-0.6c-2.8-0.4-4.9-1.4-6.3-2.8 c-1.4-1.4-2.3-3.4-2.8-6c-0.4-2.5-0.6-5.8-0.6-9.8V73.2h19.3V50.4h-19.3V10.9h-26.7v97.9c0,8.3,0.7,15.3,2.1,20.9 c1.4,5.5,3.8,10,7.1,13.4s7.7,5.8,13,7.3c5.4,1.5,12.2,2.2,20.3,2.2L208.9,152.6L208.9,152.6z M361.7,151.6V3.1H335v148.5H361.7z M137.2,60.3c-7.4-8-17.8-12-31-12c-6.4,0-12.2,1.3-17.5,3.9C83.5,54.8,79,58.4,75.5,63L74,64.9v-1.7V50.4H47.7v101.2h26.5V97.7 v3.7c0-0.6,0-1.2,0-1.8c0.3-9.5,2.6-16.5,7-21c4.7-4.8,10.4-7.2,16.9-7.2c7.7,0,13.6,2.4,17.5,7c3.8,4.6,5.8,11.1,5.8,19.4l0,0V98 l0,0l0,0v53.5h26.9V94.1C148.4,79.7,144.6,68.3,137.2,60.3z M321.2,100.8c0-7.3-1.3-14.1-3.8-20.5c-2.6-6.3-6.2-11.9-10.7-16.7 c-4.6-4.8-10.1-8.5-16.5-11.2s-13.5-4-21.2-4c-7.3,0-14.2,1.4-20.6,4.1c-6.4,2.8-12,6.5-16.7,11.2s-8.5,10.3-11.2,16.7 c-2.8,6.4-4.1,13.3-4.1,20.6c0,7.3,1.3,14.2,3.9,20.6c2.6,6.4,6.3,12,10.9,16.7c4.6,4.7,10.3,8.5,16.9,11.2 c6.6,2.8,13.9,4.2,21.7,4.2c22.6,0,36.6-10.3,45-19.9l-19.2-14.6c-4,4.8-13.6,11.3-25.6,11.3c-7.5,0-13.7-1.7-18.4-5.2 c-4.7-3.4-7.9-8.2-9.6-14.1l-0.3-0.9h79.5L321.2,100.8L321.2,100.8z M241.9,91.5c0-7.4,8.5-20.3,26.8-20.4 c18.3,0,26.9,12.9,26.9,20.3L241.9,91.5z" id="path1057" style="fill:#0068b5;fill-opacity:1"/>
<path d="M392.1,138.4c-0.5-1.2-1.2-2.2-2.1-3.1c-0.9-0.9-1.9-1.6-3.1-2.1s-2.5-0.8-3.8-0.8c-1.4,0-2.6,0.3-3.8,0.8 c-1.2,0.5-2.2,1.2-3.1,2.1c-0.9,0.9-1.6,1.9-2.1,3.1c-0.5,1.2-0.8,2.5-0.8,3.8c0,1.4,0.3,2.6,0.8,3.8s1.2,2.2,2.1,3.1 c0.9,0.9,1.9,1.6,3.1,2.1s2.5,0.8,3.8,0.8c1.4,0,2.6-0.3,3.8-0.8c1.2-0.5,2.2-1.2,3.1-2.1c0.9-0.9,1.6-1.9,2.1-3.1 c0.5-1.2,0.8-2.5,0.8-3.8S392.6,139.6,392.1,138.4z M390.5,145.4c-0.4,1-1,1.9-1.7,2.6c-0.7,0.7-1.6,1.3-2.6,1.7s-2,0.6-3.2,0.6 c-1.1,0-2.2-0.2-3.2-0.6c-1-0.4-1.9-1-2.6-1.7s-1.3-1.6-1.7-2.6c-0.4-1-0.6-2-0.6-3.2c0-1.1,0.2-2.2,0.6-3.2s1-1.9,1.7-2.6 c0.7-0.7,1.6-1.3,2.6-1.7s2-0.6,3.2-0.6c1.1,0,2.2,0.2,3.2,0.6c1,0.4,1.9,1,2.6,1.7s1.3,1.6,1.7,2.6c0.4,1,0.6,2,0.6,3.2 C391.2,143.4,390.9,144.4,390.5,145.4z M384.9,143c0.8-0.1,1.4-0.4,1.9-0.9s0.8-1.2,0.8-2.2c0-1.1-0.3-1.9-1-2.5 c-0.6-0.6-1.7-0.9-3-0.9h-4.4v11.3h2.1v-4.6h1.5l2.8,4.6h2.2L384.9,143z M383.8,141.4c-0.3,0-0.6,0-1,0h-1.5v-3.2h1.5 c0.3,0,0.6,0,1,0c0.3,0,0.6,0.1,0.9,0.2c0.3,0.1,0.5,0.3,0.6,0.5s0.2,0.5,0.2,0.9s-0.1,0.7-0.2,0.9c-0.2,0.2-0.4,0.4-0.6,0.5 C384.4,141.3,384.1,141.4,383.8,141.4z" id="path1059" style="fill:#0068b5;fill-opacity:1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M23.862 23.46v-3.816l1.13-.047c10.46-.33 17.313 8.998 17.313 8.998s-7.396 10.27-15.335 10.27a9.73 9.73 0 0 1-3.086-.495v-11.59c4.075.495 4.9 2.285 7.326 6.36l5.44-4.57s-3.98-5.206-10.67-5.206c-.707-.024-1.413.024-2.12.094m0-12.626v5.7l1.13-.07c14.534-.495 24.026 11.92 24.026 11.92S38.136 41.622 26.806 41.622c-.99 0-1.955-.094-2.92-.26v3.533c.8.094 1.625.165 2.426.165 10.553 0 18.185-5.394 25.58-11.754 1.225.99 6.242 3.368 7.28 4.405-7.02 5.89-23.39 10.623-32.67 10.623a23.24 23.24 0 0 1-2.591-.141v4.97H64v-42.33zm0 27.536v3.015C14.1 39.644 11.4 29.49 11.4 29.49s4.688-5.182 12.46-6.03v3.298h-.024c-4.075-.495-7.28 3.32-7.28 3.32s1.814 6.43 7.302 8.29M6.548 29.067s5.77-8.527 17.337-9.422v-3.11C11.07 17.572 0 28.408 0 28.408s6.266 18.138 23.862 19.787v-3.298c-12.908-1.602-17.313-15.83-17.313-15.83z" fill="#76b900"/></svg>

After

Width:  |  Height:  |  Size: 900 B

View file

@ -1,2 +1,2 @@
[toolchain]
channel = "1.63"
channel = "1.65"

View file

@ -1,14 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "debug")]
use crate::state::Fps;
use crate::{
backend::render,
config::OutputConfig,
shell::Shell,
state::{BackendData, ClientState, Common, Data},
state::{BackendData, ClientState, Common, Data, Fps},
utils::prelude::*,
wayland::{
handlers::screencopy::{PendingScreencopyBuffers, UserdataExt},
protocols::screencopy::{BufferParams, Session as ScreencopySession},
},
};
use anyhow::{Context, Result};
@ -20,13 +21,16 @@ use smithay::{
input::InputEvent,
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
damage::DamageTrackedRenderer,
gles2::Gles2Renderbuffer,
glow::GlowRenderer,
multigpu::{egl::EglGlesBackend, GpuManager},
Bind,
},
session::{auto::AutoSession, Session, Signal},
udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent},
},
desktop::utils::OutputPresentationFeedback,
input::Seat,
output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel},
reexports::{
calloop::{
@ -36,6 +40,7 @@ use smithay::{
drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags},
input::Libinput,
nix::{fcntl::OFlag, sys::stat::dev_t},
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource},
},
utils::{
@ -51,7 +56,7 @@ use std::{
os::unix::io::{FromRawFd, OwnedFd},
path::PathBuf,
rc::Rc,
time::{Duration, Instant},
time::Duration,
};
mod drm_helpers;
@ -60,9 +65,13 @@ mod socket;
use session_fd::*;
use socket::*;
use super::render::{CursorMode, GlMultiRenderer};
// for now we assume we need at least 3ms
const MIN_RENDER_TIME: Duration = Duration::from_millis(3);
pub struct KmsState {
devices: HashMap<DrmNode, Device>,
pub api: GpuManager<EglGlesBackend>,
pub api: GpuManager<EglGlesBackend<GlowRenderer>>,
pub primary: DrmNode,
session: AutoSession,
signaler: Signaler<Signal>,
@ -82,16 +91,21 @@ pub struct Device {
}
pub struct Surface {
surface: Option<GbmBufferedSurface<Rc<RefCell<GbmDevice<SessionFd>>>, SessionFd>>,
surface: Option<
GbmBufferedSurface<
Rc<RefCell<GbmDevice<SessionFd>>>,
SessionFd,
Option<OutputPresentationFeedback>,
>,
>,
damage_tracker: DamageTrackedRenderer,
connector: connector::Handle,
output: Output,
last_render: Option<(Dmabuf, Instant)>,
last_submit: Option<DrmEventTime>,
refresh_rate: u32,
vrr: bool,
pending: bool,
dirty: bool,
render_timer_token: Option<RegistrationToken>,
#[cfg(feature = "debug")]
fps: Fps,
}
@ -120,12 +134,12 @@ pub fn init_backend(
}
data.state.process_input_event(event);
for output in data.state.common.shell.outputs() {
if let Err(err) = data
.state
.backend
.kms()
.schedule_render(&data.state.common.event_loop_handle, output)
{
if let Err(err) = data.state.backend.kms().schedule_render(
&data.state.common.event_loop_handle,
output,
None,
None,
) {
slog_scope::crit!(
"Error scheduling event loop for output {}: {:?}",
output.name(),
@ -142,7 +156,8 @@ pub fn init_backend(
.map_err(|err| err.error)
.context("Failed to initialize session event source")?;
let api = GpuManager::new(EglGlesBackend, None).context("Failed to initialize renderers")?;
let api = GpuManager::new(EglGlesBackend::<GlowRenderer>::default(), None)
.context("Failed to initialize renderers")?;
// TODO get this info from system76-power, if available and setup a watcher
let primary = if let Some(path) = std::env::var("COSMIC_RENDER_DEVICE")
@ -238,20 +253,15 @@ pub fn init_backend(
}
}
}
data.state.common.output_configuration_state.update();
let seats = data.state.common.seats().cloned().collect::<Vec<_>>();
data.state.common.config.read_outputs(
data.state.common.output_configuration_state.outputs(),
&mut data.state.common.output_configuration_state,
&mut data.state.backend,
&mut data.state.common.shell,
seats.into_iter(),
&data.state.common.event_loop_handle,
);
data.state.common.shell.refresh_outputs();
data.state
.common
.config
.write_outputs(data.state.common.output_configuration_state.outputs());
for surface in data
.state
.backend
@ -263,12 +273,17 @@ pub fn init_backend(
surface.pending = false;
}
for output in data.state.common.shell.outputs() {
if let Err(err) = data
.state
.backend
.kms()
.schedule_render(&data.state.common.event_loop_handle, output)
{
let sessions = output.pending_buffers().collect::<Vec<_>>();
if let Err(err) = data.state.backend.kms().schedule_render(
&data.state.common.event_loop_handle,
output,
None,
if !sessions.is_empty() {
Some(sessions)
} else {
None
},
) {
slog_scope::crit!(
"Error scheduling event loop for output {}: {:?}",
output.name(),
@ -364,27 +379,87 @@ impl State {
let dispatcher =
Dispatcher::new(drm, move |event, metadata, data: &mut Data| match event {
DrmEvent::VBlank(crtc) => {
if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) {
if let Some(surface) = device.surfaces.get_mut(&crtc) {
match surface.surface.as_mut().map(|x| x.frame_submitted()) {
Some(Ok(_)) => {
surface.last_submit = metadata.take().map(|data| data.time);
surface.pending = false;
data.state
.common
.shell
.active_space_mut(&surface.output)
.space
.send_frames(
data.state.common.start_time.elapsed().as_millis()
as u32,
);
let rescheduled =
if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) {
if let Some(surface) = device.surfaces.get_mut(&crtc) {
#[cfg(feature = "debug")]
surface.fps.displayed();
match surface.surface.as_mut().map(|x| x.frame_submitted()) {
Some(Ok(feedback)) => {
if let Some(mut feedback) = feedback.flatten() {
let submit_time =
match metadata.take().map(|data| data.time) {
Some(DrmEventTime::Monotonic(tp)) => Some(tp),
_ => None,
};
let seq = metadata
.as_ref()
.map(|metadata| metadata.sequence)
.unwrap_or(0);
let (clock, flags) = if let Some(tp) = submit_time {
(
tp.into(),
wp_presentation_feedback::Kind::Vsync
| wp_presentation_feedback::Kind::HwClock
| wp_presentation_feedback::Kind::HwCompletion,
)
} else {
(
data.state.common.clock.now(),
wp_presentation_feedback::Kind::Vsync,
)
};
feedback.presented(
clock,
surface
.output
.current_mode()
.map(|mode| mode.refresh as u32)
.unwrap_or_default(),
seq as u64,
flags,
);
}
surface.pending = false;
surface.dirty.then(|| {
(surface.output.clone(), surface.fps.avg_rendertime(5))
})
}
Some(Err(err)) => {
slog_scope::warn!("Failed to submit frame: {}", err);
None
}
_ => None, // got disabled
}
Some(Err(err)) => {
slog_scope::warn!("Failed to submit frame: {}", err)
}
None => {} // got disabled
};
} else {
None
}
} else {
None
};
if let Some((output, avg_rendertime)) = rescheduled {
let mut scheduled_sessions =
data.state.workspace_session_for_output(&output);
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>()
{
scheduled_sessions
.get_or_insert_with(Vec::new)
.extend(sessions.borrow_mut().drain(..));
}
let repaint_delay = std::cmp::max(avg_rendertime, MIN_RENDER_TIME);
if let Err(err) = data.state.backend.kms().schedule_render(
&data.state.common.event_loop_handle,
&output,
Some(repaint_delay),
scheduled_sessions,
) {
slog_scope::warn!("Failed to schedule render: {}", err);
}
}
}
@ -424,47 +499,44 @@ impl State {
let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices
let mut wl_outputs = Vec::new();
let mut w = self.common.shell.global_space().size.w;
for (crtc, conn) in outputs {
match device.setup_surface(crtc, conn, (w, 0)) {
Ok(output) => {
w += output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.mode_size()
.w;
wl_outputs.push(output);
}
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
};
{
let backend = self.backend.kms();
for (crtc, conn) in outputs {
let mut renderer = match backend.api.renderer(&render_node, &render_node) {
Ok(renderer) => renderer,
Err(err) => {
slog_scope::warn!("Failed to initialize output: {}", err);
continue;
}
};
match device.setup_surface(crtc, conn, (w, 0), &mut renderer) {
Ok(output) => {
w += output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.mode_size()
.w;
wl_outputs.push(output);
}
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
};
}
backend.devices.insert(drm_node, device);
}
self.backend.kms().devices.insert(drm_node, device);
self.common
.output_configuration_state
.add_heads(wl_outputs.iter());
self.common.output_configuration_state.update();
for output in wl_outputs {
if let Err(err) = self.backend.kms().apply_config_for_output(
&output,
&mut self.common.shell,
false,
&self.common.event_loop_handle,
) {
slog_scope::warn!("Failed to initialize output: {}", err);
}
}
let seats = self.common.seats().cloned().collect::<Vec<_>>();
self.common.config.read_outputs(
self.common.output_configuration_state.outputs(),
&mut self.common.output_configuration_state,
&mut self.backend,
&mut self.common.shell,
seats.into_iter(),
&self.common.event_loop_handle,
);
self.common.shell.refresh_outputs();
self.common
.config
.write_outputs(self.common.output_configuration_state.outputs());
Ok(())
}
@ -477,32 +549,45 @@ impl State {
let drm_node = DrmNode::from_dev_id(dev)?;
let mut outputs_removed = Vec::new();
let mut outputs_added = Vec::new();
if let Some(device) = self.backend.kms().devices.get_mut(&drm_node) {
let changes = device.enumerate_surfaces()?;
let mut w = self.common.shell.global_space().size.w;
for crtc in changes.removed {
if let Some(surface) = device.surfaces.remove(&crtc) {
if let Some(token) = surface.render_timer_token {
self.common.event_loop_handle.remove(token);
{
let backend = self.backend.kms();
if let Some(device) = backend.devices.get_mut(&drm_node) {
let changes = device.enumerate_surfaces()?;
let mut w = self.common.shell.global_space().size.w;
for crtc in changes.removed {
if let Some(surface) = device.surfaces.remove(&crtc) {
if let Some(token) = surface.render_timer_token {
self.common.event_loop_handle.remove(token);
}
w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0);
outputs_removed.push(surface.output.clone());
}
w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0);
outputs_removed.push(surface.output.clone());
}
}
for (crtc, conn) in changes.added {
match device.setup_surface(crtc, conn, (w, 0)) {
Ok(output) => {
w += output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.mode_size()
.w;
outputs_added.push(output);
}
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
};
for (crtc, conn) in changes.added {
let mut renderer = match backend
.api
.renderer(&device.render_node, &device.render_node)
{
Ok(renderer) => renderer,
Err(err) => {
slog_scope::warn!("Failed to initialize output: {}", err);
continue;
}
};
match device.setup_surface(crtc, conn, (w, 0), &mut renderer) {
Ok(output) => {
w += output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.mode_size()
.w;
outputs_added.push(output);
}
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
};
}
}
}
@ -512,30 +597,19 @@ impl State {
self.common
.output_configuration_state
.add_heads(outputs_added.iter());
for output in outputs_added {
if let Err(err) = self.backend.kms().apply_config_for_output(
&output,
&mut self.common.shell,
false,
&self.common.event_loop_handle,
) {
slog_scope::warn!("Failed to initialize output: {}", err);
}
}
let seats = self.common.seats().cloned().collect::<Vec<_>>();
for output in outputs_removed {
self.common.shell.remove_output(&output);
self.common
.shell
.remove_output(&output, seats.iter().cloned());
}
self.common.output_configuration_state.update();
self.common.config.read_outputs(
self.common.output_configuration_state.outputs(),
&mut self.common.output_configuration_state,
&mut self.backend,
&mut self.common.shell,
seats.into_iter(),
&self.common.event_loop_handle,
);
self.common.shell.refresh_outputs();
self.common
.config
.write_outputs(self.common.output_configuration_state.outputs());
Ok(())
}
@ -564,22 +638,23 @@ impl State {
self.common
.output_configuration_state
.remove_heads(outputs_removed.iter());
self.common.output_configuration_state.update();
let seats = self.common.seats().cloned().collect::<Vec<_>>();
if self.backend.kms().session.is_active() {
for output in outputs_removed {
self.common.shell.remove_output(&output);
self.common
.shell
.remove_output(&output, seats.iter().cloned());
}
self.common.config.read_outputs(
self.common.output_configuration_state.outputs(),
&mut self.common.output_configuration_state,
&mut self.backend,
&mut self.common.shell,
seats.into_iter(),
&self.common.event_loop_handle,
);
self.common.shell.refresh_outputs();
self.common
.config
.write_outputs(self.common.output_configuration_state.outputs());
} else {
self.common.output_configuration_state.update();
}
Ok(())
@ -624,6 +699,7 @@ impl Device {
crtc: crtc::Handle,
conn: connector::Handle,
position: (i32, i32),
renderer: &mut GlMultiRenderer<'_>,
) -> Result<Output> {
let drm = &mut *self.drm.as_source_mut();
let crtc_info = drm.get_crtc(crtc)?;
@ -689,16 +765,15 @@ impl Device {
let data = Surface {
output: output.clone(),
damage_tracker: DamageTrackedRenderer::from_output(&output),
surface: None,
connector: conn,
vrr,
refresh_rate,
last_submit: None,
last_render: None,
pending: false,
dirty: false,
render_timer_token: None,
#[cfg(feature = "debug")]
fps: Fps::default(),
fps: Fps::new(renderer.as_mut()),
};
self.surfaces.insert(crtc, data);
@ -717,8 +792,8 @@ fn render_node_for_output(
let workspace = shell.active_space(output);
let nodes = workspace
.get_fullscreen(output)
.map(|w| vec![w])
.unwrap_or_else(|| workspace.space.windows().collect::<Vec<_>>())
.map(|w| vec![w.clone()])
.unwrap_or_else(|| workspace.windows().collect::<Vec<_>>())
.into_iter()
.flat_map(|w| {
dh.get_client(w.toplevel().wl_surface().id())
@ -750,44 +825,44 @@ impl Surface {
pub fn render_output(
&mut self,
dh: &DisplayHandle,
api: &mut GpuManager<EglGlesBackend>,
api: &mut GpuManager<EglGlesBackend<GlowRenderer>>,
target_node: &DrmNode,
state: &mut Common,
screencopy: Option<&[(ScreencopySession, BufferParams)]>,
) -> Result<()> {
if self.surface.is_none() {
return Ok(());
}
if render::needs_buffer_reset(&self.output, state) {
self.surface.as_mut().unwrap().reset_buffers();
}
let render_node = render_node_for_output(dh, &self.output, *target_node, &state.shell);
let mut renderer = api.renderer(&render_node, &target_node).unwrap();
let mut renderer: GlMultiRenderer = api.renderer(&render_node, &target_node).unwrap();
let surface = self.surface.as_mut().unwrap();
let (buffer, age) = surface
.next_buffer()
.with_context(|| "Failed to allocate buffer")?;
renderer
.bind(buffer.clone())
.with_context(|| "Failed to bind buffer")?;
match render::render_output(
match render::render_output::<GlMultiRenderer, _, Gles2Renderbuffer, _>(
Some(&render_node),
&mut renderer,
age,
buffer.clone(),
&mut self.damage_tracker,
age as usize,
state,
&self.output,
false,
#[cfg(feature = "debug")]
CursorMode::All,
screencopy.map(|sessions| (buffer, sessions)),
Some(&mut self.fps),
) {
Ok(_) => {
self.last_render = Some((buffer, Instant::now()));
Ok((damage, states)) => {
let feedback = if damage.is_some() {
Some(state.take_presentation_feedback(&self.output, &states))
} else {
None
};
state.send_frames(&self.output, &states);
surface
.queue_buffer()
.queue_buffer(feedback)
.with_context(|| "Failed to submit buffer for display")?;
}
Err(err) => {
@ -795,6 +870,7 @@ impl Surface {
anyhow::bail!("Rendering failed: {}", err);
}
};
Ok(())
}
}
@ -807,6 +883,7 @@ impl KmsState {
pub fn apply_config_for_output(
&mut self,
output: &Output,
seats: impl Iterator<Item = Seat<State>>,
shell: &mut Shell,
test_only: bool,
loop_handle: &LoopHandle<'_, Data>,
@ -829,10 +906,11 @@ impl KmsState {
if !output_config.enabled {
if !test_only {
shell.remove_output(output, seats);
if surface.surface.take().is_some() {
// just drop it
shell.remove_output(output);
surface.pending = false;
surface.dirty = false;
}
}
false
@ -856,7 +934,7 @@ impl KmsState {
.ok_or(anyhow::anyhow!("Unknown mode"))?;
if !test_only {
if let Some(gbm_surface) = surface.surface.as_mut() {
let res = if let Some(gbm_surface) = surface.surface.as_mut() {
if output_config.vrr != surface.vrr {
surface.vrr = drm_helpers::set_vrr(
drm,
@ -888,9 +966,10 @@ impl KmsState {
)
})?;
surface.surface = Some(target);
shell.add_output(output);
true
}
};
shell.add_output(output);
res
} else {
false
}
@ -901,7 +980,17 @@ impl KmsState {
shell.refresh_outputs();
if recreated {
if let Err(err) = self.schedule_render(loop_handle, output) {
let sessions = output.pending_buffers().collect::<Vec<_>>();
if let Err(err) = self.schedule_render(
loop_handle,
output,
None,
if !sessions.is_empty() {
Some(sessions)
} else {
None
},
) {
slog_scope::crit!(
"Error scheduling event loop for output {}: {:?}",
output.name(),
@ -964,6 +1053,8 @@ impl KmsState {
&mut self,
loop_handle: &LoopHandle<'_, Data>,
output: &Output,
delay: Option<Duration>,
mut screencopy_sessions: Option<Vec<(ScreencopySession, BufferParams)>>,
) -> Result<(), InsertError<Timer>> {
if let Some((device, crtc, surface)) = self
.devices
@ -975,20 +1066,8 @@ impl KmsState {
return Ok(());
}
if !surface.pending {
surface.dirty = false;
surface.pending = true;
/*
let instant = surface
.last_submit
.as_ref()
.and_then(|x| match x {
DrmEventTime::Monotonic(instant) => Some(instant),
DrmEventTime::Realtime(_) => None,
})
.map(|i| {
*i + Duration::from_secs_f64(1.0 / surface.refresh_rate as f64)
- Duration::from_millis(20) // render budget
});
*/
let device = *device;
let crtc = *crtc;
@ -996,10 +1075,11 @@ impl KmsState {
loop_handle.remove(token);
}
surface.render_timer_token = Some(loop_handle.insert_source(
//if surface.vrr || instant.is_none() {
Timer::immediate(), /*} else {
Timer::from_deadline(instant.unwrap())
}*/
if surface.vrr || delay.is_none() {
Timer::immediate()
} else {
Timer::from_duration(delay.unwrap())
},
move |_time, _, data| {
let backend = data.state.backend.kms();
if let Some(device) = backend.devices.get_mut(&device) {
@ -1009,11 +1089,17 @@ impl KmsState {
&mut backend.api,
&device.render_node,
&mut data.state.common,
screencopy_sessions.as_deref(),
) {
if let Some(sessions) = screencopy_sessions.as_mut() {
for (session, params) in sessions.drain(..) {
data.state.common.still_pending(session, params);
}
}
if backend.session.is_active() {
slog_scope::error!("Error rendering: {}", err);
return TimeoutAction::ToDuration(Duration::from_secs_f64(
1.0 / surface.refresh_rate as f64,
(1000.0 / surface.refresh_rate as f64) - 0.003,
));
}
}
@ -1022,21 +1108,10 @@ impl KmsState {
TimeoutAction::Drop
},
)?);
} else {
surface.dirty = true;
}
}
Ok(())
}
pub fn capture_output(&self, output: &Output) -> Option<(DrmNode, Dmabuf, Instant)> {
self.devices.values().find_map(|dev| {
dev.surfaces
.values()
.find(|s| &s.output == output)
.and_then(|s| {
s.last_render
.clone()
.map(|(buf, time)| (dev.render_node.clone(), buf, time))
})
})
}
}

View file

@ -39,18 +39,22 @@ pub fn init_backend_auto(
}
}
};
if res.is_ok() {
for seat in &state.common.seats {
let output = state
.common
.shell
.outputs()
.next()
.with_context(|| "Backend initialized without output")?
.clone();
seat.user_data()
.insert_if_missing(|| crate::input::ActiveOutput(std::cell::RefCell::new(output)));
}
let output = state
.common
.shell
.outputs()
.next()
.with_context(|| "Backend initialized without output")?;
let initial_seat = crate::input::add_seat(
dh,
&mut state.common.seat_state,
output,
&state.common.config,
"seat-0".into(),
);
state.common.add_seat(initial_seat);
}
res
}

View file

@ -2,14 +2,20 @@
use crate::utils::prelude::*;
use smithay::{
backend::renderer::{Frame, ImportAll, ImportMem, Renderer, Texture},
desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree},
backend::renderer::{
element::{
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
texture::{TextureBuffer, TextureRenderElement},
},
ImportAll, ImportMem, Renderer,
},
input::{
pointer::{CursorImageAttributes, CursorImageStatus},
Seat,
},
reexports::wayland_server::protocol::wl_surface,
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform},
render_elements,
utils::{IsAlive, Logical, Monotonic, Point, Scale, Time, Transform},
wayland::compositor::{get_role, with_states},
};
use std::{
@ -18,6 +24,7 @@ use std::{
collections::HashMap,
io::Read,
sync::Mutex,
time::Duration,
};
use xcursor::{
parser::{parse_xcursor, Image},
@ -119,13 +126,21 @@ fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
parse_xcursor(&cursor_data).ok_or(Error::Parse)
}
pub fn draw_surface_cursor(
surface: wl_surface::WlSurface,
render_elements! {
pub CursorRenderElement<R> where R: ImportAll;
Static=TextureRenderElement<<R as Renderer>::TextureId>,
Surface=WaylandSurfaceRenderElement,
}
pub fn draw_surface_cursor<R: Renderer + ImportAll>(
surface: &wl_surface::WlSurface,
location: impl Into<Point<i32, Logical>>,
) -> SurfaceTree
scale: impl Into<Scale<f64>>,
) -> Vec<CursorRenderElement<R>>
where
{
let mut position = location.into();
let scale = scale.into();
let h = with_states(&surface, |states| {
states
.data_map
@ -136,129 +151,30 @@ where
.hotspot
});
position -= h;
SurfaceTree {
surface,
position,
z_index: 100,
}
render_elements_from_surface_tree(surface, position.to_physical_precise_round(scale), scale)
}
pub fn draw_dnd_icon(
surface: wl_surface::WlSurface,
pub fn draw_dnd_icon<R: Renderer + ImportAll>(
surface: &wl_surface::WlSurface,
location: impl Into<Point<i32, Logical>>,
) -> SurfaceTree {
scale: impl Into<Scale<f64>>,
) -> Vec<CursorRenderElement<R>> {
if get_role(&surface) != Some("dnd_icon") {
slog_scope::warn!(
"Trying to display as a dnd icon a surface that does not have the DndIcon role."
);
}
SurfaceTree {
let scale = scale.into();
render_elements_from_surface_tree(
surface,
position: location.into(),
z_index: 100,
}
location.into().to_physical_precise_round(scale),
scale,
)
}
pub struct PointerElement<T: Texture> {
seat_id: usize,
texture: T,
position: Point<f64, Logical>,
size: Size<i32, Logical>,
new_frame: bool,
}
impl<T: Texture> PointerElement<T> {
pub fn new(
seat: &Seat<State>,
texture: T,
relative_pointer_pos: Point<f64, Logical>,
new_frame: bool,
) -> PointerElement<T> {
let size = texture.size().to_logical(1, Transform::Normal);
PointerElement {
seat_id: seat.id(),
texture,
position: relative_pointer_pos,
size,
new_frame,
}
}
}
impl<R> RenderElement<R> for PointerElement<<R as Renderer>::TextureId>
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: 'static,
{
fn id(&self) -> usize {
self.seat_id
}
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
self.position.to_physical(scale)
}
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
Rectangle::from_loc_and_size(self.position, self.size.to_f64())
.to_physical(scale)
.to_i32_round()
}
fn accumulated_damage(
&self,
scale: impl Into<Scale<f64>>,
_: Option<SpaceOutputTuple<'_, '_>>,
) -> Vec<Rectangle<i32, Physical>> {
if self.new_frame {
let scale = scale.into();
vec![Rectangle::from_loc_and_size(
self.position.to_physical(scale).to_i32_round(),
self.size.to_physical_precise_round(scale),
)]
} else {
vec![]
}
}
fn opaque_regions(
&self,
_scale: impl Into<Scale<f64>>,
) -> Option<Vec<Rectangle<i32, Physical>>> {
None
}
fn draw(
&self,
_renderer: &mut R,
frame: &mut <R as Renderer>::Frame,
scale: impl Into<Scale<f64>>,
position: Point<f64, Physical>,
damage: &[Rectangle<i32, Physical>],
_log: &slog::Logger,
) -> Result<(), <R as Renderer>::Error> {
let scale = scale.into();
frame.render_texture_at(
&self.texture,
position.to_i32_round(),
1,
scale,
Transform::Normal,
&damage
.iter()
.copied()
.map(|mut rect| {
rect.loc -= self.position.to_physical(scale).to_i32_round();
rect
})
.collect::<Vec<_>>(),
1.0,
)?;
Ok(())
}
}
struct CursorState {
cursor: Cursor,
pub struct CursorState {
pub cursor: Cursor,
current_image: RefCell<Option<Image>>,
image_cache: RefCell<HashMap<(TypeId, usize), Vec<(Image, Box<dyn Any + 'static>)>>>,
}
@ -273,79 +189,90 @@ impl Default for CursorState {
}
}
pub fn draw_cursor<R, I>(
pub fn draw_cursor<R>(
renderer: &mut R,
seat: &Seat<State>,
location: Point<f64, Logical>,
start_time: &std::time::Instant,
scale: Scale<f64>,
time: Time<Monotonic>,
draw_default: bool,
) -> Option<I>
) -> Vec<CursorRenderElement<R>>
where
I: From<SurfaceTree> + From<PointerElement<<R as Renderer>::TextureId>>,
R: Renderer + ImportAll + ImportMem,
R: Renderer + ImportMem + ImportAll,
<R as Renderer>::TextureId: Clone + 'static,
{
// draw the cursor as relevant
{
// reset the cursor if the surface is no longer alive
let cursor_status = seat
.user_data()
.get::<RefCell<CursorImageStatus>>()
.map(|cell| {
let mut cursor_status = cell.borrow_mut();
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
if !surface.alive() {
*cursor_status = CursorImageStatus::Default;
}
// reset the cursor if the surface is no longer alive
let cursor_status = seat
.user_data()
.get::<RefCell<CursorImageStatus>>()
.map(|cell| {
let mut cursor_status = cell.borrow_mut();
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
if !surface.alive() {
*cursor_status = CursorImageStatus::Default;
}
cursor_status.clone()
})
.unwrap_or(CursorImageStatus::Default);
}
cursor_status.clone()
})
.unwrap_or(CursorImageStatus::Default);
if let CursorImageStatus::Surface(wl_surface) = cursor_status {
Some(draw_surface_cursor(wl_surface.clone(), location.to_i32_round()).into())
} else if draw_default {
let seat_userdata = seat.user_data();
seat_userdata.insert_if_missing(CursorState::default);
let state = seat_userdata.get::<CursorState>().unwrap();
let frame = state
.cursor
.get_image(1, start_time.elapsed().as_millis() as u32);
let new_frame = state.current_image.borrow().as_ref() != Some(&frame);
if let CursorImageStatus::Surface(ref wl_surface) = cursor_status {
return draw_surface_cursor(wl_surface, location.to_i32_round(), scale);
} else if draw_default && CursorImageStatus::Default == cursor_status {
let integer_scale = scale.x.max(scale.y).ceil() as u32;
let mut cache = state.image_cache.borrow_mut();
let pointer_images = cache
.entry((TypeId::of::<<R as Renderer>::TextureId>(), renderer.id()))
.or_default();
let pointer_image = pointer_images
.iter()
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None })
.and_then(|texture| {
texture
.downcast_ref::<<R as Renderer>::TextureId>()
.cloned()
})
.unwrap_or_else(|| {
let texture = renderer
.import_memory(
&frame.pixels_rgba,
(frame.width as i32, frame.height as i32).into(),
false,
)
.expect("Failed to import cursor bitmap");
pointer_images.push((frame.clone(), Box::new(texture.clone())));
texture
});
let hotspot =
Point::<i32, Logical>::from((frame.xhot as i32, frame.yhot as i32)).to_f64();
*state.current_image.borrow_mut() = Some(frame);
let seat_userdata = seat.user_data();
seat_userdata.insert_if_missing(CursorState::default);
let state = seat_userdata.get::<CursorState>().unwrap();
let frame = state.cursor.get_image(
integer_scale,
Into::<Duration>::into(time).as_millis() as u32,
);
Some(
PointerElement::new(seat, pointer_image.clone(), location - hotspot, new_frame)
.into(),
)
} else {
None
}
let mut cache = state.image_cache.borrow_mut();
let pointer_images = cache
.entry((TypeId::of::<TextureBuffer<R::TextureId>>(), renderer.id()))
.or_default();
let maybe_image = pointer_images
.iter()
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None })
.and_then(|texture| texture.downcast_ref::<TextureBuffer<R::TextureId>>());
let pointer_image = match maybe_image {
Some(image) => image,
None => {
let texture = TextureBuffer::from_memory(
renderer,
&frame.pixels_rgba,
(frame.width as i32, frame.height as i32),
false,
integer_scale as i32,
Transform::Normal,
None,
)
.expect("Failed to import cursor bitmap");
pointer_images.push((frame.clone(), Box::new(texture.clone())));
pointer_images
.last()
.and_then(|(_, i)| i.downcast_ref::<TextureBuffer<R::TextureId>>())
.unwrap()
}
};
let hotspot = Point::<i32, Logical>::from((frame.xhot as i32, frame.yhot as i32)).to_f64();
*state.current_image.borrow_mut() = Some(frame);
return vec![CursorRenderElement::Static(
TextureRenderElement::from_texture_buffer(
(location - hotspot).to_physical(scale),
pointer_image,
None,
None,
None,
),
)];
} else {
Vec::new()
}
}

View file

@ -0,0 +1,309 @@
use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement};
use smithay::{
backend::renderer::{
element::{texture::TextureRenderElement, Element, RenderElement, UnderlyingStorage},
gles2::{Gles2Frame, Gles2Texture},
glow::GlowRenderer,
multigpu::Error as MultiError,
Frame, ImportAll, Renderer,
},
utils::{Physical, Point, Rectangle, Scale},
};
use super::{cursor::CursorRenderElement, GlMultiFrame, GlMultiRenderer};
pub enum CosmicElement<R>
where
R: AsGlowRenderer + Renderer + ImportAll,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
Workspace(WorkspaceRenderElement<R>),
Cursor(CursorRenderElement<R>),
MoveGrab(CosmicMappedRenderElement<R>),
#[cfg(feature = "debug")]
Egui(TextureRenderElement<Gles2Texture>),
}
impl<R> Element for CosmicElement<R>
where
R: AsGlowRenderer + Renderer + ImportAll,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn id(&self) -> &smithay::backend::renderer::element::Id {
match self {
CosmicElement::Workspace(elem) => elem.id(),
CosmicElement::Cursor(elem) => elem.id(),
CosmicElement::MoveGrab(elem) => elem.id(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.id(),
}
}
fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter {
match self {
CosmicElement::Workspace(elem) => elem.current_commit(),
CosmicElement::Cursor(elem) => elem.current_commit(),
CosmicElement::MoveGrab(elem) => elem.current_commit(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.current_commit(),
}
}
fn src(&self) -> Rectangle<f64, smithay::utils::Buffer> {
match self {
CosmicElement::Workspace(elem) => elem.src(),
CosmicElement::Cursor(elem) => elem.src(),
CosmicElement::MoveGrab(elem) => elem.src(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.src(),
}
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
match self {
CosmicElement::Workspace(elem) => elem.geometry(scale),
CosmicElement::Cursor(elem) => elem.geometry(scale),
CosmicElement::MoveGrab(elem) => elem.geometry(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.geometry(scale),
}
}
fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
match self {
CosmicElement::Workspace(elem) => elem.location(scale),
CosmicElement::Cursor(elem) => elem.location(scale),
CosmicElement::MoveGrab(elem) => elem.location(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.location(scale),
}
}
fn transform(&self) -> smithay::utils::Transform {
match self {
CosmicElement::Workspace(elem) => elem.transform(),
CosmicElement::Cursor(elem) => elem.transform(),
CosmicElement::MoveGrab(elem) => elem.transform(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.transform(),
}
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<smithay::backend::renderer::utils::CommitCounter>,
) -> Vec<Rectangle<i32, Physical>> {
match self {
CosmicElement::Workspace(elem) => elem.damage_since(scale, commit),
CosmicElement::Cursor(elem) => elem.damage_since(scale, commit),
CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.damage_since(scale, commit),
}
}
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
match self {
CosmicElement::Workspace(elem) => elem.opaque_regions(scale),
CosmicElement::Cursor(elem) => elem.opaque_regions(scale),
CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.opaque_regions(scale),
}
}
}
impl RenderElement<GlowRenderer> for CosmicElement<GlowRenderer> {
fn draw(
&self,
renderer: &mut GlowRenderer,
frame: &mut <GlowRenderer as Renderer>::Frame,
location: Point<i32, Physical>,
scale: Scale<f64>,
damage: &[Rectangle<i32, Physical>],
log: &slog::Logger,
) -> Result<(), <GlowRenderer as Renderer>::Error> {
match self {
CosmicElement::Workspace(elem) => {
elem.draw(renderer, frame, location, scale, damage, log)
}
CosmicElement::Cursor(elem) => elem.draw(renderer, frame, location, scale, damage, log),
CosmicElement::MoveGrab(elem) => {
elem.draw(renderer, frame, location, scale, damage, log)
}
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.draw(renderer, frame, location, scale, damage, log),
}
}
fn underlying_storage(
&self,
renderer: &GlowRenderer,
) -> Option<UnderlyingStorage<'_, GlowRenderer>> {
match self {
CosmicElement::Workspace(elem) => elem.underlying_storage(renderer),
CosmicElement::Cursor(elem) => elem.underlying_storage(renderer),
CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.underlying_storage(renderer),
}
}
}
impl<'a> RenderElement<GlMultiRenderer<'a>> for CosmicElement<GlMultiRenderer<'a>> {
fn draw(
&self,
renderer: &mut GlMultiRenderer<'a>,
frame: &mut <GlMultiRenderer<'a> as Renderer>::Frame,
location: Point<i32, Physical>,
scale: Scale<f64>,
damage: &[Rectangle<i32, Physical>],
log: &slog::Logger,
) -> Result<(), <GlMultiRenderer<'_> as Renderer>::Error> {
match self {
CosmicElement::Workspace(elem) => {
elem.draw(renderer, frame, location, scale, damage, log)
}
CosmicElement::Cursor(elem) => elem.draw(renderer, frame, location, scale, damage, log),
CosmicElement::MoveGrab(elem) => {
elem.draw(renderer, frame, location, scale, damage, log)
}
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => {
let glow_renderer = renderer.glow_renderer_mut();
let gles2_frame = frame.gles2_frame_mut();
elem.draw(glow_renderer, gles2_frame, location, scale, damage, log)
.map_err(|err| MultiError::Render(err))
}
}
}
fn underlying_storage(
&self,
renderer: &GlMultiRenderer<'a>,
) -> Option<UnderlyingStorage<'_, GlMultiRenderer<'a>>> {
match self {
CosmicElement::Workspace(elem) => elem.underlying_storage(renderer),
CosmicElement::Cursor(elem) => elem.underlying_storage(renderer),
CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => {
let glow_renderer = renderer.glow_renderer();
match elem.underlying_storage(glow_renderer) {
Some(UnderlyingStorage::Wayland(buffer)) => {
Some(UnderlyingStorage::Wayland(buffer))
}
_ => None,
}
}
}
}
}
impl<R> From<WorkspaceRenderElement<R>> for CosmicElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: WorkspaceRenderElement<R>) -> Self {
Self::Workspace(elem)
}
}
impl<R> From<CursorRenderElement<R>> for CosmicElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: CursorRenderElement<R>) -> Self {
Self::Cursor(elem)
}
}
impl<R> From<CosmicMappedRenderElement<R>> for CosmicElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: CosmicMappedRenderElement<R>) -> Self {
Self::MoveGrab(elem)
}
}
#[cfg(feature = "debug")]
impl<R> From<TextureRenderElement<Gles2Texture>> for CosmicElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: TextureRenderElement<Gles2Texture>) -> Self {
Self::Egui(elem)
}
}
pub trait AsGlowRenderer
where
Self: Renderer,
<Self as Renderer>::Frame: AsGles2Frame,
{
fn glow_renderer(&self) -> &GlowRenderer;
fn glow_renderer_mut(&mut self) -> &mut GlowRenderer;
}
impl AsGlowRenderer for GlowRenderer {
fn glow_renderer(&self) -> &GlowRenderer {
self
}
fn glow_renderer_mut(&mut self) -> &mut GlowRenderer {
self
}
}
impl<'a> AsGlowRenderer for GlMultiRenderer<'a> {
fn glow_renderer(&self) -> &GlowRenderer {
self.as_ref()
}
fn glow_renderer_mut(&mut self) -> &mut GlowRenderer {
self.as_mut()
}
}
pub trait AsGles2Frame
where
Self: Frame,
{
fn gles2_frame(&self) -> &Gles2Frame;
fn gles2_frame_mut(&mut self) -> &mut Gles2Frame;
}
impl AsGles2Frame for Gles2Frame {
fn gles2_frame(&self) -> &Gles2Frame {
self
}
fn gles2_frame_mut(&mut self) -> &mut Gles2Frame {
self
}
}
impl AsGles2Frame for GlMultiFrame {
fn gles2_frame(&self) -> &Gles2Frame {
self.as_ref()
}
fn gles2_frame_mut(&mut self) -> &mut Gles2Frame {
self.as_mut()
}
}

View file

@ -1,477 +1,314 @@
// SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "debug")]
use crate::{debug::fps_ui, utils::prelude::*};
use crate::{
debug::{debug_ui, fps_ui, log_ui, EguiFrame},
state::Fps,
utils::prelude::*,
};
use crate::{
shell::grabs::{MoveGrabRenderElement, SeatMoveGrabState},
state::Common,
wayland::handlers::data_device::get_dnd_icon,
};
use slog::Logger;
use smithay::{
backend::{
drm::DrmNode,
renderer::{
gles2::{Gles2Renderbuffer, Gles2Renderer, Gles2Texture},
multigpu::{egl::EglGlesBackend, Error as MultiError, MultiFrame, MultiRenderer},
Frame, ImportAll, Renderer,
shell::{layout::floating::SeatMoveGrabState, CosmicMappedRenderElement},
state::{Common, Fps},
wayland::{
handlers::{data_device::get_dnd_icon, screencopy::render_session},
protocols::{
screencopy::{
BufferParams, CursorMode as ScreencopyCursorMode, Session as ScreencopySession,
},
workspace::WorkspaceHandle,
},
},
desktop::{
draw_layer_popups, draw_layer_surface, draw_window, draw_window_popups,
layer_map_for_output,
space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree},
utils::damage_from_surface_tree,
Window,
};
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason;
use smithay::{
backend::{
allocator::dmabuf::Dmabuf,
drm::DrmNode,
renderer::{
buffer_dimensions,
damage::{
DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode,
},
element::{RenderElement, RenderElementStates},
gles2::{Gles2Error, Gles2Renderbuffer},
glow::GlowRenderer,
multigpu::{egl::EglGlesBackend, MultiFrame, MultiRenderer},
Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, TextureFilter,
},
},
output::Output,
utils::{Physical, Point, Rectangle, Scale, Transform},
wayland::shell::wlr_layer::Layer as WlrLayer,
utils::{Physical, Rectangle},
wayland::dmabuf::get_dmabuf,
};
pub mod cursor;
use self::cursor::PointerElement;
use self::cursor::CursorRenderElement;
pub mod element;
use self::element::{AsGles2Frame, AsGlowRenderer, CosmicElement};
pub type GlMultiRenderer<'a> =
MultiRenderer<'a, 'a, EglGlesBackend, EglGlesBackend, Gles2Renderbuffer>;
pub type GlMultiFrame = MultiFrame<EglGlesBackend, EglGlesBackend>;
pub type GlMultiRenderer<'a> = MultiRenderer<
'a,
'a,
EglGlesBackend<GlowRenderer>,
EglGlesBackend<GlowRenderer>,
Gles2Renderbuffer,
>;
pub type GlMultiFrame = MultiFrame<EglGlesBackend<GlowRenderer>, EglGlesBackend<GlowRenderer>>;
static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0];
pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0];
smithay::custom_elements! {
pub CustomElem<=Gles2Renderer>;
SurfaceTree=SurfaceTree,
PointerElement=PointerElement::<Gles2Texture>,
MoveGrabRenderElement=MoveGrabRenderElement,
#[cfg(feature = "debug")]
EguiFrame=EguiFrame,
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorMode {
None,
NotDefault,
All,
}
// TODO: due to the lifetime of MultiRenderer, we cannot be generic over CustomElem's renderer
// util after GATs land. So we generate with the macro for Gles2 and then
// do a manual impl for MultiRenderer.
impl RenderElement<GlMultiRenderer<'_>> for CustomElem {
fn id(&self) -> usize {
RenderElement::<Gles2Renderer>::id(self)
}
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
RenderElement::<Gles2Renderer>::location(self, scale)
}
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
RenderElement::<Gles2Renderer>::geometry(self, scale)
}
fn accumulated_damage(
&self,
scale: impl Into<Scale<f64>>,
for_values: Option<SpaceOutputTuple<'_, '_>>,
) -> Vec<Rectangle<i32, Physical>> {
RenderElement::<Gles2Renderer>::accumulated_damage(self, scale, for_values)
}
fn opaque_regions(
&self,
scale: impl Into<Scale<f64>>,
) -> Option<Vec<Rectangle<i32, Physical>>> {
RenderElement::<Gles2Renderer>::opaque_regions(self, scale)
}
fn draw(
&self,
renderer: &mut GlMultiRenderer<'_>,
frame: &mut GlMultiFrame,
scale: impl Into<Scale<f64>>,
location: Point<f64, Physical>,
damage: &[Rectangle<i32, Physical>],
log: &Logger,
) -> Result<(), MultiError<EglGlesBackend, EglGlesBackend>> {
RenderElement::<Gles2Renderer>::draw(
self,
renderer.as_mut(),
frame.as_mut(),
scale,
location,
damage,
log,
)
.map_err(MultiError::Render)
}
fn z_index(&self) -> u8 {
RenderElement::<Gles2Renderer>::z_index(self)
}
}
pub trait AsGles2Renderer {
fn as_gles2(&mut self) -> &mut Gles2Renderer;
}
impl AsGles2Renderer for Gles2Renderer {
fn as_gles2(&mut self) -> &mut Gles2Renderer {
self
}
}
impl AsGles2Renderer for GlMultiRenderer<'_> {
fn as_gles2(&mut self) -> &mut Gles2Renderer {
self.as_mut()
}
}
pub fn needs_buffer_reset(output: &Output, state: &Common) -> bool {
use std::sync::atomic::{AtomicBool, Ordering};
struct DidCustomRendering(AtomicBool);
let will_render_custom = {
let workspace = state.shell.active_space(output);
workspace.get_fullscreen(output).is_some()
};
let userdata = output.user_data();
userdata.insert_if_missing(|| DidCustomRendering(AtomicBool::new(false)));
userdata
.get::<DidCustomRendering>()
.unwrap()
.0
.swap(will_render_custom, Ordering::AcqRel)
!= will_render_custom
}
pub fn cursor_custom_elements<R>(
pub fn cursor_elements<E, R>(
renderer: &mut R,
state: &Common,
output: &Output,
hardware_cursor: bool,
) -> Vec<CustomElem>
mode: CursorMode,
) -> Vec<E>
where
R: AsGles2Renderer,
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
<R as Renderer>::Frame: AsGles2Frame,
<R as Renderer>::TextureId: Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
E: From<CursorRenderElement<R>> + From<CosmicMappedRenderElement<R>>,
{
let mut custom_elements = Vec::new();
let scale = output.current_scale().fractional_scale();
let mut elements = Vec::new();
for seat in &state.seats {
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);
let location = pointer.current_location() - output.current_location().to_f64();
if let Some(grab) = seat
if mode != CursorMode::None {
elements.extend(
cursor::draw_cursor(
renderer,
seat,
location,
scale.into(),
state.clock.now(),
mode != CursorMode::NotDefault,
)
.into_iter()
.map(E::from),
);
}
if let Some(wl_surface) = get_dnd_icon(seat) {
elements.extend(
cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale)
.into_iter()
.map(E::from),
);
}
if let Some(grab_elements) = seat
.user_data()
.get::<SeatMoveGrabState>()
.unwrap()
.borrow()
.as_ref()
.and_then(|state| state.render(seat, output))
.map(|state| state.render::<E, R>(seat, output))
{
custom_elements.push(grab);
}
if let Some(wl_surface) = get_dnd_icon(seat) {
custom_elements.push(cursor::draw_dnd_icon(wl_surface, location.to_i32_round()).into());
}
if let Some(cursor) = cursor::draw_cursor(
renderer.as_gles2(),
seat,
location,
&state.start_time,
!hardware_cursor,
) {
custom_elements.push(cursor)
elements.extend(grab_elements);
}
}
custom_elements
elements
}
pub fn render_output<R>(
pub fn render_output<R, Target, OffTarget, Source>(
gpu: Option<&DrmNode>,
renderer: &mut R,
age: u8,
target: Target,
damage_tracker: &mut DamageTrackedRenderer,
age: usize,
state: &mut Common,
output: &Output,
hardware_cursor: bool,
#[cfg(feature = "debug")] mut fps: Option<&mut Fps>,
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
cursor_mode: CursorMode,
screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
fps: Option<&mut Fps>,
) -> Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
R: Renderer
+ ImportAll
+ ImportMem
+ ExportMem
+ Bind<Dmabuf>
+ Bind<Target>
+ Offscreen<OffTarget>
+ Blit<Source>
+ AsGlowRenderer,
<R as Renderer>::Frame: AsGles2Frame,
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
<R as Renderer>::Error: From<Gles2Error>,
CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>,
Source: Clone,
{
let workspace = state.shell.active_space(output).idx;
let handle = state.shell.workspaces.active(output).handle;
render_workspace(
gpu,
renderer,
target,
damage_tracker,
age,
state,
workspace,
output,
hardware_cursor,
&handle,
cursor_mode,
screencopy,
fps,
)
}
pub fn render_workspace<R>(
pub fn render_workspace<R, Target, OffTarget, Source>(
gpu: Option<&DrmNode>,
renderer: &mut R,
age: u8,
target: Target,
damage_tracker: &mut DamageTrackedRenderer,
age: usize,
state: &mut Common,
space_idx: u8,
output: &Output,
hardware_cursor: bool,
#[cfg(feature = "debug")] mut fps: Option<&mut Fps>,
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
handle: &WorkspaceHandle,
mut cursor_mode: CursorMode,
screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
mut fps: Option<&mut Fps>,
) -> Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
R: Renderer
+ ImportAll
+ ImportMem
+ ExportMem
+ Bind<Dmabuf>
+ Bind<Target>
+ Offscreen<OffTarget>
+ Blit<Source>
+ AsGlowRenderer,
<R as Renderer>::Frame: AsGles2Frame,
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
<R as Renderer>::Error: From<Gles2Error>,
CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>,
Source: Clone,
{
#[cfg(feature = "debug")]
if let Some(ref mut fps) = fps {
fps.start();
}
let space_idx = space_idx as usize;
let workspace = &mut state.shell.spaces[space_idx];
let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned();
let workspace = state.shell.space_for_handle(&handle).ok_or(OutputNoMode)?;
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.as_deref_mut(),
)
}
} else {
#[cfg(not(feature = "debug"))]
{
render_desktop(
gpu,
renderer,
age,
state,
space_idx,
output,
hardware_cursor,
)
}
#[cfg(feature = "debug")]
{
render_desktop(
gpu,
renderer,
age,
state,
space_idx,
output,
hardware_cursor,
fps.as_deref_mut(),
)
}
let screencopy_contains_embedded = screencopy.as_ref().map_or(false, |(_, sessions)| {
sessions
.iter()
.any(|(s, _)| s.cursor_mode() == ScreencopyCursorMode::Embedded)
});
// cursor handling without a cursor_plane in this case is horrible.
// because what if some session disagree and/or the backend wants to render with a different mode?
// It seems we would need to render to an offscreen buffer in those cases (and do multiple renders, which messes with damage tracking).
// So for now, we just pick the worst mode (embedded), if any requires it.
//
// Once we move to a cursor_plane, the default framebuffer will never contain a cursor and we can just composite the cursor for each session separately on top (or not).
if screencopy_contains_embedded {
cursor_mode = CursorMode::All;
};
let mut elements: Vec<CosmicElement<R>> = cursor_elements(renderer, state, output, cursor_mode);
#[cfg(feature = "debug")]
if let Some(ref mut fps) = fps {
fps.end();
{
let output_geo = output.geometry();
let scale = output.current_scale().fractional_scale();
if let Some(fps) = fps.as_mut() {
let fps_overlay = fps_ui(
gpu,
state,
renderer.glow_renderer_mut(),
fps,
Rectangle::from_loc_and_size(
(0, 0),
(output_geo.size.w.min(400), output_geo.size.h.min(800)),
),
scale,
)
.map_err(<R as Renderer>::Error::from)
.map_err(RenderError::Rendering)?;
elements.push(fps_overlay.into());
}
}
elements.extend(
workspace
.render_output::<R>(output)
.map_err(|_| OutputNoMode)?
.into_iter()
.map(Into::into),
);
if let Some(fps) = fps.as_mut() {
fps.elements();
}
renderer.bind(target).map_err(RenderError::Rendering)?;
let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None);
if let Some(fps) = fps.as_mut() {
fps.render();
}
if let Some((source, buffers)) = screencopy {
if res.is_ok() {
for (session, params) in buffers {
match render_session(
gpu.cloned(),
renderer,
&session,
params,
output.current_transform(),
|_node, buffer, renderer, dtr, age| {
let res = dtr.damage_output(age, &elements, slog_scope::logger())?;
if let (Some(ref damage), _) = &res {
if let Ok(dmabuf) = get_dmabuf(buffer) {
renderer.bind(dmabuf).map_err(RenderError::Rendering)?;
} else {
let size = buffer_dimensions(buffer).unwrap();
let render_buffer = renderer
.create_buffer(size)
.map_err(RenderError::Rendering)?;
renderer
.bind(render_buffer)
.map_err(RenderError::Rendering)?;
}
for rect in damage {
renderer
.blit_from(source.clone(), *rect, *rect, TextureFilter::Nearest)
.map_err(RenderError::Rendering)?;
}
}
Ok(res)
},
) {
Ok(true) => {} // success
Ok(false) => state.still_pending(session.clone(), params.clone()),
Err(err) => {
slog_scope::warn!("Error rendering to screencopy session: {}", err);
session.failed(FailureReason::Unspec);
}
}
}
}
if let Some(fps) = fps.as_mut() {
fps.screencopy();
}
}
res
}
fn render_desktop<R>(
_gpu: Option<&DrmNode>,
renderer: &mut R,
age: u8,
state: &mut Common,
space_idx: usize,
output: &Output,
hardware_cursor: bool,
#[cfg(feature = "debug")] fps: Option<&mut Fps>,
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
{
let mut custom_elements = Vec::<CustomElem>::new();
#[cfg(feature = "debug")]
{
let workspace = &state.shell.spaces[space_idx];
let output_geo = workspace
.space
.output_geometry(output)
.unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0)));
let scale = output.current_scale().fractional_scale();
if let Some(fps) = fps {
let fps_overlay = fps_ui(
_gpu,
state,
fps,
output_geo.to_f64().to_physical(scale),
scale,
);
custom_elements.push(fps_overlay.into());
}
let area = Rectangle::<f64, smithay::utils::Logical>::from_loc_and_size(
state
.shell
.space_relative_output_geometry((0.0f64, 0.0f64), output),
state.shell.global_space().to_f64().size,
)
.to_physical(scale);
if let Some(log_ui) = log_ui(state, area, scale, output_geo.size.w as f32 * 0.6) {
custom_elements.push(log_ui.into());
}
if let Some(debug_overlay) = debug_ui(state, area, scale) {
custom_elements.push(debug_overlay.into());
}
}
custom_elements.extend(cursor_custom_elements(
renderer,
state,
output,
hardware_cursor,
));
state.shell.spaces[space_idx].space.render_output(
renderer,
&output,
age as usize,
CLEAR_COLOR,
&*custom_elements,
)
}
fn render_fullscreen<R>(
_gpu: Option<&DrmNode>,
renderer: &mut R,
window: Window,
state: &mut Common,
output: &Output,
hardware_cursor: bool,
#[cfg(feature = "debug")] fps: Option<&mut Fps>,
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
{
let transform = Transform::from(output.current_transform());
let mode = output.current_mode().unwrap();
let scale = output.current_scale().fractional_scale();
let mut custom_elements = Vec::<CustomElem>::new();
#[cfg(feature = "debug")]
if let Some(fps) = fps {
let output_geo = output.geometry();
let fps_overlay = fps_ui(
_gpu,
state,
fps,
Rectangle::from_loc_and_size((0, 0), output_geo.size)
.to_f64()
.to_physical(scale),
scale,
);
custom_elements.push(fps_overlay.into());
}
custom_elements.extend(cursor_custom_elements(
renderer,
state,
output,
hardware_cursor,
));
renderer
.render(mode.size, transform, |renderer, frame| {
let mut damage = window.accumulated_damage((0.0, 0.0), scale, None);
frame.clear(
CLEAR_COLOR,
&[Rectangle::from_loc_and_size((0, 0), mode.size)],
)?;
draw_window(
renderer,
frame,
&window,
scale,
(0.0, 0.0),
&[Rectangle::from_loc_and_size((0, 0), mode.size)],
&slog_scope::logger(),
)?;
draw_window_popups(
renderer,
frame,
&window,
scale,
(0.0, 0.0),
&[Rectangle::from_loc_and_size((0, 0), mode.size)],
&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.to_f64().to_physical(scale),
&[Rectangle::from_loc_and_size(
(0, 0),
geo.size.to_physical_precise_round(scale),
)],
&slog_scope::logger(),
)?;
draw_layer_popups(
renderer,
frame,
layer_surface,
scale,
geo.loc.to_f64().to_physical(scale),
&[Rectangle::from_loc_and_size(
(0, 0),
geo.size.to_physical_precise_round(scale),
)],
&slog_scope::logger(),
)?;
damage.extend(damage_from_surface_tree(
layer_surface.wl_surface(),
geo.loc.to_f64().to_physical(scale),
scale,
None,
));
}
for elem in custom_elements {
let loc = elem.location(scale);
let geo = elem.geometry(scale);
let elem_damage = elem.accumulated_damage(scale, None);
elem.draw(renderer, frame, scale, loc, &[geo], &slog_scope::logger())?;
damage.extend(elem_damage)
}
Ok(Some(damage))
})
.and_then(std::convert::identity)
.map_err(RenderError::<R>::Rendering)
}

View file

@ -6,17 +6,22 @@ use crate::{
input::Devices,
state::{BackendData, Common, Data},
utils::prelude::*,
wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession},
};
use anyhow::{anyhow, Context, Result};
use smithay::{
backend::{
renderer::{ImportDma, ImportEgl},
renderer::{
damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, ImportDma,
ImportEgl,
},
winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice},
},
desktop::layer_map_for_output,
output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
reexports::{
calloop::{ping, EventLoop},
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::DisplayHandle,
},
utils::Transform,
@ -26,52 +31,71 @@ use std::cell::RefCell;
#[cfg(feature = "debug")]
use crate::state::Fps;
use super::render::CursorMode;
pub struct WinitState {
// The winit backend currently has no notion of multiple windows
pub backend: WinitGraphicsBackend,
pub backend: WinitGraphicsBackend<GlowRenderer>,
output: Output,
age_reset: u8,
damage_tracker: DamageTrackedRenderer,
screencopy: Vec<(ScreencopySession, BufferParams)>,
#[cfg(feature = "debug")]
fps: Fps,
}
impl WinitState {
pub fn render_output(&mut self, state: &mut Common) -> Result<()> {
if render::needs_buffer_reset(&self.output, state) {
self.reset_buffers();
}
self.backend
.bind()
.with_context(|| "Failed to bind buffer")?;
let age = if self.age_reset > 0 {
self.age_reset -= 1;
0
} else {
self.backend.buffer_age().unwrap_or(0)
};
let age = self.backend.buffer_age().unwrap_or(0);
match render::render_output(
let surface = self.backend.egl_surface();
match render::render_output::<_, _, Gles2Renderbuffer, _>(
None,
self.backend.renderer(),
age as u8,
surface.clone(),
&mut self.damage_tracker,
age,
state,
&self.output,
true,
CursorMode::NotDefault,
if !self.screencopy.is_empty() {
Some((surface, &self.screencopy))
} else {
None
},
#[cfg(not(feature = "debug"))]
None,
#[cfg(feature = "debug")]
Some(&mut self.fps),
) {
Ok(damage) => {
state
.shell
.active_space_mut(&self.output)
.space
.send_frames(state.start_time.elapsed().as_millis() as u32);
Ok((damage, states)) => {
self.screencopy.clear();
self.backend
.submit(damage.as_ref().map(|x| &**x))
.submit(damage.as_deref())
.with_context(|| "Failed to submit buffer for display")?;
#[cfg(feature = "debug")]
self.fps.displayed();
state.send_frames(&self.output, &states);
if damage.is_some() {
let mut output_presentation_feedback =
state.take_presentation_feedback(&self.output, &states);
output_presentation_feedback.presented(
state.clock.now(),
self.output
.current_mode()
.map(|mode| mode.refresh as u32)
.unwrap_or_default(),
0,
wp_presentation_feedback::Kind::Vsync,
)
}
}
Err(err) => {
for (session, params) in self.screencopy.drain(..) {
state.still_pending(session, params)
}
anyhow::bail!("Rendering failed: {}", err);
}
};
@ -105,8 +129,10 @@ impl WinitState {
}
}
pub fn reset_buffers(&mut self) {
self.age_reset = 3;
pub fn pending_screencopy(&mut self, new: Option<Vec<(ScreencopySession, BufferParams)>>) {
if let Some(sessions) = new {
self.screencopy.extend(sessions);
}
}
}
@ -133,7 +159,6 @@ pub fn init_backend(
refresh: 60_000,
};
let output = Output::new(name, props, None);
let _global = output.create_global::<State>(dh);
output.add_mode(mode);
output.set_preferred(mode);
output.change_current_state(
@ -180,8 +205,7 @@ pub fn init_backend(
.handle()
.insert_source(event_source, move |_, _, data| {
match input.dispatch_new_events(|event| {
data.state
.process_winit_event(&data.display.handle(), event, &render_ping_handle)
data.state.process_winit_event(event, &render_ping_handle)
}) {
Ok(_) => {
event_ping_handle.ping();
@ -189,7 +213,11 @@ pub fn init_backend(
}
Err(winit::WinitError::WindowClosed) => {
let output = data.state.backend.winit().output.clone();
data.state.common.shell.remove_output(&output);
let seats = data.state.common.seats().cloned().collect::<Vec<_>>();
data.state
.common
.shell
.remove_output(&output, seats.into_iter());
if let Some(token) = token.take() {
event_loop_handle.remove(token);
}
@ -199,27 +227,30 @@ pub fn init_backend(
.map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?;
event_ping.ping();
#[cfg(feature = "debug")]
let fps = Fps::new(backend.renderer());
state.backend = BackendData::Winit(WinitState {
backend,
output: output.clone(),
damage_tracker: DamageTrackedRenderer::from_output(&output),
screencopy: Vec::new(),
#[cfg(feature = "debug")]
fps: Fps::default(),
age_reset: 0,
fps,
});
state
.common
.output_configuration_state
.add_heads(std::iter::once(&output));
state.common.output_configuration_state.update();
state.common.shell.add_output(&output);
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.config.read_outputs(
std::iter::once(&output),
&mut state.common.output_configuration_state,
&mut state.backend,
&mut state.common.shell,
seats.iter().cloned(),
&state.common.event_loop_handle,
);
state.common.shell.refresh_outputs();
state.common.config.write_outputs(std::iter::once(&output));
Ok(())
}
@ -227,7 +258,7 @@ pub fn init_backend(
fn init_egl_client_side(
dh: &DisplayHandle,
state: &mut State,
renderer: &mut WinitGraphicsBackend,
renderer: &mut WinitGraphicsBackend<GlowRenderer>,
) -> Result<()> {
let bind_result = renderer.renderer().bind_wl_display(dh);
match bind_result {
@ -250,19 +281,14 @@ fn init_egl_client_side(
}
impl State {
pub fn process_winit_event(
&mut self,
dh: &DisplayHandle,
event: WinitEvent,
render_ping: &ping::Ping,
) {
pub fn process_winit_event(&mut self, event: WinitEvent, render_ping: &ping::Ping) {
// here we can handle special cases for winit inputs
match event {
WinitEvent::Focus(true) => {
for seat in self.common.seats.clone().iter() {
for seat in self.common.seats().cloned().collect::<Vec<_>>().iter() {
let devices = seat.user_data().get::<Devices>().unwrap();
if devices.has_device(&WinitVirtualDevice) {
set_active_output(seat, &self.backend.winit().output);
seat.set_active_output(&self.backend.winit().output);
break;
}
}
@ -286,7 +312,7 @@ impl State {
output.delete_mode(output.current_mode().unwrap());
output.set_preferred(mode);
output.change_current_state(Some(mode), None, None, None);
layer_map_for_output(output).arrange(dh);
layer_map_for_output(output).arrange();
self.common.output_configuration_state.update();
self.common.shell.refresh_outputs();
render_ping.ping();

View file

@ -6,6 +6,7 @@ use crate::{
input::Devices,
state::{BackendData, Common, Data},
utils::prelude::*,
wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession},
};
use anyhow::{Context, Result};
use smithay::{
@ -13,7 +14,10 @@ use smithay::{
allocator::dmabuf::Dmabuf,
egl::{EGLContext, EGLDisplay},
input::{Event, InputEvent},
renderer::{gles2::Gles2Renderer, Bind, ImportDma, ImportEgl},
renderer::{
damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, Bind,
ImportDma, ImportEgl,
},
x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface},
},
desktop::layer_map_for_output,
@ -21,6 +25,7 @@ use smithay::{
reexports::{
calloop::{ping, EventLoop, LoopHandle},
gbm::{Device as GbmDevice, FdWrapper},
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::DisplayHandle,
},
utils::Transform,
@ -36,7 +41,7 @@ use crate::state::Fps;
pub struct X11State {
allocator: Arc<Mutex<GbmDevice<FdWrapper>>>,
_egl: EGLDisplay,
pub renderer: Gles2Renderer,
pub renderer: GlowRenderer,
surfaces: Vec<Surface>,
handle: X11Handle,
}
@ -114,12 +119,14 @@ impl X11State {
self.surfaces.push(Surface {
window,
surface,
damage_tracker: DamageTrackedRenderer::from_output(&output),
output: output.clone(),
render: ping.clone(),
dirty: false,
pending: true,
screencopy: Vec::new(),
#[cfg(feature = "debug")]
fps: Fps::default(),
fps: Fps::new(&mut self.renderer),
});
// schedule first render
@ -127,9 +134,16 @@ impl X11State {
Ok(output)
}
pub fn schedule_render(&mut self, output: &Output) {
pub fn schedule_render(
&mut self,
output: &Output,
screencopy: Option<Vec<(ScreencopySession, BufferParams)>>,
) {
if let Some(surface) = self.surfaces.iter_mut().find(|s| s.output == *output) {
surface.dirty = true;
if let Some(sessions) = screencopy {
surface.screencopy.extend(sessions);
}
if !surface.pending {
surface.render.ping();
}
@ -168,6 +182,8 @@ impl X11State {
pub struct Surface {
window: Window,
damage_tracker: DamageTrackedRenderer,
screencopy: Vec<(ScreencopySession, BufferParams)>,
surface: X11Surface,
output: Output,
render: ping::Ping,
@ -178,44 +194,56 @@ pub struct Surface {
}
impl Surface {
pub fn render_output(
&mut self,
renderer: &mut Gles2Renderer,
state: &mut Common,
) -> Result<()> {
if render::needs_buffer_reset(&self.output, state) {
self.surface.reset_buffers();
}
pub fn render_output(&mut self, renderer: &mut GlowRenderer, state: &mut Common) -> Result<()> {
let (buffer, age) = self
.surface
.buffer()
.with_context(|| "Failed to allocate buffer")?;
renderer
.bind(buffer)
.with_context(|| "Failed to bind buffer")?;
match render::render_output(
match render::render_output::<_, _, Gles2Renderbuffer, _>(
None,
renderer,
age as u8,
buffer.clone(),
&mut self.damage_tracker,
age as usize,
state,
&self.output,
true,
render::CursorMode::NotDefault,
if !self.screencopy.is_empty() {
Some((buffer, &self.screencopy))
} else {
None
},
#[cfg(not(feature = "debug"))]
None,
#[cfg(feature = "debug")]
Some(&mut self.fps),
) {
Ok(_) => {
state
.shell
.active_space_mut(&self.output)
.space
.send_frames(state.start_time.elapsed().as_millis() as u32);
Ok((damage, states)) => {
self.screencopy.clear();
self.surface
.submit()
.with_context(|| "Failed to submit buffer for display")?;
#[cfg(feature = "debug")]
self.fps.displayed();
state.send_frames(&self.output, &states);
if damage.is_some() {
let mut output_presentation_feedback =
state.take_presentation_feedback(&self.output, &states);
output_presentation_feedback.presented(
state.clock.now(),
self.output
.current_mode()
.map(|mode| mode.refresh as u32)
.unwrap_or_default(),
0,
wp_presentation_feedback::Kind::Vsync,
)
}
}
Err(err) => {
for (session, params) in self.screencopy.drain(..) {
state.still_pending(session, params)
}
self.surface.reset_buffers();
anyhow::bail!("Rendering failed: {}", err);
}
@ -247,7 +275,7 @@ pub fn init_backend(
// Create the OpenGL context
let context = EGLContext::new(&egl, None).with_context(|| "Failed to create EGL context")?;
// Create a renderer
let mut renderer = unsafe { Gles2Renderer::new(context, None) }
let mut renderer = unsafe { GlowRenderer::new(context, None) }
.with_context(|| "Failed to initialize renderer")?;
init_egl_client_side(dh, state, &mut renderer)?;
@ -269,20 +297,19 @@ pub fn init_backend(
.common
.output_configuration_state
.add_heads(std::iter::once(&output));
state.common.output_configuration_state.update();
state.common.shell.add_output(&output);
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.config.read_outputs(
std::iter::once(&output),
&mut state.common.output_configuration_state,
&mut state.backend,
&mut state.common.shell,
seats.iter().cloned(),
&state.common.event_loop_handle,
);
state.common.shell.refresh_outputs();
state.common.config.write_outputs(std::iter::once(&output));
event_loop
.handle()
.insert_source(backend, |event, _, data| match event {
.insert_source(backend, move |event, _, data| match event {
X11Event::CloseRequested { window_id } => {
// TODO: drain_filter
let mut outputs_removed = Vec::new();
@ -303,7 +330,10 @@ pub fn init_backend(
.surfaces
.retain(|s| s.window.id() != window_id);
for output in outputs_removed.into_iter() {
data.state.common.shell.remove_output(&output);
data.state
.common
.shell
.remove_output(&output, seats.iter().cloned());
}
}
X11Event::Resized {
@ -336,7 +366,7 @@ pub fn init_backend(
output.delete_mode(output.current_mode().unwrap());
output.change_current_state(Some(mode), None, None, None);
output.set_preferred(mode);
layer_map_for_output(output).arrange(&data.display.handle());
layer_map_for_output(output).arrange();
data.state.common.output_configuration_state.update();
data.state.common.shell.refresh_outputs();
surface.dirty = true;
@ -368,11 +398,10 @@ pub fn init_backend(
Ok(())
}
fn init_egl_client_side(
dh: &DisplayHandle,
state: &mut State,
renderer: &mut Gles2Renderer,
) -> Result<()> {
fn init_egl_client_side<R>(dh: &DisplayHandle, state: &mut State, renderer: &mut R) -> Result<()>
where
R: ImportEgl + ImportDma,
{
let bind_result = renderer.bind_wl_display(dh);
match bind_result {
Ok(_) => {
@ -405,10 +434,10 @@ impl State {
.unwrap();
let device = event.device();
for seat in self.common.seats.clone().iter() {
for seat in self.common.seats().cloned().collect::<Vec<_>>().iter() {
let devices = seat.user_data().get::<Devices>().unwrap();
if devices.has_device(&device) {
set_active_output(seat, &output);
seat.set_active_output(&output);
break;
}
}
@ -420,8 +449,7 @@ impl State {
self.process_input_event(event);
// TODO actually figure out the output
for output in self.common.shell.outputs() {
self.backend
.schedule_render(&self.common.event_loop_handle, output);
self.backend.x11().schedule_render(output, None);
}
}
}

View file

@ -1,10 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
shell::{focus::FocusDirection, Shell},
state::{BackendData, Data},
shell::{focus::FocusDirection, layout::tiling::Direction, Shell, WorkspaceAmount},
state::{BackendData, Data, State},
wayland::protocols::output_configuration::OutputConfigurationState,
};
use serde::{Deserialize, Serialize};
use smithay::input::Seat;
pub use smithay::{
backend::input::KeyState,
input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState},
@ -32,6 +34,7 @@ pub struct Config {
pub struct StaticConfig {
pub key_bindings: HashMap<KeyPattern, Action>,
pub workspace_mode: WorkspaceMode,
pub workspace_amount: WorkspaceAmount,
pub floating_default: bool,
}
@ -215,6 +218,7 @@ impl Config {
StaticConfig {
key_bindings: HashMap::new(),
workspace_mode: WorkspaceMode::Global,
workspace_amount: WorkspaceAmount::Dynamic,
floating_default: false,
}
}
@ -276,12 +280,14 @@ impl Config {
pub fn read_outputs(
&mut self,
outputs: impl Iterator<Item = impl std::borrow::Borrow<Output>>,
output_state: &mut OutputConfigurationState<State>,
backend: &mut BackendData,
shell: &mut Shell,
seats: impl Iterator<Item = Seat<State>>,
loop_handle: &LoopHandle<'_, Data>,
) {
let outputs = outputs.map(|x| x.borrow().clone()).collect::<Vec<_>>();
let seats = seats.collect::<Vec<_>>();
let outputs = output_state.outputs().collect::<Vec<_>>();
let mut infos = outputs
.iter()
.cloned()
@ -305,14 +311,19 @@ impl Config {
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
{
let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone();
let enabled = output_config.enabled;
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
if let Err(err) =
backend.apply_config_for_output(&output, false, shell, loop_handle)
{
if let Err(err) = backend.apply_config_for_output(
&output,
false,
shell,
seats.iter().cloned(),
loop_handle,
) {
slog_scope::warn!(
"Failed to set new config for output {}: {}",
output.name(),
@ -320,6 +331,12 @@ impl Config {
);
reset = true;
break;
} else {
if enabled {
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
}
}
@ -329,22 +346,36 @@ impl Config {
.into_iter()
.zip(known_good_configs.into_iter())
{
let enabled = output_config.enabled;
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
if let Err(err) =
backend.apply_config_for_output(&output, false, shell, loop_handle)
{
if let Err(err) = backend.apply_config_for_output(
&output,
false,
shell,
seats.iter().cloned(),
loop_handle,
) {
slog_scope::error!(
"Failed to reset config for output {}: {}",
output.name(),
err
);
} else {
if enabled {
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
}
}
}
output_state.update();
self.write_outputs(output_state.outputs());
}
}
@ -689,7 +720,7 @@ pub enum KeyModifier {
Ctrl,
Alt,
Shift,
Logo,
Super,
CapsLock,
NumLock,
}
@ -721,7 +752,7 @@ impl std::ops::AddAssign<KeyModifier> for KeyModifiers {
KeyModifier::Ctrl => self.ctrl = true,
KeyModifier::Alt => self.alt = true,
KeyModifier::Shift => self.shift = true,
KeyModifier::Logo => self.logo = true,
KeyModifier::Super => self.logo = true,
KeyModifier::CapsLock => self.caps_lock = true,
KeyModifier::NumLock => self.num_lock = true,
};
@ -780,13 +811,30 @@ pub enum Action {
Terminate,
Debug,
Close,
Workspace(u8),
NextWorkspace,
PreviousWorkspace,
LastWorkspace,
MoveToWorkspace(u8),
MoveToNextWorkspace,
MoveToPreviousWorkspace,
MoveToLastWorkspace,
NextOutput,
PreviousOutput,
MoveToNextOutput,
MoveToPreviousOutput,
Focus(FocusDirection),
Move(Direction),
ToggleOrientation,
Orientation(crate::shell::layout::Orientation),
ToggleTiling,
ToggleWindowFloating,
Fullscreen,
Screenshot,
Maximize,
Spawn(String),
}

View file

@ -1,22 +1,35 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::collections::HashMap;
use crate::state::{Common, Fps};
use egui::{Color32, Vec2};
use smithay::{
backend::drm::DrmNode,
desktop::layer_map_for_output,
reexports::wayland_server::Resource,
utils::{IsAlive, Physical, Rectangle},
backend::{
drm::DrmNode,
renderer::{
element::texture::TextureRenderElement,
gles2::{Gles2Error, Gles2Texture},
glow::GlowRenderer,
},
},
utils::{Logical, Rectangle},
};
pub use smithay_egui::EguiFrame;
pub const ELEMENTS_COLOR: Color32 = Color32::from_rgb(70, 198, 115);
pub const RENDER_COLOR: Color32 = Color32::from_rgb(29, 114, 58);
pub const SCREENCOPY_COLOR: Color32 = Color32::from_rgb(253, 178, 39);
pub const DISPLAY_COLOR: Color32 = Color32::from_rgb(41, 184, 209);
pub fn fps_ui(
gpu: Option<&DrmNode>,
state: &Common,
renderer: &mut GlowRenderer,
fps: &mut Fps,
area: Rectangle<f64, Physical>,
area: Rectangle<i32, Logical>,
scale: f64,
) -> EguiFrame {
use egui::widgets::plot::{Bar, BarChart, HLine, Legend, Plot};
) -> Result<TextureRenderElement<Gles2Texture>, Gles2Error> {
use egui::widgets::plot::{Bar, BarChart, Legend, Plot};
let (max, min, avg, avg_fps) = (
fps.max_frametime().as_secs_f64(),
@ -24,404 +37,154 @@ pub fn fps_ui(
fps.avg_frametime().as_secs_f64(),
fps.avg_fps(),
);
let bars = fps
let (max_disp, min_disp) = (
fps.max_time_to_display().as_secs_f64(),
fps.min_time_to_display().as_secs_f64(),
);
let amount = avg_fps.round() as usize * 2;
let ((bars_elements, bars_render), (bars_screencopy, bars_displayed)): (
(Vec<Bar>, Vec<Bar>),
(Vec<Bar>, Vec<Bar>),
) = fps
.frames
.iter()
.rev()
.take(30)
.take(amount)
.rev()
.enumerate()
.map(|(i, (_, d))| {
let value = d.as_secs_f64();
let transformed = ((value - min) / (max - min) * 255.0).round() as u8;
Bar::new(i as f64, transformed as f64).fill(egui::Color32::from_rgb(
transformed,
255 - transformed,
0,
))
})
.collect();
.map(|(i, frame)| {
let elements_val = frame.duration_elements.as_secs_f64();
let render_val = frame.duration_render.as_secs_f64();
let screencopy_val = frame
.duration_screencopy
.as_ref()
.map(|val| val.as_secs_f64())
.unwrap_or(0.0);
let displayed_val = frame.duration_displayed.as_secs_f64();
fps.state.run(
let transformed_elements =
((elements_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
let transformed_render =
((render_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
let transformed_screencopy =
((screencopy_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
let transformed_displayed =
((displayed_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
(
(
Bar::new(i as f64, transformed_elements as f64).fill(ELEMENTS_COLOR),
Bar::new(i as f64, transformed_render as f64).fill(RENDER_COLOR),
),
(
Bar::new(i as f64, transformed_screencopy as f64).fill(SCREENCOPY_COLOR),
Bar::new(i as f64, transformed_displayed as f64).fill(DISPLAY_COLOR),
),
)
})
.unzip();
let vendors = HashMap::from([
(
"0x10de",
fps.state
.with_image(renderer, "nvidia", |image, ctx| {
(image.texture_id(ctx), image.size_vec2())
})
.expect("Logo images not loaded?"),
),
(
"0x1002",
fps.state
.with_image(renderer, "amd", |image, ctx| {
(image.texture_id(ctx), image.size_vec2())
})
.expect("Logo images not loaded?"),
),
(
"0x8086",
fps.state
.with_image(renderer, "intel", |image, ctx| {
(image.texture_id(ctx), image.size_vec2())
})
.expect("Logo images not loaded?"),
),
]);
fps.state.render(
|ctx| {
egui::Area::new("main")
.anchor(egui::Align2::LEFT_TOP, (10.0, 10.0))
.show(ctx, |ui| {
let label_res = ui.label(format!(
ui.label(format!(
"cosmic-comp version {}",
std::env!("CARGO_PKG_VERSION")
));
if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..8)) {
ui.label(hash);
if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) {
ui.label(format!(": {hash}"));
}
if !state.egui.active {
ui.label("Press Mod+Escape for debug menu");
ui.label("Press Super+Escape for debug overlay");
} else {
ui.set_max_width(label_res.rect.min.x + label_res.rect.width());
ui.set_max_width(300.0);
ui.separator();
if let Some(gpu) = gpu {
ui.label(egui::RichText::new(format!("renderD{}", gpu.minor())).code());
ui.horizontal(|ui| {
let resp = ui.label(
egui::RichText::new(format!("renderD{}", gpu.minor())).code(),
);
if let Ok(vendor) = std::fs::read_to_string(format!(
"/sys/class/drm/renderD{}/device/vendor",
gpu.minor()
)) {
if let Some((texture_id, mut size)) = vendors.get(vendor.trim())
{
let factor = resp.rect.height() / size.y;
size = Vec2::from([size.x * factor, resp.rect.height()]);
ui.image(*texture_id, size);
}
}
});
}
ui.label(egui::RichText::new(format!("FPS: {:>7.3}", avg_fps)).heading());
ui.label("Frame Times:");
ui.label(egui::RichText::new(format!("avg: {:>7.6}", avg)).code());
ui.label(egui::RichText::new(format!("min: {:>7.6}", min)).code());
ui.label(egui::RichText::new(format!("max: {:>7.6}", max)).code());
let fps_chart = BarChart::new(bars).vertical();
let elements_chart = BarChart::new(bars_elements).vertical();
let render_chart = BarChart::new(bars_render)
.stack_on(&[&elements_chart])
.vertical();
let screencopy_chart = BarChart::new(bars_screencopy)
.stack_on(&[&elements_chart, &render_chart])
.vertical();
let display_chart = BarChart::new(bars_displayed)
.stack_on(&[&elements_chart, &render_chart, &screencopy_chart])
.vertical();
Plot::new("FPS")
.legend(Legend::default())
.view_aspect(33.0)
.view_aspect(50.0)
.include_x(0.0)
.include_x(30.0)
.include_x(amount as f64)
.include_y(0.0)
.include_y(300.0)
.include_y(300)
.show_x(false)
.show(ui, |plot_ui| {
plot_ui.bar_chart(fps_chart);
plot_ui.hline(
HLine::new(avg)
.highlight(true)
.color(egui::Color32::LIGHT_BLUE),
);
plot_ui.bar_chart(elements_chart);
plot_ui.bar_chart(render_chart);
plot_ui.bar_chart(screencopy_chart);
plot_ui.bar_chart(display_chart);
});
}
});
},
renderer,
area,
scale,
1.0,
&state.start_time,
fps.modifiers.clone(),
0.8,
state.clock.now().into(),
)
}
pub fn debug_ui(
state: &mut Common,
area: Rectangle<f64, Physical>,
scale: f64,
) -> Option<EguiFrame> {
if !state.egui.active {
return None;
}
Some(state.egui.debug_state.run(
|ctx| {
use crate::utils::prelude::*;
egui::Window::new("Workspaces")
.default_pos([0.0, 300.0])
.vscroll(true)
.collapsible(true)
.show(ctx, |ui| {
use crate::{
config::WorkspaceMode as ConfigMode,
shell::{OutputBoundState, WorkspaceMode, MAX_WORKSPACES},
};
ui.set_min_width(250.0);
// Mode
ui.label(egui::RichText::new("Mode").heading());
let mut mode = match &state.shell.workspace_mode {
WorkspaceMode::Global { .. } => ConfigMode::Global,
WorkspaceMode::OutputBound => ConfigMode::OutputBound,
};
ui.radio_value(&mut mode, ConfigMode::OutputBound, "Output bound");
ui.radio_value(&mut mode, ConfigMode::Global, "Global");
state.shell.set_mode(mode);
let mode = match &state.shell.workspace_mode {
WorkspaceMode::OutputBound => (ConfigMode::OutputBound, None),
WorkspaceMode::Global { ref active, .. } => {
(ConfigMode::Global, Some(*active))
}
};
match mode {
(ConfigMode::OutputBound, _) => {
ui.label("Workspaces:");
for output in state.shell.outputs().cloned().collect::<Vec<_>>() {
ui.horizontal(|ui| {
let active = output
.user_data()
.get::<OutputBoundState>()
.unwrap()
.active
.get();
let mut active_val = active as f64;
ui.label(output.name());
ui.add(
egui::DragValue::new(&mut active_val)
.clamp_range(0..=(MAX_WORKSPACES - 1))
.speed(1.0),
);
if active != active_val as usize {
state.shell.activate(
&state.seats[0],
&output,
active_val as usize,
);
}
});
}
}
(ConfigMode::Global, Some(active)) => {
ui.horizontal(|ui| {
let mut active_val = active as f64;
ui.label("Workspace:");
ui.add(
egui::DragValue::new(&mut active_val)
.clamp_range(0..=(MAX_WORKSPACES - 1))
.speed(1.0),
);
if active != active_val as usize {
let output = state.shell.outputs().next().cloned().unwrap();
state.shell.activate(
&state.seats[0],
&output,
active_val as usize,
);
}
});
}
_ => unreachable!(),
}
// Spaces
for (i, workspace) in state.shell.spaces.iter().enumerate() {
ui.collapsing(format!("Space: {}", i), |ui| {
ui.collapsing(format!("Windows"), |ui| {
for window in workspace.space.windows() {
ui.collapsing(format!("{:?}", window.toplevel()), |ui| {
ui.label(format!("Rect: {:?}", {
let mut geo = window.geometry();
geo.loc += workspace
.space
.window_location(window)
.unwrap_or((0, 0).into());
geo
}));
ui.label(format!(
"Bounding box: {:?}",
workspace.space.window_bbox(window)
));
});
}
})
});
}
});
egui::Window::new("Outputs")
.collapsible(true)
.hscroll(true)
.default_pos([300.0, 300.0])
.show(ctx, |ui| {
ui.label(format!("Global Space: {:?}", state.shell.global_space()));
for output in state
.shell
.outputs()
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
ui.separator();
ui.collapsing(output.name(), |ui| {
ui.label(format!("Mode: {:#?}", output.current_mode()));
ui.label(format!("Scale: {:#?}", output.current_scale()));
ui.label(format!("Transform: {:#?}", output.current_transform()));
ui.label(format!("Geometry: {:?}", output.geometry()));
ui.label(format!(
"Local Geometry: {:?}",
state
.shell
.active_space(&output)
.space
.output_geometry(&output)
));
ui.label(format!(
"Relative Geometry: {:?}",
state
.shell
.space_relative_output_geometry((0i32, 0i32), &output)
));
ui.separator();
ui.collapsing("Layers:", |ui| {
let map = layer_map_for_output(&output);
for layer in map.layers() {
ui.collapsing(
format!(
"{}/{:?}",
layer.wl_surface().id(),
layer.wl_surface().client_id()
),
|ui| {
ui.label(format!(
"Alive: {:?} {:?} {:?}",
layer.alive(),
layer.layer_surface().alive(),
layer.wl_surface().alive()
));
ui.label(format!("Layer: {:?}", layer.layer()));
ui.label(format!("Namespace: {:?}", layer.namespace()));
ui.label(format!("Geometry: {:?}", layer.bbox()));
ui.label(format!(
"Anchor: {:?}",
layer.cached_state().anchor
));
ui.label(format!(
"Margin: {:?}",
layer.cached_state().margin
));
ui.label(format!(
"Exclusive: {:?}",
layer.cached_state().exclusive_zone
));
},
);
}
ui.label(format!("{:?}", map));
});
});
}
});
},
area,
scale,
state.egui.alpha,
&state.start_time,
state.egui.modifiers.clone(),
))
}
pub fn log_ui(
state: &mut Common,
area: Rectangle<f64, Physical>,
scale: f64,
default_width: f32,
) -> Option<EguiFrame> {
if !state.egui.active {
return None;
}
Some(state.egui.log_state.run(
|ctx| {
egui::SidePanel::right("Log")
.frame(egui::Frame {
inner_margin: egui::Vec2::new(10.0, 10.0).into(),
outer_margin: egui::Vec2::new(0.0, 0.0).into(),
rounding: 5.0.into(),
shadow: egui::epaint::Shadow {
extrusion: 0.0,
color: egui::Color32::TRANSPARENT,
},
fill: egui::Color32::from_black_alpha(100),
stroke: egui::Stroke::none(),
})
.default_width(default_width)
.show(ctx, |ui| {
egui::ScrollArea::vertical()
.always_show_scroll(true)
.stick_to_bottom()
.show(ui, |ui| {
for (_i, record) in state
.log
.debug_buffer
.lock()
.unwrap()
.iter()
.rev()
.enumerate()
{
let mut message = egui::text::LayoutJob::single_section(
record.level.as_short_str().to_string(),
egui::TextFormat::simple(
egui::FontId::monospace(16.0),
match record.level {
slog::Level::Critical => egui::Color32::RED,
slog::Level::Error => egui::Color32::LIGHT_RED,
slog::Level::Warning => egui::Color32::LIGHT_YELLOW,
slog::Level::Info => egui::Color32::LIGHT_BLUE,
slog::Level::Debug => egui::Color32::LIGHT_GREEN,
slog::Level::Trace => egui::Color32::GRAY,
},
),
);
message.append(
&record.message,
6.0,
egui::TextFormat::simple(
egui::FontId::default(),
egui::Color32::WHITE,
),
);
ui.vertical(|ui| {
ui.add(egui::Label::new(message));
ui.add_space(4.0);
for (k, v) in &record.kv {
ui.horizontal(|ui| {
ui.add(
egui::Label::new(egui::RichText::new(k).code())
.sense(egui::Sense::click()),
)
.on_hover_cursor(egui::CursorIcon::PointingHand);
render_value(ui, v);
});
}
});
}
})
});
},
area,
scale,
state.egui.alpha,
&state.start_time,
state.egui.modifiers.clone(),
))
}
fn render_value(ui: &mut egui::Ui, value: &serde_json::Value) {
use serde_json::Value::*;
match value {
Null => {
ui.label(egui::RichText::new("null").code());
}
Bool(val) => {
ui.label(egui::RichText::new(format!("{}", val)).code());
}
Number(val) => {
ui.label(egui::RichText::new(format!("{}", val)).code());
}
String(val) => {
ui.label(val);
}
Array(list) => {
ui.vertical(|ui| {
ui.label("[");
for val in list {
ui.horizontal(|ui| {
ui.add_space(4.0);
render_value(ui, val);
});
}
ui.label("]");
});
}
Object(map) => {
ui.vertical(|ui| {
for (k, val) in map {
ui.horizontal(|ui| {
ui.add_space(4.0);
ui.add(egui::Label::new(egui::RichText::new(k).code()));
render_value(ui, val);
});
}
});
}
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,104 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "debug")]
use std::{
collections::VecDeque,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};
use anyhow::Result;
use slog::Drain;
#[cfg(feature = "debug")]
mod serializer;
#[cfg(feature = "debug")]
const MAX_RECORDS: usize = 1000;
#[cfg(feature = "debug")]
pub type LogBuffer = Arc<Mutex<VecDeque<OwnedRecord>>>;
#[cfg(feature = "debug")]
#[derive(Clone)]
struct DebugDrain {
buffer: LogBuffer,
dirty_flag: Arc<AtomicBool>,
}
pub struct LogState {
_guard: slog_scope::GlobalLoggerGuard,
#[cfg(feature = "debug")]
pub dirty_flag: Arc<AtomicBool>,
#[cfg(feature = "debug")]
pub debug_buffer: LogBuffer,
}
#[cfg(feature = "debug")]
pub struct OwnedRecord {
pub message: String,
pub level: slog::Level,
pub kv: serde_json::map::Map<String, serde_json::Value>,
}
#[cfg(feature = "debug")]
impl DebugDrain {
fn new() -> (DebugDrain, LogBuffer, Arc<AtomicBool>) {
let dirty_flag = Arc::new(AtomicBool::new(false));
let buffer = Arc::new(Mutex::new(VecDeque::new()));
(
DebugDrain {
buffer: buffer.clone(),
dirty_flag: dirty_flag.clone(),
},
buffer,
dirty_flag,
)
}
}
#[cfg(feature = "debug")]
impl Drain for DebugDrain {
type Ok = ();
type Err = slog::Error;
fn log(
&self,
record: &slog::Record<'_>,
values: &slog::OwnedKVList,
) -> Result<Self::Ok, Self::Err> {
use serde_json::value::{Serializer as ValueSerializer, Value};
use serializer::SerdeSerializer;
use slog::KV;
let mut serializer = SerdeSerializer::start(ValueSerializer, None)?;
values.serialize(record, &mut serializer)?;
record.kv().serialize(record, &mut serializer)?;
let value = match serializer.end().map_err(|_| slog::Error::Other)? {
Value::Object(map) => map,
_ => unreachable!(),
};
let mut buffer = self.buffer.lock().unwrap();
buffer.push_front(OwnedRecord {
message: format!("{}", record.msg()),
level: record.level(),
kv: value,
});
buffer.truncate(MAX_RECORDS);
self.dirty_flag.store(true, Ordering::SeqCst);
Ok(())
}
}
pub fn init_logger() -> Result<LogState> {
let decorator = slog_term::TermDecorator::new().stderr().build();
// usually we would not want to use a Mutex here, but this is usefull for a prototype,
// to make sure we do not miss any in-flight messages, when we crash.
#[cfg(not(feature = "debug"))]
let logger = slog::Logger::root(
std::sync::Mutex::new(
slog_term::CompactFormat::new(decorator)
@ -108,21 +20,6 @@ pub fn init_logger() -> Result<LogState> {
.fuse(),
slog::o!(),
);
#[cfg(feature = "debug")]
let (debug_drain, debug_buffer, dirty_flag) = DebugDrain::new();
#[cfg(feature = "debug")]
let logger = slog::Logger::root(
slog::Duplicate::new(
std::sync::Mutex::new(
slog_term::CompactFormat::new(decorator)
.build()
.ignore_res(),
),
debug_drain,
)
.fuse(),
slog::o!(),
);
let _guard = slog_scope::set_global_logger(logger);
slog_stdlog::init().unwrap();
@ -135,11 +32,5 @@ pub fn init_logger() -> Result<LogState> {
);
}
Ok(LogState {
_guard,
#[cfg(feature = "debug")]
debug_buffer,
#[cfg(feature = "debug")]
dirty_flag,
})
Ok(LogState { _guard })
}

View file

@ -9,11 +9,7 @@ use smithay::{
};
use anyhow::{Context, Result};
use std::{
ffi::OsString,
os::unix::prelude::AsRawFd,
sync::{atomic::Ordering, Arc},
};
use std::{ffi::OsString, os::unix::prelude::AsRawFd, sync::Arc};
pub mod backend;
pub mod config;
@ -68,18 +64,9 @@ fn main() -> Result<()> {
}
// trigger routines
data.state.common.shell.refresh(&data.display.handle());
data.state.common.shell.refresh();
state::Common::refresh_focus(&mut data.state);
// do we need to trigger another render
if data.state.common.dirty_flag.swap(false, Ordering::SeqCst) {
for output in data.state.common.shell.outputs() {
data.state
.backend
.schedule_render(&data.state.common.event_loop_handle, output)
}
}
// send out events
let _ = data.display.flush_clients();
})?;

1101
src/shell/element/mod.rs Normal file

File diff suppressed because it is too large Load diff

400
src/shell/element/stack.rs Normal file
View file

@ -0,0 +1,400 @@
use crate::{
state::State, utils::prelude::SeatExt, wayland::handlers::screencopy::ScreencopySessions,
};
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType;
use smithay::{
backend::{
input::KeyState,
renderer::{
element::{surface::WaylandSurfaceRenderElement, AsRenderElements},
ImportAll, Renderer,
},
},
desktop::{space::SpaceElement, Kind, Window},
input::{
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget},
Seat,
},
output::Output,
render_elements,
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size},
};
use std::{
hash::Hash,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex,
},
};
#[derive(Debug, Clone)]
pub struct CosmicStack {
windows: Arc<Mutex<Vec<Window>>>,
active: Arc<AtomicUsize>,
last_location: Arc<Mutex<Option<(Point<f64, Logical>, Serial, u32)>>>,
previous_keyboard: Arc<AtomicUsize>,
previous_pointer: Arc<AtomicUsize>,
pub(super) header: Arc<Mutex<Option<HeaderBar>>>,
}
impl PartialEq for CosmicStack {
fn eq(&self, other: &Self) -> bool {
*self.windows.lock().unwrap() == *other.windows.lock().unwrap()
}
}
impl Eq for CosmicStack {}
impl Hash for CosmicStack {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.windows).hash(state)
}
}
#[derive(Debug)]
pub struct HeaderBar {}
impl HeaderBar {
pub fn height(&self) -> i32 {
0
}
}
impl CosmicStack {
pub fn active(&self) -> Window {
self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)].clone()
}
pub fn set_active(&self, window: &Window) {
if let Some(val) = self
.windows
.lock()
.unwrap()
.iter()
.position(|w| w == window)
{
let old = self.active.swap(val, Ordering::SeqCst);
self.previous_keyboard.store(old, Ordering::SeqCst);
self.previous_pointer.store(old, Ordering::SeqCst);
}
}
pub fn set_size(&self, size: Size<i32, Logical>) {
let surface_size = (
size.w,
size.h
- self
.header
.lock()
.unwrap()
.as_ref()
.map(|h| h.height())
.unwrap_or(0),
)
.into();
for window in self.windows.lock().unwrap().iter() {
match window.toplevel() {
Kind::Xdg(xdg) => xdg.with_pending_state(|state| state.size = Some(surface_size)),
};
}
}
fn keyboard_leave_if_previous(
&self,
seat: &Seat<State>,
data: &mut State,
serial: Serial,
) -> usize {
let active = self.active.load(Ordering::SeqCst);
let previous = self.previous_keyboard.swap(active, Ordering::SeqCst);
if previous != active {
KeyboardTarget::leave(&self.windows.lock().unwrap()[previous], seat, data, serial);
// TODO: KeyboardTarget::enter(&self.windows.lock().unwrap()[active], seat, data, serial, seat.keys())
}
active
}
fn pointer_leave_if_previous(
&self,
seat: &Seat<State>,
data: &mut State,
serial: Serial,
time: u32,
location: Point<f64, Logical>,
) -> usize {
let active = self.active.load(Ordering::SeqCst);
let previous = self.previous_pointer.swap(active, Ordering::SeqCst);
if previous != active {
if let Some(sessions) = self.windows.lock().unwrap()[previous]
.user_data()
.get::<ScreencopySessions>()
{
for session in &*sessions.0.borrow() {
session.cursor_leave(seat, InputType::Pointer)
}
}
PointerTarget::leave(
&self.windows.lock().unwrap()[previous],
seat,
data,
serial,
time,
);
if let Some(sessions) = self.windows.lock().unwrap()[active]
.user_data()
.get::<ScreencopySessions>()
{
for session in &*sessions.0.borrow() {
session.cursor_enter(seat, InputType::Pointer)
}
}
PointerTarget::enter(
&self.windows.lock().unwrap()[active],
seat,
data,
&MotionEvent {
location,
serial,
time,
},
);
}
active
}
}
impl IsAlive for CosmicStack {
fn alive(&self) -> bool {
self.windows.lock().unwrap().iter().any(IsAlive::alive)
}
}
impl SpaceElement for CosmicStack {
fn bbox(&self) -> Rectangle<i32, Logical> {
SpaceElement::bbox(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)])
}
fn is_in_input_region(&self, point: &Point<f64, Logical>) -> bool {
SpaceElement::is_in_input_region(
&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)],
point,
)
}
fn set_activate(&self, activated: bool) {
self.windows
.lock()
.unwrap()
.iter()
.for_each(|w| SpaceElement::set_activate(w, activated))
}
fn output_enter(&self, output: &Output, overlap: Rectangle<i32, Logical>) {
self.windows
.lock()
.unwrap()
.iter()
.for_each(|w| SpaceElement::output_enter(w, output, overlap))
}
fn output_leave(&self, output: &Output) {
self.windows
.lock()
.unwrap()
.iter()
.for_each(|w| SpaceElement::output_leave(w, output))
}
fn geometry(&self) -> Rectangle<i32, Logical> {
SpaceElement::geometry(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)])
}
fn z_index(&self) -> u8 {
SpaceElement::z_index(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)])
}
fn refresh(&self) {
let mut windows = self.windows.lock().unwrap();
windows.retain(IsAlive::alive);
let len = windows.len();
let _ = self
.active
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| {
(active > len).then_some(len - 1)
});
windows.iter().for_each(|w| SpaceElement::refresh(w))
}
}
impl KeyboardTarget<State> for CosmicStack {
fn enter(
&self,
seat: &Seat<State>,
data: &mut State,
keys: Vec<KeysymHandle<'_>>,
serial: Serial,
) {
let active = self.active.load(Ordering::SeqCst);
self.previous_keyboard.store(active, Ordering::SeqCst);
KeyboardTarget::enter(
&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)],
seat,
data,
keys,
serial,
)
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial) {
let active = self.keyboard_leave_if_previous(seat, data, serial);
KeyboardTarget::leave(&self.windows.lock().unwrap()[active], seat, data, serial)
}
fn key(
&self,
seat: &Seat<State>,
data: &mut State,
key: KeysymHandle<'_>,
state: KeyState,
serial: Serial,
time: u32,
) {
let active = self.keyboard_leave_if_previous(seat, data, serial);
KeyboardTarget::key(
&self.windows.lock().unwrap()[active],
seat,
data,
key,
state,
serial,
time,
)
}
fn modifiers(
&self,
seat: &Seat<State>,
data: &mut State,
modifiers: ModifiersState,
serial: Serial,
) {
let active = self.keyboard_leave_if_previous(seat, data, serial);
KeyboardTarget::modifiers(
&self.windows.lock().unwrap()[active],
seat,
data,
modifiers,
serial,
)
}
}
impl PointerTarget<State> for CosmicStack {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
if let Some(sessions) = self.active().user_data().get::<ScreencopySessions>() {
for session in &*sessions.0.borrow() {
session.cursor_enter(seat, InputType::Pointer)
}
}
*self.last_location.lock().unwrap() = Some((event.location, event.serial, event.time));
let active = self.active.load(Ordering::SeqCst);
self.previous_pointer.store(active, Ordering::SeqCst);
PointerTarget::enter(
&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)],
seat,
data,
event,
)
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
let active =
self.pointer_leave_if_previous(seat, data, event.serial, event.time, event.location);
if let Some(sessions) = self.active().user_data().get::<ScreencopySessions>() {
for session in &*sessions.0.borrow() {
let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale
if let Some((geo, hotspot)) =
seat.cursor_geometry(buffer_loc, data.common.clock.now())
{
session.cursor_info(seat, InputType::Pointer, geo, hotspot);
}
}
}
PointerTarget::motion(&self.windows.lock().unwrap()[active], seat, data, event)
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
if let Some((location, _serial, _time)) = self.last_location.lock().unwrap().clone() {
self.pointer_leave_if_previous(seat, data, event.serial, event.time, location);
}
PointerTarget::button(
&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)],
seat,
data,
event,
)
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
if let Some((location, serial, time)) = self.last_location.lock().unwrap().clone() {
self.pointer_leave_if_previous(seat, data, serial, time, location);
}
PointerTarget::axis(
&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)],
seat,
data,
frame,
)
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
if let Some((location, serial, time)) = self.last_location.lock().unwrap().clone() {
self.pointer_leave_if_previous(seat, data, serial, time, location);
}
if let Some(sessions) = self.active().user_data().get::<ScreencopySessions>() {
for session in &*sessions.0.borrow() {
session.cursor_leave(seat, InputType::Pointer)
}
}
PointerTarget::leave(
&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)],
seat,
data,
serial,
time,
)
}
}
render_elements! {
pub CosmicStackRenderElement<R> where R: ImportAll;
Window=WaylandSurfaceRenderElement,
}
impl<R> AsRenderElements<R> for CosmicStack
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: 'static,
{
type RenderElement = CosmicStackRenderElement<R>;
fn render_elements<C: From<Self::RenderElement>>(
&self,
location: Point<i32, Physical>,
scale: Scale<f64>,
) -> Vec<C> {
AsRenderElements::<R>::render_elements::<CosmicStackRenderElement<R>>(
&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)],
location,
scale,
)
.into_iter()
.map(C::from)
.collect()
}
}
impl CosmicStack {
pub fn windows(&self) -> impl Iterator<Item = Window> {
self.windows
.lock()
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>()
.into_iter()
}
}

262
src/shell/element/window.rs Normal file
View file

@ -0,0 +1,262 @@
use crate::{
state::State, utils::prelude::SeatExt, wayland::handlers::screencopy::ScreencopySessions,
};
use smithay::{
backend::{
input::KeyState,
renderer::{
element::{surface::WaylandSurfaceRenderElement, AsRenderElements},
ImportAll, Renderer,
},
},
desktop::{space::SpaceElement, Kind, Window},
input::{
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget},
Seat,
},
output::Output,
reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode,
render_elements,
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size},
wayland::shell::xdg::ToplevelSurface,
};
use std::{
hash::Hash,
sync::{Arc, Mutex},
};
#[derive(Debug, Clone)]
pub struct CosmicWindow {
pub(super) window: Window,
pub(super) header: Arc<Mutex<Option<HeaderBar>>>,
}
impl PartialEq<Window> for CosmicWindow {
fn eq(&self, other: &Window) -> bool {
&self.window == other
}
}
impl PartialEq<CosmicWindow> for Window {
fn eq(&self, other: &CosmicWindow) -> bool {
self == &other.window
}
}
impl PartialEq for CosmicWindow {
fn eq(&self, other: &Self) -> bool {
self.window == other.window
}
}
impl Eq for CosmicWindow {}
impl Hash for CosmicWindow {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.window.hash(state)
}
}
impl CosmicWindow {
pub fn set_size(&self, size: Size<i32, Logical>) {
let surface_size = (
size.w,
size.h
- self
.header
.lock()
.unwrap()
.as_ref()
.map(|h| h.height())
.unwrap_or(0),
)
.into();
match self.window.toplevel() {
Kind::Xdg(xdg) => xdg.with_pending_state(|state| state.size = Some(surface_size)),
};
}
}
impl From<Window> for CosmicWindow {
fn from(window: Window) -> Self {
let is_ssd = matches!(
match window.toplevel() {
Kind::Xdg(xdg) => xdg.current_state().decoration_mode,
},
Some(DecorationMode::ServerSide)
);
CosmicWindow {
window,
header: Arc::new(Mutex::new(is_ssd.then_some(HeaderBar::default()))),
}
}
}
impl From<ToplevelSurface> for CosmicWindow {
fn from(surf: ToplevelSurface) -> Self {
let is_ssd = matches!(
surf.current_state().decoration_mode,
Some(DecorationMode::ServerSide)
);
CosmicWindow {
window: Window::new(Kind::Xdg(surf)),
header: Arc::new(Mutex::new(is_ssd.then_some(HeaderBar::default()))),
}
}
}
#[derive(Debug, Default, PartialEq)]
pub(super) struct HeaderBar {
pointer_loc: Option<Point<f64, Logical>>,
close_button_hover: bool,
maximize_button_hover: bool,
}
impl HeaderBar {
pub fn height(&self) -> i32 {
0
}
}
impl IsAlive for CosmicWindow {
fn alive(&self) -> bool {
self.window.alive()
}
}
impl SpaceElement for CosmicWindow {
fn bbox(&self) -> Rectangle<i32, Logical> {
SpaceElement::bbox(&self.window)
}
fn is_in_input_region(&self, point: &Point<f64, Logical>) -> bool {
SpaceElement::is_in_input_region(&self.window, point)
}
fn set_activate(&self, activated: bool) {
SpaceElement::set_activate(&self.window, activated)
}
fn output_enter(&self, output: &Output, overlap: Rectangle<i32, Logical>) {
SpaceElement::output_enter(&self.window, output, overlap)
}
fn output_leave(&self, output: &Output) {
SpaceElement::output_leave(&self.window, output)
}
fn geometry(&self) -> Rectangle<i32, Logical> {
SpaceElement::geometry(&self.window)
}
fn z_index(&self) -> u8 {
SpaceElement::z_index(&self.window)
}
fn refresh(&self) {
SpaceElement::refresh(&self.window)
}
}
impl KeyboardTarget<State> for CosmicWindow {
fn enter(
&self,
seat: &Seat<State>,
data: &mut State,
keys: Vec<KeysymHandle<'_>>,
serial: Serial,
) {
KeyboardTarget::enter(&self.window, seat, data, keys, serial)
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial) {
KeyboardTarget::leave(&self.window, seat, data, serial)
}
fn key(
&self,
seat: &Seat<State>,
data: &mut State,
key: KeysymHandle<'_>,
state: KeyState,
serial: Serial,
time: u32,
) {
KeyboardTarget::key(&self.window, seat, data, key, state, serial, time)
}
fn modifiers(
&self,
seat: &Seat<State>,
data: &mut State,
modifiers: ModifiersState,
serial: Serial,
) {
KeyboardTarget::modifiers(&self.window, seat, data, modifiers, serial)
}
}
impl PointerTarget<State> for CosmicWindow {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType;
if let Some(sessions) = self.window.user_data().get::<ScreencopySessions>() {
for session in &*sessions.0.borrow() {
session.cursor_enter(seat, InputType::Pointer)
}
}
PointerTarget::enter(&self.window, seat, data, event)
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType;
if let Some(sessions) = self.window.user_data().get::<ScreencopySessions>() {
for session in &*sessions.0.borrow() {
let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale
if let Some((geo, hotspot)) =
seat.cursor_geometry(buffer_loc, data.common.clock.now())
{
session.cursor_info(seat, InputType::Pointer, geo, hotspot);
}
}
}
PointerTarget::motion(&self.window, seat, data, event)
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
PointerTarget::button(&self.window, seat, data, event)
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
PointerTarget::axis(&self.window, seat, data, frame)
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType;
if let Some(sessions) = self.window.user_data().get::<ScreencopySessions>() {
for session in &*sessions.0.borrow() {
session.cursor_leave(seat, InputType::Pointer)
}
}
PointerTarget::leave(&self.window, seat, data, serial, time)
}
}
render_elements! {
pub CosmicWindowRenderElement<R> where R: ImportAll;
Window=WaylandSurfaceRenderElement,
}
impl<R> AsRenderElements<R> for CosmicWindow
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: 'static,
{
type RenderElement = CosmicWindowRenderElement<R>;
fn render_elements<C: From<Self::RenderElement>>(
&self,
location: Point<i32, Physical>,
scale: Scale<f64>,
) -> Vec<C> {
AsRenderElements::<R>::render_elements::<CosmicWindowRenderElement<R>>(
&self.window,
location,
scale,
)
.into_iter()
.map(C::from)
.collect()
}
}

View file

@ -1,24 +1,18 @@
use crate::{
shell::{OutputBoundState, Shell, Workspace, WorkspaceMode},
shell::{element::CosmicMapped, Shell, Workspace},
state::Common,
utils::prelude::*,
wayland::handlers::xdg_shell::PopupGrabData,
};
use indexmap::IndexSet;
use smithay::{
desktop::{layer_map_for_output, PopupUngrabStrategy, Window, WindowSurfaceType},
desktop::{layer_map_for_output, PopupUngrabStrategy},
input::Seat,
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{IsAlive, Serial, SERIAL_COUNTER},
wayland::{
compositor::get_role,
shell::{wlr_layer::LAYER_SURFACE_ROLE, xdg::XDG_TOPLEVEL_ROLE},
},
};
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
};
use std::cell::RefCell;
use self::target::{KeyboardFocusTarget, WindowGroup};
pub mod target;
@ -32,80 +26,59 @@ pub enum FocusDirection {
Out,
}
pub struct FocusStack<'a>(Ref<'a, IndexSet<Window>>);
pub struct FocusStackMut<'a>(RefMut<'a, IndexSet<Window>>);
pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet<CosmicMapped>>);
pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet<CosmicMapped>);
impl<'a> FocusStack<'a> {
pub fn last(&self) -> Option<Window> {
self.0.iter().rev().find(|w| w.toplevel().alive()).cloned()
pub fn last(&self) -> Option<&CosmicMapped> {
self.0
.as_ref()
.and_then(|set| set.iter().rev().find(|w| w.alive()))
}
pub fn iter(&self) -> impl Iterator<Item = &'_ Window> {
self.0.iter().rev().filter(|w| w.toplevel().alive())
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
self.0
.iter()
.flat_map(|set| set.iter().rev().filter(|w| w.alive()))
}
}
impl<'a> FocusStackMut<'a> {
pub fn append(&mut self, window: &Window) {
self.0.retain(|w| w.toplevel().alive());
pub fn append(&mut self, window: &CosmicMapped) {
self.0.retain(|w| w.alive());
self.0.shift_remove(window);
self.0.insert(window.clone());
}
pub fn last(&self) -> Option<Window> {
self.0.iter().rev().find(|w| w.toplevel().alive()).cloned()
pub fn last(&self) -> Option<&CosmicMapped> {
self.0.iter().rev().find(|w| w.alive())
}
pub fn iter(&self) -> impl Iterator<Item = &'_ Window> {
self.0.iter().rev().filter(|w| w.toplevel().alive())
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
self.0.iter().rev().filter(|w| w.alive())
}
}
type FocusStackData = RefCell<(HashMap<u8, IndexSet<Window>>, IndexSet<Window>)>;
impl Workspace {}
impl Workspace {
pub fn focus_stack<'a, 'b>(&'b self, seat: &'a Seat<State>) -> FocusStack<'a> {
seat.user_data()
.insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new())));
let idx = self.idx;
FocusStack(Ref::map(
seat.user_data().get::<FocusStackData>().unwrap().borrow(),
|map| map.0.get(&idx).unwrap_or(&map.1), //TODO: workaround until Ref::filter_map goes stable
))
}
pub fn focus_stack_mut<'a, 'b>(&'b self, seat: &'a Seat<State>) -> FocusStackMut<'a> {
seat.user_data()
.insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new())));
let idx = self.idx;
FocusStackMut(RefMut::map(
seat.user_data()
.get::<FocusStackData>()
.unwrap()
.borrow_mut(),
|map| map.0.entry(idx).or_insert_with(|| IndexSet::new()),
))
}
}
pub struct ActiveFocus(RefCell<Option<WlSurface>>);
pub struct ActiveFocus(RefCell<Option<KeyboardFocusTarget>>);
impl ActiveFocus {
fn set(seat: &Seat<State>, surface: Option<WlSurface>) {
fn set(seat: &Seat<State>, target: Option<KeyboardFocusTarget>) {
if !seat
.user_data()
.insert_if_missing(|| ActiveFocus(RefCell::new(surface.clone())))
.insert_if_missing(|| ActiveFocus(RefCell::new(target.clone())))
{
*seat
.user_data()
.get::<ActiveFocus>()
.unwrap()
.0
.borrow_mut() = surface;
.borrow_mut() = target;
}
}
fn get(seat: &Seat<State>) -> Option<WlSurface> {
fn get(seat: &Seat<State>) -> Option<KeyboardFocusTarget> {
seat.user_data()
.get::<ActiveFocus>()
.and_then(|a| a.0.borrow().clone())
@ -115,30 +88,25 @@ impl ActiveFocus {
impl Shell {
pub fn set_focus<'a>(
state: &mut State,
surface: Option<&WlSurface>,
target: Option<&KeyboardFocusTarget>,
active_seat: &Seat<State>,
serial: Option<Serial>,
) {
// update FocusStack and notify layouts about new focus (if any window)
if let Some(surface) = surface {
if let Some(workspace) = state.common.shell.space_for_window_mut(surface) {
if let Some(window) = workspace
.space
.window_for_surface(surface, WindowSurfaceType::ALL)
{
let mut focus_stack = workspace.focus_stack_mut(active_seat);
if Some(window) != focus_stack.last().as_ref() {
slog_scope::debug!("Focusing window: {:?}", window);
focus_stack.append(window);
// also remove popup grabs, if we are switching focus
if let Some(mut popup_grab) = active_seat
.user_data()
.get::<PopupGrabData>()
.and_then(|x| x.take())
{
if !popup_grab.has_ended() {
popup_grab.ungrab(PopupUngrabStrategy::All);
}
if let Some(KeyboardFocusTarget::Element(mapped)) = target {
if let Some(workspace) = state.common.shell.space_for_mut(mapped) {
let mut focus_stack = workspace.focus_stack.get_mut(active_seat);
if Some(mapped) != focus_stack.last() {
slog_scope::debug!("Focusing window: {:?}", mapped);
focus_stack.append(mapped);
// also remove popup grabs, if we are switching focus
if let Some(mut popup_grab) = active_seat
.user_data()
.get::<PopupGrabData>()
.and_then(|x| x.take())
{
if !popup_grab.has_ended() {
popup_grab.ungrab(PopupUngrabStrategy::All);
}
}
}
@ -147,10 +115,10 @@ impl Shell {
// update keyboard focus
if let Some(keyboard) = active_seat.get_keyboard() {
ActiveFocus::set(active_seat, surface.cloned());
ActiveFocus::set(active_seat, target.cloned());
keyboard.set_focus(
state,
surface.cloned(),
target.cloned(),
serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()),
);
}
@ -160,30 +128,23 @@ impl Shell {
// update activate status
let focused_windows = seats
.flat_map(|seat| {
self.outputs
.iter()
.flat_map(|o| self.active_space(o).focus_stack(seat).last().clone())
self.outputs.iter().flat_map(|o| {
let space = self.active_space(o);
let stack = space.focus_stack.get(seat);
stack.last().cloned()
})
})
.collect::<Vec<_>>();
for output in self.outputs.iter() {
let workspace = match &self.workspace_mode {
WorkspaceMode::OutputBound => {
let active = output
.user_data()
.get::<OutputBoundState>()
.unwrap()
.active
.get();
&mut self.spaces[active]
}
WorkspaceMode::Global { active, .. } => &mut self.spaces[*active],
};
let workspace = self.workspaces.active_mut(output);
for focused in focused_windows.iter() {
workspace.space.raise_window(focused, true);
if workspace.floating_layer.mapped().any(|m| m == focused) {
workspace.floating_layer.space.raise_element(focused, true);
}
}
for window in workspace.space.windows() {
window.set_activated(focused_windows.contains(window));
for window in workspace.mapped() {
window.set_activated(focused_windows.contains(&window));
window.configure();
}
}
@ -193,55 +154,54 @@ impl Shell {
impl Common {
pub fn set_focus(
state: &mut State,
surface: Option<&WlSurface>,
target: Option<&KeyboardFocusTarget>,
active_seat: &Seat<State>,
serial: Option<Serial>,
) {
Shell::set_focus(state, surface, active_seat, serial);
state.common.shell.update_active(state.common.seats.iter());
Shell::set_focus(state, target, active_seat, serial);
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.shell.update_active(seats.iter());
}
pub fn refresh_focus(state: &mut State) {
let seats = state.common.seats.clone();
let seats = state.common.seats().cloned().collect::<Vec<_>>();
for seat in seats {
let output = active_output(&seat, &state.common);
let output = seat.active_output();
if !state.common.shell.outputs.contains(&output) {
seat.set_active_output(&state.common.shell.outputs[0]);
continue;
}
let last_known_focus = ActiveFocus::get(&seat);
if let Some(surface) = last_known_focus {
if surface.alive() {
let is_toplevel = matches!(get_role(&surface), Some(XDG_TOPLEVEL_ROLE));
let is_layer = matches!(get_role(&surface), Some(LAYER_SURFACE_ROLE));
if let Some(popup) = state.common.shell.popups.find_popup(&surface) {
if popup.alive() {
continue;
}
} else if is_layer {
if layer_map_for_output(&output)
.layer_for_surface(&surface, WindowSurfaceType::ALL)
.is_some()
{
continue; // Focus is valid
}
} else if is_toplevel {
let workspace = state.common.shell.active_space(&output);
if let Some(window) = workspace
.space
.window_for_surface(&surface, WindowSurfaceType::ALL)
{
let focus_stack = workspace.focus_stack(&seat);
if !focus_stack.last().map(|w| &w != window).unwrap_or(true) {
if let Some(target) = last_known_focus {
if target.alive() {
match target {
KeyboardFocusTarget::Element(mapped) => {
let workspace = state.common.shell.active_space(&output);
let focus_stack = workspace.focus_stack.get(&seat);
if focus_stack.last().map(|m| m == &mapped).unwrap_or(false) {
continue; // Focus is valid
} else {
slog_scope::debug!("Wrong Window, focus fixup");
}
} else {
slog_scope::debug!("Different workspaces Window, focus fixup");
}
} else {
// unknown surface type, fixup
slog_scope::debug!("Surface unmapped, focus fixup");
}
KeyboardFocusTarget::LayerSurface(layer) => {
if layer_map_for_output(&output).layers().any(|l| l == &layer) {
continue; // Focus is valid
}
}
KeyboardFocusTarget::Group(WindowGroup {
output: weak_output,
..
}) => {
if weak_output == output {
continue; // Focus is valid,
}
}
KeyboardFocusTarget::Popup(_) | KeyboardFocusTarget::Fullscreen(_) => {
continue;
} // Focus is valid
};
} else {
slog_scope::debug!("Surface dead, focus fixup");
}
@ -263,21 +223,24 @@ impl Common {
}
// update keyboard focus
let surface = state
let target = state
.common
.shell
.active_space(&output)
.focus_stack(&seat)
.focus_stack
.get(&seat)
.last()
.map(|w| w.toplevel().wl_surface().clone());
.cloned()
.map(KeyboardFocusTarget::from);
if let Some(keyboard) = seat.get_keyboard() {
slog_scope::info!("restoring focus to: {:?}", surface.as_ref());
keyboard.set_focus(state, surface.clone(), SERIAL_COUNTER.next_serial());
ActiveFocus::set(&seat, surface);
slog_scope::info!("restoring focus to: {:?}", target.as_ref());
keyboard.set_focus(state, target.clone(), SERIAL_COUNTER.next_serial());
ActiveFocus::set(&seat, target);
}
}
}
state.common.shell.update_active(state.common.seats.iter())
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.shell.update_active(seats.iter())
}
}

View file

@ -1,5 +1,8 @@
use crate::utils::prelude::*;
pub use smithay::{
use std::sync::Weak;
use crate::{shell::element::CosmicMapped, utils::prelude::*};
use id_tree::NodeId;
use smithay::{
backend::input::KeyState,
desktop::{LayerSurface, PopupKind, Window},
input::{
@ -7,83 +10,131 @@ pub use smithay::{
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget},
Seat,
},
output::WeakOutput,
reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource},
utils::{IsAlive, Serial},
wayland::seat::WaylandFocus,
};
#[derive(Debug, Clone, PartialEq)]
pub enum FocusTarget {
Window(Window),
pub enum PointerFocusTarget {
Element(CosmicMapped),
Fullscreen(Window),
LayerSurface(LayerSurface),
Popup(PopupKind),
}
impl IsAlive for FocusTarget {
fn alive(&self) -> bool {
match self {
FocusTarget::Window(w) => w.alive(),
FocusTarget::LayerSurface(l) => l.alive(),
FocusTarget::Popup(p) => p.alive(),
#[derive(Debug, Clone, PartialEq)]
pub enum KeyboardFocusTarget {
Element(CosmicMapped),
Fullscreen(Window),
Group(WindowGroup),
LayerSurface(LayerSurface),
Popup(PopupKind),
}
impl From<KeyboardFocusTarget> for PointerFocusTarget {
fn from(target: KeyboardFocusTarget) -> Self {
match target {
KeyboardFocusTarget::Element(elem) => PointerFocusTarget::Element(elem),
KeyboardFocusTarget::Fullscreen(elem) => PointerFocusTarget::Fullscreen(elem),
KeyboardFocusTarget::LayerSurface(layer) => PointerFocusTarget::LayerSurface(layer),
KeyboardFocusTarget::Popup(popup) => PointerFocusTarget::Popup(popup),
_ => unreachable!("A window grab cannot start a popup grab"),
}
}
}
impl PointerTarget<State> for FocusTarget {
#[derive(Debug, Clone)]
pub struct WindowGroup {
pub(in crate::shell) node: NodeId,
pub(in crate::shell) output: WeakOutput,
pub(in crate::shell) alive: Weak<()>,
}
impl PartialEq for WindowGroup {
fn eq(&self, other: &Self) -> bool {
self.node == other.node
&& self.output == other.output
&& Weak::ptr_eq(&self.alive, &other.alive)
}
}
impl IsAlive for PointerFocusTarget {
fn alive(&self) -> bool {
match self {
PointerFocusTarget::Element(e) => e.alive(),
PointerFocusTarget::Fullscreen(f) => f.alive(),
PointerFocusTarget::LayerSurface(l) => l.alive(),
PointerFocusTarget::Popup(p) => p.alive(),
}
}
}
impl IsAlive for KeyboardFocusTarget {
fn alive(&self) -> bool {
match self {
KeyboardFocusTarget::Element(e) => e.alive(),
KeyboardFocusTarget::Fullscreen(f) => f.alive(),
KeyboardFocusTarget::Group(g) => g.alive.upgrade().is_some(),
KeyboardFocusTarget::LayerSurface(l) => l.alive(),
KeyboardFocusTarget::Popup(p) => p.alive(),
}
}
}
impl PointerTarget<State> for PointerFocusTarget {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
match self {
FocusTarget::Window(w) => {
PointerTarget::enter(w.toplevel().wl_surface(), seat, data, event)
}
FocusTarget::LayerSurface(l) => PointerTarget::enter(l.wl_surface(), seat, data, event),
FocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event),
PointerFocusTarget::Element(w) => PointerTarget::enter(w, seat, data, event),
PointerFocusTarget::Fullscreen(w) => PointerTarget::enter(w, seat, data, event),
PointerFocusTarget::LayerSurface(l) => PointerTarget::enter(l, seat, data, event),
PointerFocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event),
}
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
match self {
FocusTarget::Window(w) => {
PointerTarget::motion(w.toplevel().wl_surface(), seat, data, event)
PointerFocusTarget::Element(w) => PointerTarget::motion(w, seat, data, event),
PointerFocusTarget::Fullscreen(w) => PointerTarget::motion(w, seat, data, event),
PointerFocusTarget::LayerSurface(l) => PointerTarget::motion(l, seat, data, event),
PointerFocusTarget::Popup(p) => {
PointerTarget::motion(p.wl_surface(), seat, data, event)
}
FocusTarget::LayerSurface(l) => {
PointerTarget::motion(l.wl_surface(), seat, data, event)
}
FocusTarget::Popup(p) => PointerTarget::motion(p.wl_surface(), seat, data, event),
}
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
match self {
FocusTarget::Window(w) => {
PointerTarget::button(w.toplevel().wl_surface(), seat, data, event)
PointerFocusTarget::Element(w) => PointerTarget::button(w, seat, data, event),
PointerFocusTarget::Fullscreen(w) => PointerTarget::button(w, seat, data, event),
PointerFocusTarget::LayerSurface(l) => PointerTarget::button(l, seat, data, event),
PointerFocusTarget::Popup(p) => {
PointerTarget::button(p.wl_surface(), seat, data, event)
}
FocusTarget::LayerSurface(l) => {
PointerTarget::button(l.wl_surface(), seat, data, event)
}
FocusTarget::Popup(p) => PointerTarget::button(p.wl_surface(), seat, data, event),
}
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
match self {
FocusTarget::Window(w) => {
PointerTarget::axis(w.toplevel().wl_surface(), seat, data, frame)
}
FocusTarget::LayerSurface(l) => PointerTarget::axis(l.wl_surface(), seat, data, frame),
FocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame),
PointerFocusTarget::Element(w) => PointerTarget::axis(w, seat, data, frame),
PointerFocusTarget::Fullscreen(w) => PointerTarget::axis(w, seat, data, frame),
PointerFocusTarget::LayerSurface(l) => PointerTarget::axis(l, seat, data, frame),
PointerFocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame),
}
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
match self {
FocusTarget::Window(w) => {
PointerTarget::leave(w.toplevel().wl_surface(), seat, data, serial, time)
PointerFocusTarget::Element(w) => PointerTarget::leave(w, seat, data, serial, time),
PointerFocusTarget::Fullscreen(w) => PointerTarget::leave(w, seat, data, serial, time),
PointerFocusTarget::LayerSurface(l) => {
PointerTarget::leave(l, seat, data, serial, time)
}
FocusTarget::LayerSurface(l) => {
PointerTarget::leave(l.wl_surface(), seat, data, serial, time)
PointerFocusTarget::Popup(p) => {
PointerTarget::leave(p.wl_surface(), seat, data, serial, time)
}
FocusTarget::Popup(p) => PointerTarget::leave(p.wl_surface(), seat, data, serial, time),
}
}
}
impl KeyboardTarget<State> for FocusTarget {
impl KeyboardTarget<State> for KeyboardFocusTarget {
fn enter(
&self,
seat: &Seat<State>,
@ -92,26 +143,28 @@ impl KeyboardTarget<State> for FocusTarget {
serial: Serial,
) {
match self {
FocusTarget::Window(w) => {
KeyboardTarget::enter(w.toplevel().wl_surface(), seat, data, keys, serial)
KeyboardFocusTarget::Element(w) => KeyboardTarget::enter(w, seat, data, keys, serial),
KeyboardFocusTarget::Fullscreen(w) => {
KeyboardTarget::enter(w, seat, data, keys, serial)
}
FocusTarget::LayerSurface(l) => {
KeyboardTarget::enter(l.wl_surface(), seat, data, keys, serial)
KeyboardFocusTarget::Group(_) => {}
KeyboardFocusTarget::LayerSurface(l) => {
KeyboardTarget::enter(l, seat, data, keys, serial)
}
FocusTarget::Popup(p) => {
KeyboardFocusTarget::Popup(p) => {
KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial)
}
}
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial) {
match self {
FocusTarget::Window(w) => {
KeyboardTarget::leave(w.toplevel().wl_surface(), seat, data, serial)
KeyboardFocusTarget::Element(w) => KeyboardTarget::leave(w, seat, data, serial),
KeyboardFocusTarget::Fullscreen(w) => KeyboardTarget::leave(w, seat, data, serial),
KeyboardFocusTarget::Group(_) => {}
KeyboardFocusTarget::LayerSurface(l) => KeyboardTarget::leave(l, seat, data, serial),
KeyboardFocusTarget::Popup(p) => {
KeyboardTarget::leave(p.wl_surface(), seat, data, serial)
}
FocusTarget::LayerSurface(l) => {
KeyboardTarget::leave(l.wl_surface(), seat, data, serial)
}
FocusTarget::Popup(p) => KeyboardTarget::leave(p.wl_surface(), seat, data, serial),
}
}
fn key(
@ -124,19 +177,17 @@ impl KeyboardTarget<State> for FocusTarget {
time: u32,
) {
match self {
FocusTarget::Window(w) => KeyboardTarget::key(
w.toplevel().wl_surface(),
seat,
data,
key,
state,
serial,
time,
),
FocusTarget::LayerSurface(l) => {
KeyboardTarget::key(l.wl_surface(), seat, data, key, state, serial, time)
KeyboardFocusTarget::Element(w) => {
KeyboardTarget::key(w, seat, data, key, state, serial, time)
}
FocusTarget::Popup(p) => {
KeyboardFocusTarget::Fullscreen(w) => {
KeyboardTarget::key(w, seat, data, key, state, serial, time)
}
KeyboardFocusTarget::Group(_) => {}
KeyboardFocusTarget::LayerSurface(l) => {
KeyboardTarget::key(l, seat, data, key, state, serial, time)
}
KeyboardFocusTarget::Popup(p) => {
KeyboardTarget::key(p.wl_surface(), seat, data, key, state, serial, time)
}
}
@ -149,50 +200,113 @@ impl KeyboardTarget<State> for FocusTarget {
serial: Serial,
) {
match self {
FocusTarget::Window(w) => {
KeyboardTarget::modifiers(w.toplevel().wl_surface(), seat, data, modifiers, serial)
KeyboardFocusTarget::Element(w) => {
KeyboardTarget::modifiers(w, seat, data, modifiers, serial)
}
FocusTarget::LayerSurface(l) => {
KeyboardTarget::modifiers(l.wl_surface(), seat, data, modifiers, serial)
KeyboardFocusTarget::Fullscreen(w) => {
KeyboardTarget::modifiers(w, seat, data, modifiers, serial)
}
FocusTarget::Popup(p) => {
KeyboardFocusTarget::Group(_) => {}
KeyboardFocusTarget::LayerSurface(l) => {
KeyboardTarget::modifiers(l, seat, data, modifiers, serial)
}
KeyboardFocusTarget::Popup(p) => {
KeyboardTarget::modifiers(p.wl_surface(), seat, data, modifiers, serial)
}
}
}
}
impl WaylandFocus for FocusTarget {
fn wl_surface(&self) -> Option<&WlSurface> {
Some(match self {
FocusTarget::Window(w) => w.toplevel().wl_surface(),
FocusTarget::LayerSurface(l) => l.wl_surface(),
FocusTarget::Popup(p) => p.wl_surface(),
})
impl WaylandFocus for KeyboardFocusTarget {
fn wl_surface(&self) -> Option<WlSurface> {
match self {
KeyboardFocusTarget::Element(w) => WaylandFocus::wl_surface(w),
KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w),
KeyboardFocusTarget::Group(_) => None,
KeyboardFocusTarget::LayerSurface(l) => Some(l.wl_surface().clone()),
KeyboardFocusTarget::Popup(p) => Some(p.wl_surface().clone()),
}
}
fn same_client_as(&self, object_id: &ObjectId) -> bool {
match self {
FocusTarget::Window(w) => w.toplevel().wl_surface().id().same_client_as(object_id),
FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
KeyboardFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id),
KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id),
KeyboardFocusTarget::Group(_) => false,
KeyboardFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
KeyboardFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
}
}
}
impl From<Window> for FocusTarget {
impl WaylandFocus for PointerFocusTarget {
fn wl_surface(&self) -> Option<WlSurface> {
Some(match self {
PointerFocusTarget::Element(w) => WaylandFocus::wl_surface(w)?,
PointerFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w)?,
PointerFocusTarget::LayerSurface(l) => l.wl_surface().clone(),
PointerFocusTarget::Popup(p) => p.wl_surface().clone(),
})
}
fn same_client_as(&self, object_id: &ObjectId) -> bool {
match self {
PointerFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id),
PointerFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id),
PointerFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
PointerFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
}
}
}
impl From<CosmicMapped> for PointerFocusTarget {
fn from(w: CosmicMapped) -> Self {
PointerFocusTarget::Element(w)
}
}
impl From<Window> for PointerFocusTarget {
fn from(w: Window) -> Self {
FocusTarget::Window(w)
PointerFocusTarget::Fullscreen(w)
}
}
impl From<LayerSurface> for FocusTarget {
impl From<LayerSurface> for PointerFocusTarget {
fn from(l: LayerSurface) -> Self {
FocusTarget::LayerSurface(l)
PointerFocusTarget::LayerSurface(l)
}
}
impl From<PopupKind> for FocusTarget {
impl From<PopupKind> for PointerFocusTarget {
fn from(p: PopupKind) -> Self {
FocusTarget::Popup(p)
PointerFocusTarget::Popup(p)
}
}
impl From<CosmicMapped> for KeyboardFocusTarget {
fn from(w: CosmicMapped) -> Self {
KeyboardFocusTarget::Element(w)
}
}
impl From<Window> for KeyboardFocusTarget {
fn from(w: Window) -> Self {
KeyboardFocusTarget::Fullscreen(w)
}
}
impl From<WindowGroup> for KeyboardFocusTarget {
fn from(w: WindowGroup) -> Self {
KeyboardFocusTarget::Group(w)
}
}
impl From<LayerSurface> for KeyboardFocusTarget {
fn from(l: LayerSurface) -> Self {
KeyboardFocusTarget::LayerSurface(l)
}
}
impl From<PopupKind> for KeyboardFocusTarget {
fn from(p: PopupKind) -> Self {
KeyboardFocusTarget::Popup(p)
}
}

View file

@ -1,367 +1,107 @@
// SPDX-License-Identifier: GPL-3.0-only
use super::Shell;
use crate::utils::prelude::*;
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState;
use smithay::{
backend::renderer::{ImportAll, Renderer},
desktop::{
draw_window,
space::{RenderElement, SpaceOutputTuple},
Kind, Window,
input::pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle,
},
input::{
pointer::{
AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent,
PointerGrab, PointerInnerHandle,
},
Seat,
},
output::Output,
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
wayland_server::protocol::wl_surface::WlSurface,
},
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial},
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
utils::{Logical, Point},
};
use std::cell::RefCell;
impl Shell {
pub fn move_request(
state: &mut State,
window: &Window,
seat: &Seat<State>,
serial: Serial,
start_data: PointerGrabStartData<State>,
) {
// TODO touch grab
if let Some(pointer) = seat.get_pointer() {
let workspace = state
.common
.shell
.space_for_window_mut(window.toplevel().wl_surface())
.unwrap();
if workspace.fullscreen.values().any(|w| w == window) {
return;
}
use crate::state::State;
let pos = pointer.current_location();
let output = match workspace
.space
.outputs_for_window(&window)
.into_iter()
.find(|o| o.geometry().contains(pos.to_i32_round())) {
Some(o) => o,
None => return,
};
let mut initial_window_location = workspace.space.window_location(&window).unwrap();
use super::{
focus::target::PointerFocusTarget,
layout::{floating::ResizeSurfaceGrab, tiling::ResizeForkGrab},
};
match &window.toplevel() {
Kind::Xdg(surface) => {
// If surface is maximized then unmaximize it
let current_state = surface.current_state();
if current_state.states.contains(XdgState::Maximized) {
workspace
.floating_layer
.unmaximize_request(&mut workspace.space, window);
let new_size = surface.with_pending_state(|state| state.size);
let ratio = pos.x / output.geometry().size.w as f64;
bitflags::bitflags! {
pub struct ResizeEdge: u32 {
const TOP = 0b0001;
const BOTTOM = 0b0010;
const LEFT = 0b0100;
const RIGHT = 0b1000;
initial_window_location = new_size
.map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into())
.unwrap_or_else(|| pos)
.to_i32_round();
}
}
};
const TOP_LEFT = Self::TOP.bits | Self::LEFT.bits;
const BOTTOM_LEFT = Self::BOTTOM.bits | Self::LEFT.bits;
let was_tiled = if workspace.tiling_layer.windows.contains(&window) {
workspace
.tiling_layer
.unmap_window(&mut workspace.space, &window);
true
} else {
workspace
.floating_layer
.unmap_window(&mut workspace.space, &window);
false
};
let workspace_handle = workspace.handle;
let workspace_is_empty = workspace.space.windows().next().is_none();
if workspace_is_empty {
state
.common
.shell
.workspace_state
.update()
.add_workspace_state(&workspace_handle, WState::Hidden);
}
state
.common
.shell
.toplevel_info_state
.toplevel_leave_workspace(&window, &workspace_handle);
state
.common
.shell
.toplevel_info_state
.toplevel_leave_output(&window, &output);
let grab_state = MoveGrabState {
window: window.clone(),
was_tiled,
initial_cursor_location: pointer.current_location(),
initial_window_location,
initial_output_location: output.geometry().loc,
};
let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat);
*seat
.user_data()
.get::<SeatMoveGrabState>()
.unwrap()
.borrow_mut() = Some(grab_state);
pointer.set_grab(state, grab, serial, Focus::Clear);
}
}
fn drop_move(state: &mut State, seat: &Seat<State>, output: &Output) {
if let Some(move_state) = seat
.user_data()
.get::<SeatMoveGrabState>()
.unwrap()
.borrow_mut()
.take()
{
let pointer = seat.get_pointer().unwrap();
let window = move_state.window;
if window.alive() {
let delta = pointer.current_location() - move_state.initial_cursor_location;
let window_location = (move_state.initial_window_location.to_f64()
+ move_state.initial_output_location.to_f64()
- output.geometry().loc.to_f64()
+ delta)
.to_i32_round();
let surface = window.toplevel().wl_surface().clone();
let workspace_handle = state.common.shell.active_space(output).handle;
state
.common
.shell
.workspace_state
.update()
.remove_workspace_state(&workspace_handle, WState::Hidden);
state
.common
.shell
.toplevel_info_state
.toplevel_enter_workspace(&window, &workspace_handle);
state
.common
.shell
.toplevel_info_state
.toplevel_enter_output(&window, &output);
let workspace = state.common.shell.active_space_mut(output);
if move_state.was_tiled {
let focus_stack = workspace.focus_stack(&seat);
workspace.tiling_layer.map_window(
&mut workspace.space,
window,
&seat,
focus_stack.iter(),
);
} else {
workspace.floating_layer.map_window(
&mut workspace.space,
window,
&seat,
window_location,
);
}
Shell::set_focus(state, Some(&surface), &seat, None);
for window in state.common.shell.active_space(output).space.windows() {
state.common.shell.update_reactive_popups(window);
}
}
}
const TOP_RIGHT = Self::TOP.bits | Self::RIGHT.bits;
const BOTTOM_RIGHT = Self::BOTTOM.bits | Self::RIGHT.bits;
}
}
pub type SeatMoveGrabState = RefCell<Option<MoveGrabState>>;
pub struct MoveGrabState {
window: Window,
was_tiled: bool,
initial_cursor_location: Point<f64, Logical>,
initial_window_location: Point<i32, Logical>,
initial_output_location: Point<i32, Logical>,
}
pub struct MoveGrabRenderElement {
seat_id: usize,
window: Window,
window_location: Point<f64, Logical>,
}
impl<R> RenderElement<R> for MoveGrabRenderElement
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: 'static,
{
fn id(&self) -> usize {
self.seat_id
}
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
(self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale)
}
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
let scale = scale.into();
self.window
.physical_bbox_with_popups(RenderElement::<R>::location(self, scale), scale)
}
fn accumulated_damage(
&self,
scale: impl Into<Scale<f64>>,
for_values: Option<SpaceOutputTuple<'_, '_>>,
) -> Vec<Rectangle<i32, Physical>> {
let scale = scale.into();
self.window.accumulated_damage(
RenderElement::<R>::location(self, scale),
scale,
for_values.map(|t| (t.0, t.1)),
)
}
fn opaque_regions(
&self,
scale: impl Into<Scale<f64>>,
) -> Option<Vec<Rectangle<i32, Physical>>> {
let scale = scale.into();
self.window
.opaque_regions(RenderElement::<R>::location(self, scale), scale)
}
fn draw(
&self,
renderer: &mut R,
frame: &mut <R as Renderer>::Frame,
scale: impl Into<Scale<f64>>,
position: Point<f64, Physical>,
damage: &[Rectangle<i32, Physical>],
log: &slog::Logger,
) -> Result<(), <R as Renderer>::Error> {
draw_window(renderer, frame, &self.window, scale, position, damage, log)
impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
#[inline]
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
Self::from_bits(x.into()).unwrap()
}
}
impl MoveGrabState {
pub fn render<I>(&self, seat: &Seat<State>, output: &Output) -> Option<I>
where
I: From<MoveGrabRenderElement>,
{
let cursor_at = seat.get_pointer().unwrap().current_location();
let delta = cursor_at - self.initial_cursor_location;
let location =
self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta;
let mut window_geo = self.window.bbox();
window_geo.loc += location.to_i32_round();
if !output.geometry().intersection(window_geo).is_some() {
return None;
}
Some(I::from(MoveGrabRenderElement {
seat_id: seat.id(),
window: self.window.clone(),
window_location: location - output.geometry().loc.to_f64(),
}))
impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
#[inline]
fn from(x: ResizeEdge) -> Self {
Self::try_from(x.bits()).unwrap()
}
}
pub struct MoveSurfaceGrab {
window: Window,
start_data: PointerGrabStartData<State>,
seat: Seat<State>,
pub enum ResizeGrab {
Floating(ResizeSurfaceGrab),
Tiling(ResizeForkGrab),
}
impl PointerGrab<State> for MoveSurfaceGrab {
impl From<ResizeSurfaceGrab> for ResizeGrab {
fn from(grab: ResizeSurfaceGrab) -> Self {
ResizeGrab::Floating(grab)
}
}
impl From<ResizeForkGrab> for ResizeGrab {
fn from(grab: ResizeForkGrab) -> Self {
ResizeGrab::Tiling(grab)
}
}
impl PointerGrab<State> for ResizeGrab {
fn motion(
&mut self,
state: &mut State,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(WlSurface, Point<i32, Logical>)>,
focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
event: &MotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(state, None, event);
if !self.window.alive() {
self.ungrab(state, handle, event.serial, event.time);
match self {
ResizeGrab::Floating(grab) => grab.motion(data, handle, focus, event),
ResizeGrab::Tiling(grab) => grab.motion(data, handle, focus, event),
}
}
fn button(
&mut self,
state: &mut State,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
) {
handle.button(state, event);
if handle.current_pressed().is_empty() {
self.ungrab(state, handle, event.serial, event.time);
match self {
ResizeGrab::Floating(grab) => grab.button(data, handle, event),
ResizeGrab::Tiling(grab) => grab.button(data, handle, event),
}
}
fn axis(
&mut self,
state: &mut State,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
) {
handle.axis(state, details);
}
fn start_data(&self) -> &PointerGrabStartData<State> {
&self.start_data
}
}
impl MoveSurfaceGrab {
pub fn new(
start_data: PointerGrabStartData<State>,
window: Window,
seat: &Seat<State>,
) -> MoveSurfaceGrab {
MoveSurfaceGrab {
window,
start_data,
seat: seat.clone(),
match self {
ResizeGrab::Floating(grab) => grab.axis(data, handle, details),
ResizeGrab::Tiling(grab) => grab.axis(data, handle, details),
}
}
fn ungrab(
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
serial: Serial,
time: u32,
) {
// No more buttons are pressed, release the grab.
let output = active_output(&self.seat, &state.common);
let seat = self.seat.clone();
state.common.event_loop_handle.insert_idle(move |data| {
Shell::drop_move(&mut data.state, &seat, &output);
});
handle.unset_grab(state, serial, time);
fn start_data(&self) -> &PointerGrabStartData<State> {
match self {
ResizeGrab::Floating(grab) => grab.start_data(),
ResizeGrab::Tiling(grab) => grab.start_data(),
}
}
}

View file

@ -1,346 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::utils::prelude::*;
use smithay::{
desktop::{Kind, Window},
input::pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle,
},
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel,
wayland_server::protocol::wl_surface::WlSurface,
},
utils::{IsAlive, Logical, Point, Serial, Size},
wayland::{
compositor::with_states,
shell::xdg::{SurfaceCachedState, ToplevelConfigure, XdgToplevelSurfaceRoleAttributes},
},
};
use std::{cell::RefCell, convert::TryFrom, sync::Mutex};
bitflags::bitflags! {
struct ResizeEdge: u32 {
const NONE = 0;
const TOP = 1;
const BOTTOM = 2;
const LEFT = 4;
const TOP_LEFT = 5;
const BOTTOM_LEFT = 6;
const RIGHT = 8;
const TOP_RIGHT = 9;
const BOTTOM_RIGHT = 10;
}
}
impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
#[inline]
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
Self::from_bits(x.into()).unwrap()
}
}
impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
#[inline]
fn from(x: ResizeEdge) -> Self {
Self::try_from(x.bits()).unwrap()
}
}
/// Information about the resize operation.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
struct ResizeData {
/// The edges the surface is being resized with.
edges: ResizeEdge,
/// The initial window location.
initial_window_location: Point<i32, Logical>,
/// The initial window size (geometry width and height).
initial_window_size: Size<i32, Logical>,
}
/// State of the resize operation.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum ResizeState {
/// The surface is not being resized.
NotResizing,
/// The surface is currently being resized.
Resizing(ResizeData),
/// The resize has finished, and the surface needs to ack the final configure.
WaitingForFinalAck(ResizeData, Serial),
/// The resize has finished, and the surface needs to commit its final state.
WaitingForCommit(ResizeData),
}
impl Default for ResizeState {
fn default() -> Self {
ResizeState::NotResizing
}
}
pub struct ResizeSurfaceGrab {
start_data: PointerGrabStartData<State>,
window: Window,
edges: ResizeEdge,
initial_window_size: Size<i32, Logical>,
last_window_size: Size<i32, Logical>,
}
impl PointerGrab<State> for ResizeSurfaceGrab {
fn motion(
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(WlSurface, Point<i32, Logical>)>,
event: &MotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
// It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early.
if !self.window.alive() {
handle.unset_grab(data, event.serial, event.time);
return;
}
let (mut dx, mut dy) = (event.location - self.start_data.location).into();
let mut new_window_width = self.initial_window_size.w;
let mut new_window_height = self.initial_window_size.h;
let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT;
let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM;
if self.edges.intersects(left_right) {
if self.edges.intersects(ResizeEdge::LEFT) {
dx = -dx;
}
new_window_width = (self.initial_window_size.w as f64 + dx) as i32;
}
if self.edges.intersects(top_bottom) {
if self.edges.intersects(ResizeEdge::TOP) {
dy = -dy;
}
new_window_height = (self.initial_window_size.h as f64 + dy) as i32;
}
let (min_size, max_size) = with_states(self.window.toplevel().wl_surface(), |states| {
let data = states.cached_state.current::<SurfaceCachedState>();
(data.min_size, data.max_size)
});
let min_width = min_size.w.max(1);
let min_height = min_size.h.max(1);
let max_width = if max_size.w == 0 {
i32::max_value()
} else {
max_size.w
};
let max_height = if max_size.h == 0 {
i32::max_value()
} else {
max_size.h
};
new_window_width = new_window_width.max(min_width).min(max_width);
new_window_height = new_window_height.max(min_height).min(max_height);
self.last_window_size = (new_window_width, new_window_height).into();
match &self.window.toplevel() {
Kind::Xdg(xdg) => {
xdg.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Resizing);
state.size = Some(self.last_window_size);
});
xdg.send_configure();
}
}
}
fn button(
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
) {
handle.button(data, event);
if handle.current_pressed().is_empty() {
// No more buttons are pressed, release the grab.
handle.unset_grab(data, event.serial, event.time);
// If toplevel is dead, we can't resize it, so we return early.
if !self.window.alive() {
return;
}
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &self.window.toplevel() {
xdg.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Resizing);
state.size = Some(self.last_window_size);
});
xdg.send_configure();
}
let mut resize_state = self
.window
.user_data()
.get::<RefCell<ResizeState>>()
.unwrap()
.borrow_mut();
if let ResizeState::Resizing(resize_data) = *resize_state {
*resize_state = ResizeState::WaitingForFinalAck(resize_data, event.serial);
} else {
panic!("invalid resize state: {:?}", resize_state);
}
}
}
fn axis(
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
) {
handle.axis(data, details)
}
fn start_data(&self) -> &PointerGrabStartData<State> {
&self.start_data
}
}
impl ResizeSurfaceGrab {
pub fn new(
start_data: PointerGrabStartData<State>,
window: Window,
edges: xdg_toplevel::ResizeEdge,
initial_window_location: Point<i32, Logical>,
initial_window_size: Size<i32, Logical>,
) -> ResizeSurfaceGrab {
let resize_state = ResizeState::Resizing(ResizeData {
edges: edges.into(),
initial_window_location,
initial_window_size,
});
window
.user_data()
.insert_if_missing(|| RefCell::new(ResizeState::default()));
*window
.user_data()
.get::<RefCell<ResizeState>>()
.unwrap()
.borrow_mut() = resize_state;
ResizeSurfaceGrab {
start_data,
window,
edges: edges.into(),
initial_window_size,
last_window_size: initial_window_size,
}
}
pub fn ack_configure(window: &Window, configure: ToplevelConfigure) {
let surface = window.toplevel().wl_surface();
let waiting_for_serial =
if let Some(data) = window.user_data().get::<RefCell<ResizeState>>() {
if let ResizeState::WaitingForFinalAck(_, serial) = *data.borrow() {
Some(serial)
} else {
None
}
} else {
None
};
if let Some(serial) = waiting_for_serial {
// When the resize grab is released the surface
// resize state will be set to WaitingForFinalAck
// and the client will receive a configure request
// without the resize state to inform the client
// resizing has finished. Here we will wait for
// the client to acknowledge the end of the
// resizing. To check if the surface was resizing
// before sending the configure we need to use
// the current state as the received acknowledge
// will no longer have the resize state set
let is_resizing = with_states(&surface, |states| {
states
.data_map
.get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.current
.states
.contains(xdg_toplevel::State::Resizing)
});
if configure.serial >= serial && is_resizing {
let mut resize_state = window
.user_data()
.get::<RefCell<ResizeState>>()
.unwrap()
.borrow_mut();
if let ResizeState::WaitingForFinalAck(resize_data, _) = *resize_state {
*resize_state = ResizeState::WaitingForCommit(resize_data);
} else {
unreachable!()
}
}
}
}
pub fn apply_resize_state(
window: &Window,
mut location: Point<i32, Logical>,
size: Size<i32, Logical>,
) -> Option<Point<i32, Logical>> {
let mut new_location = None;
if let Some(resize_state) = window.user_data().get::<RefCell<ResizeState>>() {
let mut resize_state = resize_state.borrow_mut();
// If the window is being resized by top or left, its location must be adjusted
// accordingly.
match *resize_state {
ResizeState::Resizing(resize_data)
| ResizeState::WaitingForFinalAck(resize_data, _)
| ResizeState::WaitingForCommit(resize_data) => {
let ResizeData {
edges,
initial_window_location,
initial_window_size,
} = resize_data;
if edges.intersects(ResizeEdge::TOP_LEFT) {
if edges.intersects(ResizeEdge::LEFT) {
location.x =
initial_window_location.x + (initial_window_size.w - size.w);
}
if edges.intersects(ResizeEdge::TOP) {
location.y =
initial_window_location.y + (initial_window_size.h - size.h);
}
new_location = Some(location);
}
}
ResizeState::NotResizing => (),
}
// Finish resizing.
if let ResizeState::WaitingForCommit(_) = *resize_state {
*resize_state = ResizeState::NotResizing;
}
}
new_location
}
}

View file

@ -0,0 +1,5 @@
mod moving;
mod resize;
pub use self::moving::*;
pub use self::resize::*;

View file

@ -0,0 +1,205 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
backend::render::element::{AsGles2Frame, AsGlowRenderer},
shell::{
element::{CosmicMapped, CosmicMappedRenderElement},
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
},
utils::prelude::*,
};
use smithay::{
backend::renderer::{
element::{AsRenderElements, RenderElement},
ImportAll, Renderer,
},
desktop::space::SpaceElement,
input::{
pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent,
PointerGrab, PointerInnerHandle,
},
Seat,
},
output::Output,
utils::{IsAlive, Logical, Point, Serial},
};
use std::cell::RefCell;
pub type SeatMoveGrabState = RefCell<Option<MoveGrabState>>;
pub struct MoveGrabState {
window: CosmicMapped,
initial_cursor_location: Point<f64, Logical>,
initial_window_location: Point<i32, Logical>,
}
impl MoveGrabState {
pub fn render<I, R>(&self, seat: &Seat<State>, output: &Output) -> Vec<I>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
I: From<CosmicMappedRenderElement<R>>,
{
let cursor_at = seat.get_pointer().unwrap().current_location();
let delta = cursor_at - self.initial_cursor_location;
let location = self.initial_window_location.to_f64() + delta;
let mut window_geo = self.window.geometry();
window_geo.loc += location.to_i32_round();
if !output.geometry().intersection(window_geo).is_some() {
return Vec::new();
}
let scale = output.current_scale().fractional_scale().into();
AsRenderElements::<R>::render_elements::<I>(
&self.window,
(location.to_i32_round() - output.geometry().loc - self.window.geometry().loc)
.to_physical_precise_round(scale),
scale,
)
}
}
pub struct MoveSurfaceGrab {
window: CosmicMapped,
start_data: PointerGrabStartData<State>,
seat: Seat<State>,
}
impl PointerGrab<State> for MoveSurfaceGrab {
fn motion(
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
event: &MotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(state, None, event);
if !self.window.alive() {
self.ungrab(state, handle, event.serial, event.time);
}
}
fn button(
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
) {
handle.button(state, event);
if handle.current_pressed().is_empty() {
self.ungrab(state, handle, event.serial, event.time);
}
}
fn axis(
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
) {
handle.axis(state, details);
}
fn start_data(&self) -> &PointerGrabStartData<State> {
&self.start_data
}
}
impl MoveSurfaceGrab {
pub fn new(
start_data: PointerGrabStartData<State>,
window: CosmicMapped,
seat: &Seat<State>,
initial_cursor_location: Point<f64, Logical>,
initial_window_location: Point<i32, Logical>,
) -> MoveSurfaceGrab {
let grab_state = MoveGrabState {
window: window.clone(),
initial_cursor_location,
initial_window_location,
};
*seat
.user_data()
.get::<SeatMoveGrabState>()
.unwrap()
.borrow_mut() = Some(grab_state);
MoveSurfaceGrab {
window,
start_data,
seat: seat.clone(),
}
}
fn ungrab(
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
serial: Serial,
time: u32,
) {
// No more buttons are pressed, release the grab.
let output = self.seat.active_output();
if let Some(grab_state) = self
.seat
.user_data()
.get::<SeatMoveGrabState>()
.and_then(|s| s.borrow_mut().take())
{
if grab_state.window.alive() {
let delta = handle.current_location() - grab_state.initial_cursor_location;
let window_location = (grab_state.initial_window_location.to_f64() + delta)
.to_i32_round()
- output.geometry().loc;
let workspace_handle = state.common.shell.active_space(&output).handle;
for (window, _) in grab_state.window.windows() {
state
.common
.shell
.toplevel_info_state
.toplevel_enter_workspace(&window, &workspace_handle);
state
.common
.shell
.toplevel_info_state
.toplevel_enter_output(&window, &output);
}
let offset = state
.common
.shell
.active_space(&output)
.floating_layer
.space
.output_geometry(&output)
.unwrap()
.loc;
state
.common
.shell
.active_space_mut(&output)
.floating_layer
.map_internal(grab_state.window, &output, Some(window_location + offset));
}
}
handle.unset_grab(state, serial, time);
if self.window.alive() {
Common::set_focus(
state,
Some(&KeyboardFocusTarget::from(self.window.clone())),
&self.seat,
Some(serial),
)
}
}
}

View file

@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
shell::{element::CosmicMapped, focus::target::PointerFocusTarget, grabs::ResizeEdge},
utils::prelude::*,
};
use smithay::{
desktop::space::SpaceElement,
input::pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle,
},
utils::{IsAlive, Logical, Point, Size},
};
/// Information about the resize operation.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct ResizeData {
/// The edges the surface is being resized with.
edges: ResizeEdge,
/// The initial window location.
initial_window_location: Point<i32, Logical>,
/// The initial window size (geometry width and height).
initial_window_size: Size<i32, Logical>,
}
/// State of the resize operation.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ResizeState {
/// The surface is currently being resized.
Resizing(ResizeData),
/// The resize has finished, and the surface needs to commit its final state.
WaitingForCommit(ResizeData),
}
pub struct ResizeSurfaceGrab {
start_data: PointerGrabStartData<State>,
window: CosmicMapped,
edges: ResizeEdge,
initial_window_size: Size<i32, Logical>,
last_window_size: Size<i32, Logical>,
}
impl PointerGrab<State> for ResizeSurfaceGrab {
fn motion(
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
event: &MotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
// It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early.
if !self.window.alive() {
handle.unset_grab(data, event.serial, event.time);
return;
}
let (mut dx, mut dy) = (event.location - self.start_data.location).into();
let mut new_window_width = self.initial_window_size.w;
let mut new_window_height = self.initial_window_size.h;
let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT;
let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM;
if self.edges.intersects(left_right) {
if self.edges.intersects(ResizeEdge::LEFT) {
dx = -dx;
}
new_window_width = (self.initial_window_size.w as f64 + dx) as i32;
}
if self.edges.intersects(top_bottom) {
if self.edges.intersects(ResizeEdge::TOP) {
dy = -dy;
}
new_window_height = (self.initial_window_size.h as f64 + dy) as i32;
}
let (min_size, max_size) = (self.window.min_size(), self.window.max_size());
let min_width = min_size.w.max(1);
let min_height = min_size.h.max(1);
let max_width = if max_size.w == 0 {
i32::max_value()
} else {
max_size.w
};
let max_height = if max_size.h == 0 {
i32::max_value()
} else {
max_size.h
};
new_window_width = new_window_width.max(min_width).min(max_width);
new_window_height = new_window_height.max(min_height).min(max_height);
self.last_window_size = (new_window_width, new_window_height).into();
self.window.set_resizing(true);
self.window.set_size(self.last_window_size);
self.window.configure();
}
fn button(
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
) {
handle.button(data, event);
if handle.current_pressed().is_empty() {
// No more buttons are pressed, release the grab.
handle.unset_grab(data, event.serial, event.time);
// If toplevel is dead, we can't resize it, so we return early.
if !self.window.alive() {
return;
}
self.window.set_resizing(false);
self.window.set_size(self.last_window_size);
self.window.configure();
let mut resize_state = self.window.resize_state.lock().unwrap();
if let Some(ResizeState::Resizing(resize_data)) = *resize_state {
*resize_state = Some(ResizeState::WaitingForCommit(resize_data));
} else {
panic!("invalid resize state: {:?}", resize_state);
}
}
}
fn axis(
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
) {
handle.axis(data, details)
}
fn start_data(&self) -> &PointerGrabStartData<State> {
&self.start_data
}
}
impl ResizeSurfaceGrab {
pub fn new(
start_data: PointerGrabStartData<State>,
mapped: CosmicMapped,
edges: ResizeEdge,
initial_window_location: Point<i32, Logical>,
initial_window_size: Size<i32, Logical>,
) -> ResizeSurfaceGrab {
let resize_state = ResizeState::Resizing(ResizeData {
edges,
initial_window_location,
initial_window_size,
});
*mapped.resize_state.lock().unwrap() = Some(resize_state);
ResizeSurfaceGrab {
start_data,
window: mapped,
edges,
initial_window_size,
last_window_size: initial_window_size,
}
}
pub fn apply_resize_to_location(window: CosmicMapped, space: &mut Workspace) {
if let Some(mut location) = space.floating_layer.space.element_location(&window) {
let mut new_location = None;
let mut resize_state = window.resize_state.lock().unwrap();
// If the window is being resized by top or left, its location must be adjusted
// accordingly.
match *resize_state {
Some(ResizeState::Resizing(resize_data))
| Some(ResizeState::WaitingForCommit(resize_data)) => {
let ResizeData {
edges,
initial_window_location,
initial_window_size,
} = resize_data;
if edges.intersects(ResizeEdge::TOP_LEFT) {
let size = window.geometry().size;
if edges.intersects(ResizeEdge::LEFT) {
location.x =
initial_window_location.x + (initial_window_size.w - size.w);
}
if edges.intersects(ResizeEdge::TOP) {
location.y =
initial_window_location.y + (initial_window_size.h - size.h);
}
new_location = Some(location);
}
}
_ => {}
};
// Finish resizing.
if let Some(ResizeState::WaitingForCommit(_)) = *resize_state {
if !window.is_resizing() {
*resize_state = None;
}
}
std::mem::drop(resize_state);
if let Some(new_location) = new_location {
for (window, offset) in window.windows() {
update_reactive_popups(
&window,
new_location + offset,
space.floating_layer.space.outputs(),
);
}
space
.floating_layer
.space
.map_element(window, new_location, false);
}
}
}
}

View file

@ -1,99 +1,87 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::{
desktop::{layer_map_for_output, space::RenderZindex, Kind, Space, Window},
input::{
pointer::{Focus, GrabStartData as PointerGrabStartData},
Seat,
},
backend::renderer::{element::RenderElement, ImportAll, Renderer},
desktop::{layer_map_for_output, space::SpaceElement, Space, Window},
input::{pointer::GrabStartData as PointerGrabStartData, Seat},
output::Output,
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{
ResizeEdge, State as XdgState,
},
utils::{IsAlive, Logical, Point, Rectangle, Serial},
wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes},
utils::{Logical, Point, Rectangle, Serial},
};
use std::{collections::HashSet, sync::Mutex};
use std::collections::HashMap;
use crate::state::State;
use crate::{
backend::render::element::{AsGles2Frame, AsGlowRenderer},
shell::{
element::{CosmicMapped, CosmicMappedRenderElement},
grabs::ResizeEdge,
OutputNotMapped,
},
state::State,
utils::prelude::*,
};
mod grabs;
pub use self::grabs::*;
pub const FLOATING_INDEX: u8 = RenderZindex::Shell as u8 + 1;
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct FloatingLayout {
pending_windows: Vec<Window>,
pub windows: HashSet<Window>,
pub(in crate::shell) space: Space<CosmicMapped>,
}
#[derive(Default)]
pub struct WindowUserDataInner {
last_geometry: Rectangle<i32, Logical>,
impl Default for FloatingLayout {
fn default() -> Self {
FloatingLayout {
space: Space::new(None),
}
}
}
pub type WindowUserData = Mutex<WindowUserDataInner>;
impl FloatingLayout {
pub fn new() -> FloatingLayout {
Default::default()
}
pub fn map_window(
pub fn map_output(&mut self, output: &Output, location: Point<i32, Logical>) {
self.space.map_output(output, location)
}
pub fn unmap_output(&mut self, output: &Output) {
self.space.unmap_output(output);
self.refresh();
}
pub fn map(
&mut self,
space: &mut Space,
window: Window,
mapped: impl Into<CosmicMapped>,
seat: &Seat<State>,
position: impl Into<Option<Point<i32, Logical>>>,
) {
if let Some(output) = super::output_from_seat(Some(seat), space) {
self.map_window_internal(space, window, &output, position.into());
} else {
self.pending_windows.push(window);
}
let mapped = mapped.into();
let output = seat.active_output();
let position = position.into();
self.map_internal(mapped, &output, position)
}
pub fn refresh(&mut self, space: &mut Space) {
self.pending_windows.retain(|w| w.toplevel().alive());
if let Some(output) = super::output_from_seat(None, space) {
for window in std::mem::take(&mut self.pending_windows).into_iter() {
self.map_window_internal(space, window, &output, None);
}
}
// TODO make sure all windows are still visible on any output or move them
}
fn map_window_internal(
pub(in crate::shell) fn map_internal(
&mut self,
space: &mut Space,
window: Window,
mapped: CosmicMapped,
output: &Output,
position: Option<Point<i32, Logical>>,
) {
let last_geometry = window
.user_data()
.get::<WindowUserData>()
.map(|u| u.lock().unwrap().last_geometry);
let mut win_geo = window.geometry();
let mut win_geo = mapped.geometry();
let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone();
let last_geometry = mapped.last_geometry.lock().unwrap().clone();
let mut geo_updated = false;
if let Some(size) = last_geometry.clone().map(|g| g.size) {
geo_updated = win_geo.size == size;
if let Some(size) = last_geometry.map(|g| g.size) {
geo_updated = win_geo.size != size;
win_geo.size = size;
}
{
let (min_size, max_size) = with_states(window.toplevel().wl_surface(), |states| {
let attrs = states
.data_map
.get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap();
(attrs.min_size, attrs.max_size)
});
let (min_size, max_size) = (mapped.min_size(), mapped.max_size());
if win_geo.size.w > geometry.size.w / 3 * 2 {
// try a more reasonable size
let mut width = geometry.size.w / 3 * 2;
@ -136,124 +124,188 @@ impl FloatingLayout {
.into()
});
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
xdg.with_pending_state(|state| {
state.states.unset(XdgState::TiledLeft);
state.states.unset(XdgState::TiledRight);
state.states.unset(XdgState::TiledTop);
state.states.unset(XdgState::TiledBottom);
if geo_updated {
state.size = Some(win_geo.size);
}
});
xdg.send_configure();
mapped.set_tiled(false);
if geo_updated {
mapped.set_size(win_geo.size);
}
mapped.configure();
space.map_window(&window, position, FLOATING_INDEX, false);
self.windows.insert(window);
self.space.map_element(mapped, position, false);
}
pub fn unmap_window(&mut self, space: &mut Space, window: &Window) {
pub fn unmap(&mut self, window: &CosmicMapped) -> bool {
#[allow(irrefutable_let_patterns)]
let is_maximized = match &window.toplevel() {
Kind::Xdg(surface) => {
surface.with_pending_state(|state| state.states.contains(XdgState::Maximized))
}
};
let is_maximized = window.is_maximized();
if !is_maximized {
if let Some(location) = space.window_location(window) {
let user_data = window.user_data();
user_data.insert_if_missing(|| WindowUserData::default());
user_data
.get::<WindowUserData>()
.unwrap()
.lock()
.unwrap()
.last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size);
if let Some(location) = self.space.element_location(window) {
*window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
location,
window.geometry().size,
));
}
}
space.unmap_window(window);
self.pending_windows.retain(|w| w != window);
self.windows.remove(window);
let was_unmaped = self.space.elements().any(|e| e == window);
self.space.unmap_elem(&window);
was_unmaped
}
pub fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) {
let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone();
if let Some(location) = space.window_location(window) {
let user_data = window.user_data();
user_data.insert_if_missing(|| WindowUserData::default());
user_data
.get::<WindowUserData>()
.unwrap()
.lock()
.unwrap()
.last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size);
}
space.map_window(
&window,
(geometry.loc.x, geometry.loc.y),
FLOATING_INDEX,
true,
);
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(surface) = &window.toplevel() {
surface.with_pending_state(|state| {
state.states.set(XdgState::Maximized);
state.size = Some(geometry.size);
});
window.configure();
}
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Logical>> {
self.space.element_geometry(elem)
}
pub fn unmaximize_request(&mut self, space: &mut Space, window: &Window) {
let last_geometry = window
.user_data()
.get::<WindowUserData>()
.map(|u| u.lock().unwrap().last_geometry);
match window.toplevel() {
Kind::Xdg(toplevel) => {
toplevel.with_pending_state(|state| {
state.states.unset(XdgState::Maximized);
state.size = last_geometry.map(|g| g.size);
});
toplevel.send_configure();
pub fn maximize_request(&mut self, window: &Window) {
if let Some(mapped) = self
.space
.elements()
.find(|m| m.windows().any(|(w, _)| &w == window))
{
if let Some(location) = self.space.element_location(mapped) {
*mapped.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
location,
mapped.geometry().size,
));
}
}
if let Some(last_location) = last_geometry.map(|g| g.loc) {
space.map_window(&window, last_location, FLOATING_INDEX, true);
}
pub fn unmaximize_request(&mut self, window: &Window) {
let maybe_mapped = self
.space
.elements()
.find(|m| m.windows().any(|(w, _)| &w == window))
.cloned();
if let Some(mapped) = maybe_mapped {
let last_geometry = mapped.last_geometry.lock().unwrap().clone();
mapped.set_size(last_geometry.map(|g| g.size).expect("No previous size?"));
let last_location = last_geometry.map(|g| g.loc).expect("No previous location?");
self.space.map_element(mapped, last_location, true);
}
}
pub fn resize_request(
state: &mut State,
window: &Window,
&mut self,
mapped: &CosmicMapped,
seat: &Seat<State>,
serial: Serial,
_serial: Serial,
start_data: PointerGrabStartData<State>,
edges: ResizeEdge,
) {
// it is so stupid, that we have to do this here. TODO: Refactor grabs
let workspace = state
.common
.shell
.space_for_window_mut(window.toplevel().wl_surface())
.unwrap();
let space = &mut workspace.space;
) -> Option<ResizeSurfaceGrab> {
if seat.get_pointer().is_some() {
let location = self.space.element_location(&mapped).unwrap();
let size = mapped.geometry().size;
if let Some(pointer) = seat.get_pointer() {
let location = space.window_location(&window).unwrap();
let size = window.geometry().size;
let grab =
grabs::ResizeSurfaceGrab::new(start_data, window.clone(), edges, location, size);
pointer.set_grab(state, grab, serial, Focus::Clear);
Some(grabs::ResizeSurfaceGrab::new(
start_data,
mapped.clone(),
edges,
location,
size,
))
} else {
None
}
}
pub fn mapped(&self) -> impl Iterator<Item = &CosmicMapped> {
self.space.elements()
}
pub fn windows(&self) -> impl Iterator<Item = Window> + '_ {
self.mapped().flat_map(|e| e.windows().map(|(w, _)| w))
}
pub fn refresh(&mut self) {
self.space.refresh();
for element in self
.space
.elements()
.filter(|e| self.space.outputs_for_element(e).is_empty())
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
// TODO what about windows leaving to the top with no headerbar to drag? can that happen? (Probably if the user is moving outputs down)
*element.last_geometry.lock().unwrap() = None;
let output = self.space.outputs().next().unwrap().clone();
self.map_internal(element, &output, None);
}
}
pub fn most_overlapped_output_for_element(&self, elem: &CosmicMapped) -> Option<Output> {
let elem_geo = self.space.element_geometry(elem)?;
if self.space.outputs().nth(1).is_none() {
return self.space.outputs().next().cloned();
}
Some(
self.space
.outputs_for_element(elem)
.into_iter()
.max_by_key(|o| {
let output_geo = self.space.output_geometry(o).unwrap();
if let Some(intersection) = output_geo.intersection(elem_geo) {
intersection.size.w * intersection.size.h
} else {
0
}
})
.unwrap_or(self.space.outputs().next().unwrap().clone()),
)
}
pub fn merge(&mut self, other: FloatingLayout) {
let mut output_pos_map = HashMap::new();
for output in self.space.outputs() {
output_pos_map.insert(
output.clone(),
self.space.output_geometry(output).unwrap().loc
- other
.space
.output_geometry(output)
.map(|geo| geo.loc)
.unwrap_or_else(|| (0, 0).into()),
);
}
for element in other.space.elements() {
let mut elem_geo = other.space.element_geometry(element).unwrap();
let output = other
.space
.outputs_for_element(element)
.into_iter()
.filter(|o| self.space.outputs().any(|o2| o == o2))
.max_by_key(|o| {
let output_geo = other.space.output_geometry(o).unwrap();
let intersection = output_geo.intersection(elem_geo).unwrap();
intersection.size.w * intersection.size.h
})
.unwrap_or(self.space.outputs().next().unwrap().clone());
elem_geo.loc += output_pos_map
.get(&output)
.copied()
.unwrap_or_else(|| (0, 0).into());
self.space.map_element(element.clone(), elem_geo.loc, false);
}
self.refresh(); //fixup any out of bounds elements
}
pub fn render_output<R>(
&self,
output: &Output,
) -> Result<Vec<CosmicMappedRenderElement<R>>, OutputNotMapped>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
let output_scale = output.current_scale().fractional_scale();
let output_geo = self.space.output_geometry(output).ok_or(OutputNotMapped)?;
Ok(self
.space
.render_elements_for_region::<R, _>(&output_geo, output_scale))
}
}

View file

@ -1,11 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{input::ActiveOutput, state::State};
use regex::RegexSet;
use smithay::{
desktop::{Space, Window},
input::Seat,
output::Output,
desktop::Window,
wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes},
};
use std::sync::Mutex;
@ -19,6 +16,16 @@ pub enum Orientation {
Vertical,
}
impl std::ops::Not for Orientation {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Orientation::Horizontal => Orientation::Vertical,
Orientation::Vertical => Orientation::Horizontal,
}
}
}
lazy_static::lazy_static! {
static ref EXCEPTIONS_APPID: RegexSet = RegexSet::new(&[
r"Authy Desktop",
@ -118,17 +125,3 @@ pub fn should_be_floating(window: &Window) -> bool {
false
})
}
fn output_from_seat(seat: Option<&Seat<State>>, space: &Space) -> Option<Output> {
seat.and_then(|seat| {
seat.user_data()
.get::<ActiveOutput>()
.map(|active| active.0.borrow().clone())
.or_else(|| {
seat.get_pointer()
.map(|ptr| space.output_under(ptr.current_location()).next().unwrap())
.cloned()
})
})
.or_else(|| space.outputs().next().cloned())
}

View file

@ -1,23 +1,52 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{shell::layout::Orientation, utils::prelude::*};
use atomic_float::AtomicF64;
use crate::{
shell::{focus::target::PointerFocusTarget, layout::Orientation},
utils::prelude::*,
};
use id_tree::NodeId;
use smithay::{
input::pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle,
},
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Logical, Point, Size},
output::{Output, WeakOutput},
utils::{Logical, Point},
};
use std::sync::{atomic::Ordering, Arc};
use super::Data;
pub struct ResizeForkGrab {
pub start_data: PointerGrabStartData<State>,
pub orientation: Orientation,
pub initial_size: Size<i32, Logical>,
pub initial_ratio: f64,
pub ratio: Arc<AtomicF64>,
start_data: PointerGrabStartData<State>,
idx: usize,
initial_size_upleft: i32,
initial_size_downright: i32,
node: NodeId,
output: WeakOutput,
}
impl ResizeForkGrab {
pub fn new(
start_data: PointerGrabStartData<State>,
node: NodeId,
output: &Output,
data: &Data,
idx: usize,
) -> ResizeForkGrab {
let sizes = match data {
Data::Group { ref sizes, .. } => sizes,
_ => panic!("Resizing without a group?!?"),
};
ResizeForkGrab {
start_data,
idx,
initial_size_upleft: sizes.iter().take(idx + 1).sum(),
initial_size_downright: sizes.iter().skip(idx + 1).sum(),
node,
output: output.downgrade(),
}
}
}
impl PointerGrab<State> for ResizeForkGrab {
@ -25,21 +54,139 @@ impl PointerGrab<State> for ResizeForkGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(WlSurface, Point<i32, Logical>)>,
_focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
event: &MotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
let delta = event.location - self.start_data.location;
let delta = match self.orientation {
Orientation::Vertical => delta.x / self.initial_size.w as f64,
Orientation::Horizontal => delta.y / self.initial_size.h as f64,
};
self.ratio.store(
0.9f64.min(0.1f64.max(self.initial_ratio + delta)),
Ordering::SeqCst,
);
if let Some(output) = self.output.upgrade() {
let tiling_layer = &mut data.common.shell.active_space_mut(&output).tiling_layer;
if let Some(tree) = tiling_layer.trees.get_mut(&output) {
if tree.get(&self.node).is_ok() {
let orientation = tree.get(&self.node).unwrap().data().orientation();
let delta = match orientation {
Orientation::Vertical => delta.x,
Orientation::Horizontal => delta.y,
}
.round() as i32;
let upleft_node_id =
match tree.children_ids(&self.node).unwrap().skip(self.idx).next() {
Some(elem) => elem,
None => {
return;
}
};
let downright_node_id = match tree
.children_ids(&self.node)
.unwrap()
.skip(self.idx + 1)
.next()
{
Some(elem) => elem,
None => {
return;
}
};
let next_mapped = |mut node| loop {
if let Some(node_id) = node {
match tree.get(&node_id).unwrap().data() {
Data::Group { orientation: o, .. } if o == &orientation => {
node = tree.children_ids(&node_id).unwrap().last().cloned();
}
_ => {
break node_id;
}
}
} else {
unreachable!()
}
};
let upleft_mapped_id = next_mapped(Some(upleft_node_id.clone()));
let downright_mapped_id = next_mapped(Some(downright_node_id.clone()));
let new_upleft_size = self.initial_size_upleft + delta;
let new_downright_size = self.initial_size_downright - delta;
let new_upleft_mapped_size = match orientation {
Orientation::Horizontal => {
tree.get(&upleft_mapped_id)
.unwrap()
.data()
.geometry()
.size
.h
}
Orientation::Vertical => {
tree.get(&upleft_mapped_id)
.unwrap()
.data()
.geometry()
.size
.w
}
} + delta;
let new_downright_mapped_size = match orientation {
Orientation::Horizontal => {
tree.get(&downright_mapped_id)
.unwrap()
.data()
.geometry()
.size
.h
}
Orientation::Vertical => {
tree.get(&downright_mapped_id)
.unwrap()
.data()
.geometry()
.size
.w
}
} - delta;
if new_upleft_mapped_size > 100 && new_downright_mapped_size > 100 {
// lets update
{
let node = tree.get_mut(&self.node).unwrap();
let data = node.data_mut();
match data {
Data::Group { sizes, .. } => {
sizes[self.idx] = new_upleft_size;
sizes[self.idx + 1] = new_downright_size;
}
_ => unreachable!(),
};
}
for (mapped_id, mapped_size) in &[
(upleft_mapped_id, new_upleft_mapped_size),
(downright_mapped_id, new_downright_mapped_size),
] {
let parent = tree.get(mapped_id).unwrap().parent().cloned().unwrap();
if parent != self.node {
let idx = tree
.children_ids(&parent)
.unwrap()
.position(|id| id == mapped_id)
.unwrap();
let node = tree.get_mut(&parent).unwrap();
let data = node.data_mut();
match data {
Data::Group { sizes, .. } => {
sizes[idx] = *mapped_size;
}
_ => unreachable!(),
};
}
}
return tiling_layer.refresh();
}
}
}
}
}
fn button(

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,109 +1,221 @@
use crate::{
shell::layout::{floating::FloatingLayout, tiling::TilingLayout},
backend::render::element::{AsGles2Frame, AsGlowRenderer},
shell::layout::{
floating::{FloatingLayout, MoveSurfaceGrab},
tiling::TilingLayout,
},
state::State,
wayland::protocols::workspace::WorkspaceHandle,
utils::prelude::*,
wayland::{
handlers::screencopy::DropableSession,
protocols::{
screencopy::{BufferParams, Session as ScreencopySession},
workspace::WorkspaceHandle,
},
},
};
use indexmap::IndexSet;
use smithay::{
desktop::{Kind, Space, Window, WindowSurfaceType},
backend::renderer::{
element::{surface::WaylandSurfaceRenderElement, AsRenderElements, Element, RenderElement},
ImportAll, Renderer,
},
desktop::{layer_map_for_output, space::SpaceElement, Kind, LayerSurface, Window},
input::{pointer::GrabStartData as PointerGrabStartData, Seat},
output::Output,
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge},
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle},
wayland_server::protocol::wl_surface::WlSurface,
},
utils::{IsAlive, Serial},
utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial},
wayland::shell::wlr_layer::Layer,
};
use std::collections::HashMap;
use super::{
element::CosmicMapped,
focus::{FocusStack, FocusStackMut},
grabs::ResizeGrab,
CosmicMappedRenderElement,
};
#[derive(Debug)]
pub struct Workspace {
pub idx: u8,
pub space: Space,
pub tiling_layer: TilingLayout,
pub floating_layer: FloatingLayout,
tiling_enabled: bool,
pub fullscreen: HashMap<String, Window>,
pub tiling_enabled: bool,
pub fullscreen: HashMap<Output, Window>,
pub handle: WorkspaceHandle,
pub focus_stack: FocusStacks,
pub pending_buffers: Vec<(ScreencopySession, BufferParams)>,
pub screencopy_sessions: Vec<DropableSession>,
}
#[derive(Debug, Default)]
pub struct FocusStacks(HashMap<Seat<State>, IndexSet<CosmicMapped>>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ManagedState {
Tiling,
Floating,
}
impl Workspace {
pub fn new(idx: u8, handle: WorkspaceHandle) -> Workspace {
pub fn new(handle: WorkspaceHandle) -> Workspace {
Workspace {
idx,
space: Space::new(None),
tiling_layer: TilingLayout::new(),
floating_layer: FloatingLayout::new(),
tiling_enabled: true,
fullscreen: HashMap::new(),
handle,
focus_stack: FocusStacks::default(),
pending_buffers: Vec::new(),
screencopy_sessions: Vec::new(),
}
}
pub fn refresh(&mut self, dh: &DisplayHandle) {
let outputs = self.space.outputs().collect::<Vec<_>>();
let dead_output_windows = self
.fullscreen
.iter()
.filter(|(name, _)| !outputs.iter().any(|o| o.name() == **name))
.map(|(_, w)| w)
.cloned()
.collect::<Vec<_>>();
for window in dead_output_windows {
self.unfullscreen_request(&window);
}
pub fn refresh(&mut self) {
self.fullscreen.retain(|_, w| w.alive());
self.floating_layer.refresh(&mut self.space);
self.tiling_layer.refresh(&mut self.space);
self.space.refresh(dh);
self.floating_layer.refresh();
self.tiling_layer.refresh();
}
pub fn commit(&mut self, surface: &WlSurface) {
if let Some(mapped) = self.element_for_surface(surface) {
mapped
.windows()
.find(|(w, _)| w.toplevel().wl_surface() == surface)
.unwrap()
.0
.on_commit();
}
}
pub fn map_output(&mut self, output: &Output, position: Point<i32, Logical>) {
self.tiling_layer.map_output(output, position);
self.floating_layer.map_output(output, position);
}
pub fn unmap_output(&mut self, output: &Output) {
if let Some(dead_output_window) = self.fullscreen.remove(output) {
self.unfullscreen_request(&dead_output_window);
}
self.tiling_layer.unmap_output(output);
self.floating_layer.unmap_output(output);
self.refresh();
}
pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option<ManagedState> {
let was_floating = self.floating_layer.unmap(&mapped);
let was_tiling = self.tiling_layer.unmap(&mapped).is_some();
if was_floating || was_tiling {
assert!(was_floating != was_tiling);
}
self.focus_stack
.0
.values_mut()
.for_each(|set| set.retain(|m| m != mapped));
if was_floating {
Some(ManagedState::Floating)
} else if was_tiling {
Some(ManagedState::Tiling)
} else {
None
}
}
pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> {
self.floating_layer
.mapped()
.chain(self.tiling_layer.mapped().map(|(_, w, _)| w))
.find(|e| {
e.windows()
.any(|(w, _)| w.toplevel().wl_surface() == surface)
})
}
pub fn outputs_for_element(&self, elem: &CosmicMapped) -> impl Iterator<Item = Output> {
self.floating_layer
.space
.outputs_for_element(elem)
.into_iter()
.chain(self.tiling_layer.output_for_element(elem).cloned())
}
pub fn output_under(&self, point: Point<i32, Logical>) -> Option<&Output> {
let space = &self.floating_layer.space;
space.outputs().find(|o| {
let internal_output_geo = space.output_geometry(o).unwrap();
let external_output_geo = o.geometry();
internal_output_geo.contains(point - external_output_geo.loc + internal_output_geo.loc)
})
}
pub fn element_under(
&self,
location: Point<f64, Logical>,
) -> Option<(&CosmicMapped, Point<i32, Logical>)> {
self.floating_layer
.space
.element_under(location)
.or_else(|| {
self.tiling_layer.mapped().find_map(|(_, mapped, loc)| {
let test_point = location - loc.to_f64() + mapped.geometry().loc.to_f64();
mapped
.is_in_input_region(&test_point)
.then_some((mapped, loc - mapped.geometry().loc))
})
})
}
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Logical>> {
let space = &self.floating_layer.space;
let outputs = space.outputs().collect::<Vec<_>>();
let offset = if outputs.len() == 1
&& space.output_geometry(&outputs[0]).unwrap().loc == Point::from((0, 0))
{
outputs[0].geometry().loc
} else {
(0, 0).into()
};
self.floating_layer
.space
.element_geometry(elem)
.or_else(|| self.tiling_layer.element_geometry(elem))
.map(|mut geo| {
geo.loc += offset;
geo
})
}
pub fn maximize_request(&mut self, window: &Window, output: &Output) {
if self.fullscreen.values().any(|w| w == window) {
if self.fullscreen.contains_key(output) {
return;
}
if self.floating_layer.windows.contains(window) {
self.floating_layer
.maximize_request(&mut self.space, window, output);
}
}
self.floating_layer.maximize_request(window);
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
xdg.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Maximized);
state.states.unset(xdg_toplevel::State::Fullscreen);
});
}
self.set_fullscreen(window, output)
}
pub fn unmaximize_request(&mut self, window: &Window) {
if self.fullscreen.values().any(|w| w == window) {
return self.unfullscreen_request(window);
}
if self.floating_layer.windows.contains(window) {
self.floating_layer
.unmaximize_request(&mut self.space, window);
}
}
pub fn resize_request(
state: &mut State,
surface: &WlSurface,
seat: &Seat<State>,
serial: Serial,
start_data: PointerGrabStartData<State>,
edges: ResizeEdge,
) {
let workspace = state.common.shell.space_for_window_mut(surface).unwrap();
let window = workspace
.space
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
.unwrap()
.clone();
if workspace.fullscreen.values().any(|w| w == &window) {
return;
}
if workspace.floating_layer.windows.contains(&window) {
FloatingLayout::resize_request(state, &window, seat, serial, start_data.clone(), edges)
} else if workspace.tiling_layer.windows.contains(&window) {
TilingLayout::resize_request(state, &window, seat, serial, start_data, edges)
self.unfullscreen_request(window);
self.floating_layer.unmaximize_request(window);
}
}
pub fn fullscreen_request(&mut self, window: &Window, output: &Output) {
if self.fullscreen.contains_key(&output.name()) {
if self.fullscreen.contains_key(output) {
return;
}
@ -111,6 +223,24 @@ impl Workspace {
if let Kind::Xdg(xdg) = &window.toplevel() {
xdg.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
state.states.unset(xdg_toplevel::State::Maximized);
});
}
self.set_fullscreen(window, output)
}
fn set_fullscreen(&mut self, window: &Window, output: &Output) {
if let Some(mapped) = self
.mapped()
.find(|m| m.windows().any(|(w, _)| &w == window))
{
mapped.set_active(window);
}
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
xdg.with_pending_state(|state| {
state.size = Some(
output
.current_mode()
@ -123,8 +253,8 @@ impl Workspace {
});
xdg.send_configure();
self.fullscreen.insert(output.name(), window.clone());
}
self.fullscreen.insert(output.clone(), window.clone());
}
pub fn unfullscreen_request(&mut self, window: &Window) {
@ -133,45 +263,127 @@ impl Workspace {
if let Kind::Xdg(xdg) = &window.toplevel() {
xdg.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen);
state.states.unset(xdg_toplevel::State::Maximized);
state.size = None;
});
self.floating_layer.refresh(&mut self.space);
self.tiling_layer.refresh(&mut self.space);
self.floating_layer.refresh();
self.tiling_layer.refresh();
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)
pub fn maximize_toggle(&mut self, window: &Window, output: &Output) {
if self.fullscreen.contains_key(output) {
self.unmaximize_request(window)
} else {
self.fullscreen_request(window, output)
self.maximize_request(window, output)
}
}
pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> {
if !self.space.outputs().any(|o| o == output) {
self.fullscreen.get(output).filter(|w| w.alive())
}
pub fn resize_request(
&mut self,
mapped: &CosmicMapped,
seat: &Seat<State>,
serial: Serial,
start_data: PointerGrabStartData<State>,
edges: ResizeEdge,
) -> Option<ResizeGrab> {
if mapped.is_fullscreen() || mapped.is_maximized() {
return None;
}
self.fullscreen.get(&output.name()).filter(|w| w.alive())
let edges = edges.into();
if self.floating_layer.mapped().any(|m| m == mapped) {
self.floating_layer
.resize_request(mapped, seat, serial, start_data.clone(), edges)
.map(Into::into)
} else if self.tiling_layer.mapped().any(|(_, m, _)| m == mapped) {
self.tiling_layer
.resize_request(mapped, seat, serial, start_data, edges)
.map(Into::into)
} else {
None
}
}
pub fn move_request(
&mut self,
window: &Window,
seat: &Seat<State>,
output: &Output,
_serial: Serial,
start_data: PointerGrabStartData<State>,
) -> Option<MoveSurfaceGrab> {
let pointer = seat.get_pointer().unwrap();
let pos = pointer.current_location();
let mapped = self
.element_for_surface(window.toplevel().wl_surface())?
.clone();
let mut initial_window_location = self.element_geometry(&mapped).unwrap().loc;
if mapped.is_fullscreen() || mapped.is_maximized() {
// If surface is maximized then unmaximize it
self.unmaximize_request(window);
let new_size = match window.toplevel() {
Kind::Xdg(toplevel) => toplevel.with_pending_state(|state| state.size),
//_ => unreachable!(), // TODO x11
};
let ratio = pos.x / output.geometry().size.w as f64;
initial_window_location = new_size
.map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into())
.unwrap_or_else(|| pos)
.to_i32_round();
}
let was_floating = self.floating_layer.unmap(&mapped);
//let was_tiled = self.tiling_layer.unmap(&mapped);
//assert!(was_floating != was_tiled);
if was_floating {
Some(MoveSurfaceGrab::new(
start_data,
mapped,
seat,
pos,
initial_window_location,
))
} else {
None // TODO
}
}
pub fn toggle_tiling(&mut self, seat: &Seat<State>) {
if self.tiling_enabled {
for window in self.tiling_layer.windows.clone().into_iter() {
self.tiling_layer.unmap_window(&mut self.space, &window);
self.floating_layer
.map_window(&mut self.space, window, seat, None);
for window in self
.tiling_layer
.mapped()
.map(|(_, m, _)| m.clone())
.collect::<Vec<_>>()
.into_iter()
{
self.tiling_layer.unmap(&window);
self.floating_layer.map(window, seat, None);
}
self.tiling_enabled = false;
} else {
let focus_stack = self.focus_stack(seat);
for window in self.floating_layer.windows.clone().into_iter() {
self.floating_layer.unmap_window(&mut self.space, &window);
self.tiling_layer
.map_window(&mut self.space, window, seat, focus_stack.iter())
let focus_stack = self.focus_stack.get(seat);
for window in self
.floating_layer
.mapped()
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
self.floating_layer.unmap(&window);
self.tiling_layer.map(window, seat, focus_stack.iter())
}
self.tiling_enabled = true;
}
@ -179,18 +391,290 @@ impl Workspace {
pub fn toggle_floating_window(&mut self, seat: &Seat<State>) {
if self.tiling_enabled {
if let Some(window) = self.focus_stack(seat).iter().next().cloned() {
if self.tiling_layer.windows.contains(&window) {
self.tiling_layer.unmap_window(&mut self.space, &window);
self.floating_layer
.map_window(&mut self.space, window, seat, None);
} else if self.floating_layer.windows.contains(&window) {
let focus_stack = self.focus_stack(seat);
self.floating_layer.unmap_window(&mut self.space, &window);
self.tiling_layer
.map_window(&mut self.space, window, seat, focus_stack.iter())
if let Some(window) = self.focus_stack.get(seat).iter().next().cloned() {
if self.tiling_layer.mapped().any(|(_, m, _)| m == &window) {
self.tiling_layer.unmap(&window);
self.floating_layer.map(window, seat, None);
} else if self.floating_layer.mapped().any(|w| w == &window) {
let focus_stack = self.focus_stack.get(seat);
self.floating_layer.unmap(&window);
self.tiling_layer.map(window, seat, focus_stack.iter())
}
}
}
}
pub fn mapped(&self) -> impl Iterator<Item = &CosmicMapped> {
self.floating_layer
.mapped()
.chain(self.tiling_layer.mapped().map(|(_, w, _)| w))
}
pub fn windows(&self) -> impl Iterator<Item = Window> + '_ {
self.floating_layer
.windows()
.chain(self.tiling_layer.windows().map(|(_, w, _)| w))
}
pub fn render_output<R>(
&self,
output: &Output,
) -> Result<Vec<WorkspaceRenderElement<R>>, OutputNotMapped>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
let mut render_elements = Vec::new();
let output_scale = output.current_scale().fractional_scale();
let layer_map = layer_map_for_output(output);
if let Some(fullscreen) = self.fullscreen.get(output) {
// overlay layer surfaces
render_elements.extend(
layer_map
.layers()
.rev()
.filter(|s| s.layer() == Layer::Overlay)
.filter_map(|surface| {
layer_map
.layer_geometry(surface)
.map(|geo| (geo.loc, surface))
})
.flat_map(|(loc, surface)| {
AsRenderElements::<R>::render_elements::<WorkspaceRenderElement<R>>(
surface,
loc.to_physical_precise_round(output_scale),
Scale::from(output_scale),
)
}),
);
// fullscreen window
render_elements.extend(AsRenderElements::<R>::render_elements::<
WorkspaceRenderElement<R>,
>(fullscreen, (0, 0).into(), output_scale.into()));
} else {
// TODO: Handle modes like
// - keyboard window swapping
// - resizing / moving in tiling
// overlay and top layer surfaces
let lower = {
let (lower, upper): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map
.layers()
.rev()
.partition(|s| matches!(s.layer(), Layer::Background | Layer::Bottom));
render_elements.extend(
upper
.into_iter()
.filter_map(|surface| {
layer_map
.layer_geometry(surface)
.map(|geo| (geo.loc, surface))
})
.flat_map(|(loc, surface)| {
AsRenderElements::<R>::render_elements::<WorkspaceRenderElement<R>>(
surface,
loc.to_physical_precise_round(output_scale),
Scale::from(output_scale),
)
}),
);
lower
};
// floating surfaces
render_elements.extend(
self.floating_layer
.render_output::<R>(output)?
.into_iter()
.map(WorkspaceRenderElement::from),
);
//tiling surfaces
render_elements.extend(
self.tiling_layer
.render_output::<R>(output)?
.into_iter()
.map(WorkspaceRenderElement::from),
);
// bottom and background layer surfaces
{
render_elements.extend(
lower
.into_iter()
.filter_map(|surface| {
layer_map
.layer_geometry(surface)
.map(|geo| (geo.loc, surface))
})
.flat_map(|(loc, surface)| {
AsRenderElements::<R>::render_elements::<WorkspaceRenderElement<R>>(
surface,
loc.to_physical_precise_round(output_scale),
Scale::from(output_scale),
)
}),
);
}
}
Ok(render_elements)
}
}
impl FocusStacks {
pub fn get<'a>(&'a self, seat: &Seat<State>) -> FocusStack<'a> {
FocusStack(self.0.get(seat))
}
pub fn get_mut<'a>(&'a mut self, seat: &Seat<State>) -> FocusStackMut<'a> {
FocusStackMut(self.0.entry(seat.clone()).or_default())
}
}
pub struct OutputNotMapped;
pub enum WorkspaceRenderElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
{
Wayland(WaylandSurfaceRenderElement),
Window(CosmicMappedRenderElement<R>),
}
impl<R> Element for WorkspaceRenderElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
{
fn id(&self) -> &smithay::backend::renderer::element::Id {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.id(),
WorkspaceRenderElement::Window(elem) => elem.id(),
}
}
fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.current_commit(),
WorkspaceRenderElement::Window(elem) => elem.current_commit(),
}
}
fn src(&self) -> Rectangle<f64, smithay::utils::Buffer> {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.src(),
WorkspaceRenderElement::Window(elem) => elem.src(),
}
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, smithay::utils::Physical> {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.geometry(scale),
WorkspaceRenderElement::Window(elem) => elem.geometry(scale),
}
}
fn location(&self, scale: Scale<f64>) -> Point<i32, smithay::utils::Physical> {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.location(scale),
WorkspaceRenderElement::Window(elem) => elem.location(scale),
}
}
fn transform(&self) -> smithay::utils::Transform {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.transform(),
WorkspaceRenderElement::Window(elem) => elem.transform(),
}
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<smithay::backend::renderer::utils::CommitCounter>,
) -> Vec<Rectangle<i32, smithay::utils::Physical>> {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.damage_since(scale, commit),
WorkspaceRenderElement::Window(elem) => elem.damage_since(scale, commit),
}
}
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, smithay::utils::Physical>> {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.opaque_regions(scale),
WorkspaceRenderElement::Window(elem) => elem.opaque_regions(scale),
}
}
}
impl<R> RenderElement<R> for WorkspaceRenderElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn draw(
&self,
renderer: &mut R,
frame: &mut <R as Renderer>::Frame,
location: Point<i32, smithay::utils::Physical>,
scale: Scale<f64>,
damage: &[Rectangle<i32, smithay::utils::Physical>],
log: &slog::Logger,
) -> Result<(), <R as Renderer>::Error> {
match self {
WorkspaceRenderElement::Wayland(elem) => {
elem.draw(renderer, frame, location, scale, damage, log)
}
WorkspaceRenderElement::Window(elem) => {
elem.draw(renderer, frame, location, scale, damage, log)
}
}
}
fn underlying_storage(
&self,
renderer: &R,
) -> Option<smithay::backend::renderer::element::UnderlyingStorage<'_, R>> {
match self {
WorkspaceRenderElement::Wayland(elem) => elem.underlying_storage(renderer),
WorkspaceRenderElement::Window(elem) => elem.underlying_storage(renderer),
}
}
}
impl<R> From<WaylandSurfaceRenderElement> for WorkspaceRenderElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: WaylandSurfaceRenderElement) -> Self {
WorkspaceRenderElement::Wayland(elem)
}
}
impl<R> From<CosmicMappedRenderElement<R>> for WorkspaceRenderElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Frame: AsGles2Frame,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: CosmicMappedRenderElement<R>) -> Self {
WorkspaceRenderElement::Window(elem)
}
}

View file

@ -7,37 +7,46 @@ use crate::{
shell::Shell,
utils::prelude::*,
wayland::protocols::{
drm::WlDrmState, export_dmabuf::ExportDmabufState,
output_configuration::OutputConfigurationState, workspace::WorkspaceClientState,
drm::WlDrmState,
output_configuration::OutputConfigurationState,
screencopy::{BufferParams, ScreencopyState, Session as ScreencopySession},
workspace::WorkspaceClientState,
},
};
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode;
use smithay::{
backend::drm::DrmNode,
backend::{
drm::DrmNode,
renderer::{
element::{default_primary_scanout_output_compare, RenderElementStates},
glow::GlowRenderer,
},
},
desktop::utils::{
surface_presentation_feedback_flags_from_states, surface_primary_scanout_output,
update_surface_primary_scanout_output, OutputPresentationFeedback,
},
input::{Seat, SeatState},
output::{Mode as OutputMode, Output, Scale},
reexports::{
calloop::{LoopHandle, LoopSignal},
wayland_server::{
backend::{ClientData, ClientId, DisconnectReason},
protocol::wl_shm,
Display, DisplayHandle,
},
},
utils::{Buffer, Size},
utils::{Clock, Monotonic},
wayland::{
compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState,
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState,
primary_selection::PrimarySelectionState, shm::ShmState, viewporter::ViewporterState,
presentation::PresentationState, primary_selection::PrimarySelectionState, shm::ShmState,
viewporter::ViewporterState,
},
};
use std::{
cell::RefCell,
ffi::OsString,
sync::{atomic::AtomicBool, Arc},
time::Instant,
};
#[cfg(feature = "debug")]
use std::{collections::VecDeque, time::Duration};
use std::{cell::RefCell, ffi::OsString, time::Duration};
use std::{collections::VecDeque, time::Instant};
pub struct ClientState {
pub workspace_client_state: WorkspaceClientState,
@ -69,12 +78,11 @@ pub struct Common {
//pub output_conf: ConfigurationManager,
pub shell: Shell,
pub dirty_flag: Arc<AtomicBool>,
pub seats: Vec<Seat<State>>,
pub last_active_seat: Seat<State>,
seats: Vec<Seat<State>>,
last_active_seat: Option<Seat<State>>,
pub start_time: Instant,
pub clock: Clock<Monotonic>,
pub should_stop: bool,
pub log: LogState,
@ -85,34 +93,18 @@ pub struct Common {
pub compositor_state: CompositorState,
pub data_device_state: DataDeviceState,
pub dmabuf_state: DmabufState,
pub export_dmabuf_state: ExportDmabufState,
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
pub output_state: OutputManagerState,
pub output_configuration_state: OutputConfigurationState<State>,
pub presentation_state: PresentationState,
pub primary_selection_state: PrimarySelectionState,
pub screencopy_state: ScreencopyState,
pub seat_state: SeatState<State>,
pub shm_state: ShmState,
pub wl_drm_state: WlDrmState,
pub viewporter_state: ViewporterState,
}
#[cfg(feature = "debug")]
pub struct Egui {
pub debug_state: smithay_egui::EguiState,
pub log_state: smithay_egui::EguiState,
pub modifiers: smithay::wayland::seat::ModifiersState,
pub active: bool,
pub alpha: f32,
}
#[cfg(feature = "debug")]
pub struct Fps {
pub state: smithay_egui::EguiState,
pub modifiers: smithay::wayland::seat::ModifiersState,
pub frames: VecDeque<(Instant, Duration)>,
pub start: Instant,
}
pub enum BackendData {
X11(X11State),
Winit(WinitState),
@ -149,11 +141,12 @@ impl BackendData {
output: &Output,
test_only: bool,
shell: &mut Shell,
seats: impl Iterator<Item = Seat<State>>,
loop_handle: &LoopHandle<'_, Data>,
) -> Result<(), anyhow::Error> {
let result = match self {
BackendData::Kms(ref mut state) => {
state.apply_config_for_output(output, shell, test_only, loop_handle)
state.apply_config_for_output(output, seats, shell, test_only, loop_handle)
}
BackendData::Winit(ref mut state) => state.apply_config_for_output(output, test_only),
BackendData::X11(ref mut state) => state.apply_config_for_output(output, test_only),
@ -187,102 +180,25 @@ impl BackendData {
result
}
pub fn schedule_render(&mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output) {
pub fn schedule_render(
&mut self,
loop_handle: &LoopHandle<'_, Data>,
output: &Output,
screencopy: Option<Vec<(ScreencopySession, BufferParams)>>,
) {
match self {
BackendData::Winit(_) => {} // We cannot do this on the winit backend.
BackendData::Winit(ref mut state) => state.pending_screencopy(screencopy), // We cannot do this on the winit backend.
// Winit has a very strict render-loop and skipping frames breaks atleast the wayland winit-backend.
// Swapping with damage (which should be empty on these frames) is likely good enough anyway.
BackendData::X11(ref mut state) => state.schedule_render(output),
BackendData::X11(ref mut state) => state.schedule_render(output, screencopy),
BackendData::Kms(ref mut state) => {
if let Err(err) = state.schedule_render(loop_handle, output) {
if let Err(err) = state.schedule_render(loop_handle, output, None, screencopy) {
slog_scope::crit!("Failed to schedule event, are we shutting down? {:?}", err);
}
}
_ => unreachable!("No backend was initialized"),
}
}
pub fn offscreen_for_output(
&mut self,
output: &Output,
state: &mut Common,
) -> anyhow::Result<(Vec<u8>, Size<i32, Buffer>)> {
use crate::backend::render::{render_output, AsGles2Renderer, CustomElem};
use anyhow::Context;
use smithay::backend::renderer::{ImportAll, Renderer};
use smithay::desktop::space::RenderElement;
use smithay::{
backend::{
drm::NodeType,
renderer::{gles2::Gles2Renderbuffer, Bind, ExportMem, Offscreen},
},
utils::Rectangle,
};
fn capture<E, T, R>(
gpu: Option<DrmNode>,
renderer: &mut R,
output: &Output,
state: &mut Common,
) -> anyhow::Result<(Vec<u8>, Size<i32, Buffer>)>
where
E: std::error::Error + Send + Sync + 'static,
T: Clone + 'static,
R: Renderer<Error = E, TextureId = T>
+ ImportAll
+ AsGles2Renderer
+ Offscreen<Gles2Renderbuffer>
+ Bind<Gles2Renderbuffer>
+ ExportMem,
CustomElem: RenderElement<R>,
{
let size = output
.geometry()
.size
.to_f64()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform().into(),
)
.to_i32_round();
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)?;
renderer.bind(buffer)?;
render_output(
gpu.as_ref(),
renderer,
0,
state,
output,
false,
#[cfg(feature = "debug")]
None,
)
.map_err(|err| anyhow::anyhow!("Failed to render output: {:?}", err))?; // lifetime issue, grrr
let mapping = renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), size))?;
let data = Vec::from(renderer.map_texture(&mapping)?);
Ok((data, size))
}
match self {
BackendData::Winit(winit) => capture(None, winit.backend.renderer(), output, state),
BackendData::X11(x11) => capture(None, &mut x11.renderer, output, state),
BackendData::Kms(kms) => {
let node = kms
.target_node_for_output(output)
.unwrap_or(kms.primary)
.node_with_type(NodeType::Render)
.with_context(|| "Unable to find node")??;
capture(
Some(node),
&mut kms.api.renderer::<Gles2Renderbuffer>(&node, &node)?,
output,
state,
)
}
BackendData::Unset => unreachable!(),
}
}
}
impl State {
@ -293,31 +209,31 @@ impl State {
signal: LoopSignal,
log: LogState,
) -> State {
let clock = Clock::new().expect("Failed to initialize clock");
let config = Config::load();
let compositor_state = CompositorState::new::<Self, _>(dh, None);
let data_device_state = DataDeviceState::new::<Self, _>(dh, None);
let dmabuf_state = DmabufState::new();
let export_dmabuf_state = ExportDmabufState::new::<Self, _>(
dh,
//|client| client.get_data::<ClientState>().unwrap().privileged,
|_| true,
);
let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::<Self>(dh);
let output_state = OutputManagerState::new_with_xdg_output::<Self>(dh);
let output_configuration_state = OutputConfigurationState::new(dh, |_| true);
let presentation_state = PresentationState::new::<Self>(dh, clock.id() as u32);
let primary_selection_state = PrimarySelectionState::new::<Self, _>(dh, None);
let shm_state = ShmState::new::<Self, _>(dh, vec![], None);
let mut seat_state = SeatState::<Self>::new();
let screencopy_state = ScreencopyState::new::<Self, _, _>(
dh,
vec![CursorMode::Embedded, CursorMode::Hidden],
|_| true,
); // TODO: privileged
let shm_state = ShmState::new::<Self, _>(
dh,
vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888],
None,
);
let seat_state = SeatState::<Self>::new();
let viewporter_state = ViewporterState::new::<Self, _>(dh, None);
let wl_drm_state = WlDrmState;
let shell = Shell::new(&config, dh);
let initial_seat = crate::input::add_seat(dh, &mut seat_state, &config, "seat-0".into());
#[cfg(not(feature = "debug"))]
let dirty_flag = Arc::new(AtomicBool::new(false));
#[cfg(feature = "debug")]
let dirty_flag = log.dirty_flag.clone();
State {
common: Common {
@ -328,38 +244,27 @@ impl State {
event_loop_signal: signal,
shell,
dirty_flag,
seats: vec![initial_seat.clone()],
last_active_seat: initial_seat,
seats: Vec::new(),
last_active_seat: None,
start_time: Instant::now(),
clock,
should_stop: false,
log,
#[cfg(feature = "debug")]
egui: Egui {
debug_state: smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous),
log_state: {
let mut state =
smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous);
state.set_zindex(0);
state
},
modifiers: Default::default(),
active: false,
alpha: 1.0,
},
egui: Egui { active: false },
compositor_state,
data_device_state,
dmabuf_state,
export_dmabuf_state,
screencopy_state,
shm_state,
seat_state,
keyboard_shortcuts_inhibit_state,
output_state,
output_configuration_state,
presentation_state,
primary_selection_state,
viewporter_state,
wl_drm_state,
@ -376,10 +281,9 @@ impl State {
match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) {
Ok(val) if val == "y" || val == "yes" || val == "true" => Some(
kms_state
.target_node_for_output(&active_output(
&self.common.last_active_seat,
&self.common,
))
.target_node_for_output(
&self.common.last_active_seat().active_output(),
)
.unwrap_or(kms_state.primary),
),
_ => Some(kms_state.primary),
@ -415,49 +319,275 @@ impl State {
}
}
#[cfg(feature = "debug")]
impl Fps {
const WINDOW_SIZE: usize = 100;
pub fn start(&mut self) {
self.start = Instant::now();
impl Common {
pub fn add_seat(&mut self, seat: Seat<State>) {
if self.seats.is_empty() {
self.last_active_seat = Some(seat.clone());
}
self.seats.push(seat);
}
pub fn end(&mut self) {
let frame_time = Instant::now().duration_since(self.start);
self.frames.push_back((self.start, frame_time));
if self.frames.len() > Fps::WINDOW_SIZE {
self.frames.pop_front();
pub fn remove_seat(&mut self, seat: &Seat<State>) {
self.seats.retain(|s| s != seat);
if self.seats.is_empty() {
self.last_active_seat = None;
} else if self.last_active_seat() == seat {
self.last_active_seat = Some(self.seats[0].clone());
}
}
pub fn max_frametime(&self) -> &Duration {
self.frames
.iter()
.map(|(_, f)| f)
.max()
.unwrap_or(&Duration::ZERO)
pub fn seats(&self) -> impl Iterator<Item = &Seat<State>> {
self.seats.iter()
}
pub fn min_frametime(&self) -> &Duration {
pub fn last_active_seat(&self) -> &Seat<State> {
self.last_active_seat.as_ref().expect("No seat?")
}
pub fn send_frames(&self, output: &Output, render_element_states: &RenderElementStates) {
let time = self.clock.now();
let throttle = Some(Duration::from_secs(1));
let active = self.shell.active_space(output);
active.mapped().for_each(|mapped| {
if active.outputs_for_element(mapped).any(|o| &o == output) {
let window = mapped.active_window();
window.with_surfaces(|surface, states| {
update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
default_primary_scanout_output_compare,
)
});
window.send_frame(output, time, throttle, surface_primary_scanout_output);
}
});
for space in self
.shell
.workspaces
.spaces()
.filter(|w| w.handle != active.handle)
{
space.mapped().for_each(|mapped| {
if space.outputs_for_element(mapped).any(|o| &o == output) {
let window = mapped.active_window();
window.send_frame(output, time, throttle, |_, _| None);
}
});
}
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.with_surfaces(|surface, states| {
update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
default_primary_scanout_output_compare,
)
});
layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output);
}
}
pub fn take_presentation_feedback(
&self,
output: &Output,
render_element_states: &RenderElementStates,
) -> OutputPresentationFeedback {
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
let active = self.shell.active_space(output);
active.mapped().for_each(|mapped| {
mapped.active_window().take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(surface, render_element_states)
},
);
});
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(surface, render_element_states)
},
);
}
output_presentation_feedback
}
}
#[cfg(feature = "debug")]
pub struct Egui {
pub active: bool,
}
pub struct Fps {
#[cfg(feature = "debug")]
pub state: smithay_egui::EguiState,
pending_frame: Option<PendingFrame>,
pub frames: VecDeque<Frame>,
}
#[derive(Debug)]
struct PendingFrame {
start: Instant,
duration_elements: Option<Duration>,
duration_render: Option<Duration>,
duration_screencopy: Option<Duration>,
duration_displayed: Option<Duration>,
}
#[derive(Debug)]
pub struct Frame {
pub start: Instant,
pub duration_elements: Duration,
pub duration_render: Duration,
pub duration_screencopy: Option<Duration>,
pub duration_displayed: Duration,
}
impl Frame {
fn render_time(&self) -> Duration {
self.duration_elements + self.duration_render
}
fn frame_time(&self) -> Duration {
self.duration_elements
+ self.duration_render
+ self.duration_screencopy.clone().unwrap_or(Duration::ZERO)
}
fn time_to_display(&self) -> Duration {
self.duration_elements
+ self.duration_render
+ self.duration_screencopy.clone().unwrap_or(Duration::ZERO)
+ self.duration_displayed
}
}
impl From<PendingFrame> for Frame {
fn from(pending: PendingFrame) -> Self {
Frame {
start: pending.start,
duration_elements: pending.duration_elements.unwrap_or(Duration::ZERO),
duration_render: pending.duration_render.unwrap_or(Duration::ZERO),
duration_screencopy: pending.duration_screencopy,
duration_displayed: pending.duration_displayed.unwrap_or(Duration::ZERO),
}
}
}
impl Fps {
const WINDOW_SIZE: usize = 360;
pub fn start(&mut self) {
self.pending_frame = Some(PendingFrame {
start: Instant::now(),
duration_elements: None,
duration_render: None,
duration_screencopy: None,
duration_displayed: None,
});
}
pub fn elements(&mut self) {
if let Some(frame) = self.pending_frame.as_mut() {
frame.duration_elements = Some(Instant::now().duration_since(frame.start));
}
}
pub fn render(&mut self) {
if let Some(frame) = self.pending_frame.as_mut() {
frame.duration_render = Some(
Instant::now().duration_since(frame.start)
- frame.duration_elements.clone().unwrap_or(Duration::ZERO),
);
}
}
pub fn screencopy(&mut self) {
if let Some(frame) = self.pending_frame.as_mut() {
frame.duration_screencopy = Some(
Instant::now().duration_since(frame.start)
- frame.duration_elements.clone().unwrap_or(Duration::ZERO)
- frame.duration_render.clone().unwrap_or(Duration::ZERO),
);
}
}
pub fn displayed(&mut self) {
if let Some(mut frame) = self.pending_frame.take() {
frame.duration_displayed = Some(
Instant::now().duration_since(frame.start)
- frame.duration_elements.clone().unwrap_or(Duration::ZERO)
- frame.duration_render.clone().unwrap_or(Duration::ZERO)
- frame.duration_screencopy.clone().unwrap_or(Duration::ZERO),
);
self.frames.push_back(frame.into());
while self.frames.len() > Fps::WINDOW_SIZE {
self.frames.pop_front();
}
}
}
pub fn max_frametime(&self) -> Duration {
self.frames
.iter()
.map(|(_, f)| f)
.map(|f| f.frame_time())
.max()
.unwrap_or(Duration::ZERO)
}
pub fn min_frametime(&self) -> Duration {
self.frames
.iter()
.map(|f| f.frame_time())
.min()
.unwrap_or(&Duration::ZERO)
.unwrap_or(Duration::ZERO)
}
pub fn max_time_to_display(&self) -> Duration {
self.frames
.iter()
.map(|f| f.time_to_display())
.max()
.unwrap_or(Duration::ZERO)
}
pub fn min_time_to_display(&self) -> Duration {
self.frames
.iter()
.map(|f| f.time_to_display())
.min()
.unwrap_or(Duration::ZERO)
}
pub fn avg_frametime(&self) -> Duration {
if self.frames.is_empty() {
return Duration::ZERO;
}
self.frames.iter().map(|f| f.frame_time()).sum::<Duration>() / (self.frames.len() as u32)
}
pub fn avg_rendertime(&self, window: usize) -> Duration {
self.frames
.iter()
.map(|(_, f)| f)
.cloned()
.take(window)
.map(|f| f.render_time())
.sum::<Duration>()
/ (self.frames.len() as u32)
/ window as u32
}
pub fn avg_fps(&self) -> f64 {
@ -465,7 +595,9 @@ impl Fps {
return 0.0;
}
let secs = match (self.frames.front(), self.frames.back()) {
(Some((start, _)), Some((end, dur))) => end.duration_since(*start) + *dur,
(Some(Frame { start, .. }), Some(end_frame)) => {
end_frame.start.duration_since(*start) + end_frame.frame_time()
}
_ => Duration::ZERO,
}
.as_secs_f64();
@ -474,25 +606,44 @@ impl Fps {
}
#[cfg(feature = "debug")]
impl Default for Fps {
fn default() -> Fps {
static INTEL_LOGO: &'static [u8] = include_bytes!("../resources/icons/intel.svg");
#[cfg(feature = "debug")]
static AMD_LOGO: &'static [u8] = include_bytes!("../resources/icons/amd.svg");
#[cfg(feature = "debug")]
static NVIDIA_LOGO: &'static [u8] = include_bytes!("../resources/icons/nvidia.svg");
impl Fps {
pub fn new(_renderer: &mut GlowRenderer) -> Fps {
#[cfg(feature = "debug")]
let state = {
let state = smithay_egui::EguiState::new(smithay::utils::Rectangle::from_loc_and_size(
(0, 0),
(400, 800),
));
let mut visuals: egui::style::Visuals = Default::default();
visuals.window_shadow.extrusion = 0.0;
state.context().set_visuals(visuals);
state
.load_svg(_renderer, String::from("intel"), INTEL_LOGO)
.unwrap();
state
.load_svg(_renderer, String::from("amd"), AMD_LOGO)
.unwrap();
state
.load_svg(_renderer, String::from("nvidia"), NVIDIA_LOGO)
.unwrap();
state
};
Fps {
state: {
let mut state = smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous);
let mut visuals: egui::style::Visuals = Default::default();
visuals.window_shadow.extrusion = 0.0;
state.context().set_visuals(visuals);
state.set_zindex(110); // always render on top
state
},
modifiers: Default::default(),
#[cfg(feature = "debug")]
state,
pending_frame: None,
frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1),
start: Instant::now(),
}
}
}
#[cfg(feature = "debug")]
pub fn avg_fps<'a>(iter: impl Iterator<Item = &'a Duration>) -> f64 {
let sum_secs = iter.map(|d| d.as_secs_f64()).sum::<f64>();
1.0 / (sum_secs / Fps::WINDOW_SIZE as f64)

View file

@ -1,10 +1,19 @@
use crate::input::{ActiveOutput, SeatId};
use smithay::{
input::Seat,
output::Output,
utils::{Logical, Rectangle, Transform},
use std::{cell::RefCell, sync::Mutex, time::Duration};
use crate::{
backend::render::cursor::CursorState,
input::{ActiveOutput, SeatId},
};
use smithay::{
desktop::utils::bbox_from_surface_tree,
input::{
pointer::{CursorImageAttributes, CursorImageStatus},
Seat,
},
output::Output,
utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Time, Transform},
wayland::compositor::with_states,
};
use std::cell::RefCell;
pub use crate::shell::{Shell, Workspace};
pub use crate::state::{Common, State};
@ -32,38 +41,93 @@ impl OutputExt for Output {
pub trait SeatExt {
fn id(&self) -> usize;
fn active_output(&self) -> Output;
fn set_active_output(&self, output: &Output);
fn cursor_geometry(
&self,
loc: impl Into<Point<f64, Buffer>>,
time: Time<Monotonic>,
) -> Option<(Rectangle<i32, Buffer>, Point<i32, Buffer>)>;
}
impl SeatExt for Seat<State> {
fn id(&self) -> usize {
self.user_data().get::<SeatId>().unwrap().0
}
}
pub fn active_output(seat: &Seat<State>, state: &Common) -> Output {
seat.user_data()
.get::<ActiveOutput>()
.map(|x| x.0.borrow().clone())
.unwrap_or_else(|| {
state
.shell
.outputs()
.next()
.cloned()
.expect("Backend has no outputs?")
})
}
fn active_output(&self) -> Output {
self.user_data()
.get::<ActiveOutput>()
.map(|x| x.0.borrow().clone())
.unwrap()
}
pub fn set_active_output(seat: &Seat<State>, output: &Output) {
if !seat
.user_data()
.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone())))
{
*seat
fn set_active_output(&self, output: &Output) {
*self
.user_data()
.get::<ActiveOutput>()
.unwrap()
.0
.borrow_mut() = output.clone();
}
fn cursor_geometry(
&self,
loc: impl Into<Point<f64, Buffer>>,
time: Time<Monotonic>,
) -> Option<(Rectangle<i32, Buffer>, Point<i32, Buffer>)> {
let location = loc.into().to_i32_round();
let cursor_status = self
.user_data()
.get::<RefCell<CursorImageStatus>>()
.map(|cell| {
let mut cursor_status = cell.borrow_mut();
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
if !surface.alive() {
*cursor_status = CursorImageStatus::Default;
}
}
cursor_status.clone()
})
.unwrap_or(CursorImageStatus::Default);
match cursor_status {
CursorImageStatus::Surface(surface) => {
let hotspot = with_states(&surface, |states| {
states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.unwrap()
.lock()
.unwrap()
.hotspot
});
let geo = bbox_from_surface_tree(&surface, (location.x, location.y));
let buffer_geo = Rectangle::from_loc_and_size(
(geo.loc.x, geo.loc.y),
geo.size.to_buffer(1, Transform::Normal),
);
Some((buffer_geo, (hotspot.x, hotspot.y).into()))
}
CursorImageStatus::Default => {
let seat_userdata = self.user_data();
seat_userdata.insert_if_missing(CursorState::default);
let state = seat_userdata.get::<CursorState>().unwrap();
let frame = state
.cursor
.get_image(1, Into::<Duration>::into(time).as_millis() as u32);
Some((
Rectangle::from_loc_and_size(
location,
(frame.width as i32, frame.height as i32),
),
(frame.xhot as i32, frame.yhot as i32).into(),
))
}
CursorImageStatus::Hidden => None,
}
}
}

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{state::BackendData, utils::prelude::*};
use crate::{state::BackendData, utils::prelude::*, wayland::protocols::screencopy::SessionType};
use smithay::{
backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state},
delegate_compositor,
@ -18,11 +18,13 @@ use smithay::{
};
use std::sync::Mutex;
use super::screencopy::PendingScreencopyBuffers;
impl State {
fn early_import_surface(&mut self, surface: &WlSurface) {
let mut import_nodes = std::collections::HashSet::new();
let dh = &self.common.display_handle;
for output in self.common.shell.outputs_for_surface(&surface) {
for output in self.common.shell.visible_outputs_for_surface(&surface) {
if let BackendData::Kms(ref mut kms_state) = &mut self.backend {
if let Some(target) = kms_state.target_node_for_output(&output) {
if import_nodes.insert(target) {
@ -90,7 +92,7 @@ impl State {
if !initial_configure_sent {
// compute initial dimensions by mapping
Shell::map_layer(self, &surface);
// this will also send a configure
surface.layer_surface().send_configure();
}
initial_configure_sent
}
@ -121,7 +123,7 @@ impl CompositorHandler for State {
state.wl_buffer().is_some()
})
{
let output = active_output(&seat, &self.common);
let output = seat.active_output();
Shell::map_window(self, &window, &output);
} else {
return;
@ -152,46 +154,25 @@ impl CompositorHandler for State {
// If we would re-position the window inside the grab we would get a weird jittery animation.
// We only want to resize once the client has acknoledged & commited the new size,
// so we need to carefully track the state through different handlers.
if let Some((space, window)) =
self.common
.shell
.space_for_window_mut(surface)
.and_then(|workspace| {
workspace
.space
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
.cloned()
.map(|window| (&mut workspace.space, window))
})
{
let new_location =
crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_state(
&window,
space.window_location(&window).unwrap(),
window.geometry().size,
if let Some(element) = self.common.shell.element_for_surface(surface).cloned() {
if let Some(workspace) = self.common.shell.space_for_mut(&element) {
crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_to_location(
element.clone(),
workspace,
);
if let Some(location) = new_location {
space.map_window(
&window,
location,
crate::shell::layout::floating::FLOATING_INDEX,
true,
);
for window in space.windows() {
update_reactive_popups(space, window);
}
workspace.commit(surface);
}
}
//handle window screencopy sessions
self.schedule_window_session(surface);
// We need to know every potential output for importing to the right gpu and scheduling a render,
// so call this only after every potential surface map operation has been done.
self.early_import_surface(surface);
// and refresh smithays internal state
self.common.shell.popups.commit(surface);
for workspace in &self.common.shell.spaces {
workspace.space.commit(surface);
}
// re-arrange layer-surfaces (commits may change size and positioning)
if let Some(output) = self.common.shell.outputs().find(|o| {
@ -199,14 +180,37 @@ impl CompositorHandler for State {
map.layer_for_surface(surface, WindowSurfaceType::ALL)
.is_some()
}) {
let dh = &self.common.display_handle;
layer_map_for_output(output).arrange(dh);
layer_map_for_output(output).arrange();
}
let mut scheduled_sessions = self.schedule_workspace_sessions(surface);
// schedule a new render
for output in self.common.shell.outputs_for_surface(surface) {
self.backend
.schedule_render(&self.common.event_loop_handle, &output);
for output in self.common.shell.visible_outputs_for_surface(surface) {
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>() {
scheduled_sessions
.get_or_insert_with(Vec::new)
.extend(sessions.borrow_mut().drain(..));
}
self.backend.schedule_render(
&self.common.event_loop_handle,
&output,
scheduled_sessions.as_ref().map(|sessions| {
sessions
.iter()
.filter(|(s, _)| match s.session_type() {
SessionType::Output(o) | SessionType::Workspace(o, _)
if o == output =>
{
true
}
_ => false,
})
.cloned()
.collect::<Vec<_>>()
}),
);
}
}
}

View file

@ -1,424 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
use anyhow::{anyhow, Context, Result};
use std::{cell::RefCell, time::Instant};
use smithay::{
backend::{
drm::{DrmNode, NodeType},
egl::EGLDevice,
renderer::{
gles2::{Gles2Error, Gles2Renderbuffer, Gles2Renderer},
utils::with_renderer_surface_state,
Bind, ExportDma, ImportAll, Offscreen, Renderer,
},
},
desktop::{draw_window, draw_window_popups, space::RenderElement, Kind, Window},
input::pointer::CursorImageStatus,
output::Output,
reexports::wayland_server::{protocol::wl_output::WlOutput, DisplayHandle, Resource},
utils::{IsAlive, Size, Transform},
wayland::{
compositor::{get_children, with_states, SurfaceAttributes},
dmabuf::get_dmabuf,
},
};
use crate::{
backend::render::{
cursor::draw_cursor, render_output, render_workspace, AsGles2Renderer, CustomElem,
},
state::{BackendData, ClientState, Common},
utils::prelude::*,
wayland::protocols::{
export_dmabuf::{delegate_export_dmabuf, Capture, CaptureError, ExportDmabufHandler},
workspace::WorkspaceHandle,
},
};
impl ExportDmabufHandler for State {
fn capture_output(
&mut self,
_dh: &DisplayHandle,
output: WlOutput,
overlay_cursor: bool,
) -> Result<Capture, CaptureError> {
let output = Output::from_resource(&output)
.ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?;
let renderer = match self.backend {
BackendData::Kms(ref mut kms) => {
// the kms backend just keeps its dmabufs easily accessible for capture.
return kms
.capture_output(&output)
.map(|(device, dmabuf, presentation_time)| Capture {
device,
dmabuf,
presentation_time,
})
.ok_or(CaptureError::Temporary(
anyhow!("Surface not initialized yet").into(),
));
}
BackendData::Winit(ref mut winit) => winit.backend.renderer(),
BackendData::X11(ref mut x11) => &mut x11.renderer,
_ => unreachable!(),
};
let device = device_from_renderer(renderer)
.context("Failed to find DrmNode")
.map_err(|err| CaptureError::Permanent(err.into()))?;
let size = output
.geometry()
.size
.to_f64()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform().into(),
)
.to_i32_round();
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)
.context("Failed to create render buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
renderer
.bind(buffer)
.context("Failed to bind render buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
render_output(
None,
renderer,
0,
&mut self.common,
&output,
!overlay_cursor,
#[cfg(feature = "debug")]
None,
)
.context("Failed to render desktop for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
let dmabuf = renderer
.export_framebuffer(size)
.context("Failed to export buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
Ok(Capture {
device,
dmabuf,
presentation_time: Instant::now(),
})
}
fn capture_workspace(
&mut self,
_dh: &DisplayHandle,
workspace: WorkspaceHandle,
wl_output: WlOutput,
overlay_cursor: bool,
) -> Result<Capture, CaptureError> {
let output = Output::from_resource(&wl_output)
.ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?;
let workspace = self
.common
.shell
.spaces
.iter()
.find(|w| w.handle == workspace)
.ok_or(CaptureError::Permanent(anyhow!("Workspace is gone").into()))?
.idx;
if self.common.shell.active_space(&output).idx == workspace {
self.capture_output(_dh, wl_output, overlay_cursor)
} else {
match self.backend {
BackendData::Winit(ref mut winit) => {
let device = device_from_renderer(winit.backend.renderer())
.context("Failed to find DrmNode")
.map_err(|err| CaptureError::Permanent(err.into()))?;
capture_workspace(
device,
winit.backend.renderer(),
&output,
workspace,
&mut self.common,
)
}
BackendData::X11(ref mut x11) => {
let device = device_from_renderer(&x11.renderer)
.context("Failed to find DrmNode")
.map_err(|err| CaptureError::Permanent(err.into()))?;
capture_workspace(
device,
&mut x11.renderer,
&output,
workspace,
&mut self.common,
)
}
BackendData::Kms(ref mut kms) => {
let node = kms
.target_node_for_output(&output)
.unwrap_or(kms.primary)
.node_with_type(NodeType::Render)
.with_context(|| "Unable to find node")
.map_err(|x| CaptureError::Permanent(x.into()))?
.map_err(|x| CaptureError::Permanent(x.into()))?;
let mut renderer = kms
.api
.renderer::<Gles2Renderbuffer>(&node, &node)
.with_context(|| format!("Failed to optain renderer for {:?}", node))
.map_err(|x| CaptureError::Permanent(x.into()))?;
capture_workspace(node, &mut renderer, &output, workspace, &mut self.common)
}
BackendData::Unset => unreachable!(),
}
}
}
fn capture_toplevel(
&mut self,
dh: &DisplayHandle,
window: Window,
overlay_cursor: bool,
) -> Result<Capture, CaptureError> {
let Kind::Xdg(xdg) = window.toplevel();
let surface = xdg.wl_surface();
let window_transform = with_states(surface, |states| {
states
.cached_state
.current::<SurfaceAttributes>()
.buffer_transform
.into()
});
let workspace = self.common.shell.space_for_window(surface);
let pointers = if overlay_cursor && workspace.is_some() {
self.common
.seats
.iter()
.filter_map(|seat| {
let cursor_status = seat
.user_data()
.get::<RefCell<CursorImageStatus>>()
.map(|cell| {
let mut cursor_status = cell.borrow_mut();
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
if !surface.alive() {
*cursor_status = CursorImageStatus::Default;
}
}
cursor_status.clone()
})
.unwrap_or(CursorImageStatus::Default);
if cursor_status != CursorImageStatus::Hidden {
let workspace = workspace.as_deref()?;
let loc = seat.get_pointer().map(|ptr| ptr.current_location())?;
let output = active_output(seat, &self.common);
if self.common.shell.active_space(&output).idx == workspace.idx {
let relative = self
.common
.shell
.space_relative_output_geometry(loc, &output);
// unwrap is safe, because we got this workspace from `space_for_window`. It has to contain the window.
let bbox = workspace.space.window_bbox(&window).unwrap();
bbox.contains(relative.to_i32_round())
.then_some((seat, (relative - bbox.loc.to_f64()).to_i32_round()))
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>()
} else {
Vec::with_capacity(0)
};
let device = match self.backend {
BackendData::Winit(ref mut winit) => device_from_renderer(winit.backend.renderer()),
BackendData::X11(ref x11) => device_from_renderer(&x11.renderer),
BackendData::Kms(ref kms) => Ok(dh
.get_client(window.toplevel().wl_surface().id())
.ok()
.with_context(|| "Unable to find matching wayland client")
.map_err(|x| CaptureError::Permanent(x.into()))?
.get_data::<ClientState>()
.unwrap()
.drm_node
.clone()
.unwrap_or_else(|| kms.primary.clone())),
_ => unreachable!(),
}
.context("Failed to find DrmNode")
.map_err(|err| CaptureError::Permanent(err.into()))?;
// first lets check, if we can just send a dmabuf from the client directly
if pointers.is_empty()
&& window_transform == Transform::Normal
&& get_children(surface).is_empty()
&& self.common.shell.popups.find_popup(surface).is_none()
{
let dmabuf = with_renderer_surface_state(surface, |state| {
state.wl_buffer().and_then(|buf| get_dmabuf(buf).ok())
});
if let Some(dmabuf) = dmabuf {
return Ok(Capture {
device,
dmabuf,
presentation_time: std::time::Instant::now(),
});
}
}
// we need to composite
let mut _tmp_multirenderer = None;
let renderer = match self.backend {
BackendData::Winit(ref mut winit) => winit.backend.renderer(),
BackendData::X11(ref mut x11) => &mut x11.renderer,
BackendData::Kms(ref mut kms) => {
_tmp_multirenderer = Some(
kms.api
.renderer::<Gles2Renderbuffer>(&device, &device)
.with_context(|| format!("Failed to optain renderer for {:?}", device))
.map_err(|x| CaptureError::Permanent(x.into()))?,
);
_tmp_multirenderer.as_mut().unwrap().as_gles2()
}
BackendData::Unset => unreachable!(),
};
let bbox = window.bbox_with_popups();
let size = bbox.size + Size::from((-bbox.loc.x, -bbox.loc.y));
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(
renderer,
size.to_buffer(1, window_transform),
)
.context("Failed to create render buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
renderer
.bind(buffer)
.context("Failed to bind render buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
renderer
.render(size.to_physical(1), Transform::Normal, |renderer, frame| {
let log = slog_scope::logger();
let damage = &[window.physical_bbox_with_popups((0.0, 0.0), 1.0)];
draw_window(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?;
draw_window_popups(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?;
for (seat, loc) in pointers.into_iter() {
if let Some(cursor_elem) = draw_cursor::<_, CustomElem>(
renderer,
seat,
loc,
&self.common.start_time,
true,
) {
let damage = RenderElement::<Gles2Renderer>::accumulated_damage(
&cursor_elem,
1.0,
None,
);
cursor_elem.draw(
renderer,
frame,
1.0,
loc.to_physical(1.0),
&damage,
&log,
)?;
}
}
Result::<(), Gles2Error>::Ok(())
})
.context("Failed to render window for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?
.context("Failed to render window for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
let dmabuf = renderer
.export_framebuffer(size.to_buffer(1, window_transform))
.context("Failed to export buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
Ok(Capture {
device,
dmabuf,
presentation_time: Instant::now(),
})
}
fn start_time(&mut self) -> Instant {
self.common.start_time
}
}
fn capture_workspace<E, T, R>(
gpu: DrmNode,
renderer: &mut R,
output: &Output,
idx: u8,
state: &mut Common,
) -> Result<Capture, CaptureError>
where
E: std::error::Error + Send + Sync + 'static,
T: Clone + 'static,
R: Renderer<Error = E, TextureId = T>
+ ImportAll
+ AsGles2Renderer
+ Offscreen<Gles2Renderbuffer>
+ Bind<Gles2Renderbuffer>
+ ExportDma,
CustomElem: RenderElement<R>,
{
let size = output
.geometry()
.size
.to_f64()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform().into(),
)
.to_i32_round();
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)
.context("Failed to create render buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
renderer
.bind(buffer)
.context("Failed to bind render buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
render_workspace(
Some(&gpu),
renderer,
0,
state,
idx,
&output,
true,
#[cfg(feature = "debug")]
None,
)
.map_err(|err| anyhow!("Failed to render desktop for offscreen capture: {:?}", err)) // meh..
.map_err(|err| CaptureError::Temporary(err.into()))?;
let dmabuf = renderer
.export_framebuffer(size)
.context("Failed to export buffer for offscreen capture")
.map_err(|err| CaptureError::Temporary(err.into()))?;
Ok(Capture {
device: gpu,
dmabuf,
presentation_time: Instant::now(),
})
}
fn device_from_renderer(renderer: &Gles2Renderer) -> Result<DrmNode> {
EGLDevice::device_for_display(renderer.egl_context().display())?
.try_get_render_node()?
.ok_or(anyhow!(
"No node associated with context (software context?)"
))
}
delegate_export_dmabuf!(State);

View file

@ -3,7 +3,7 @@
use crate::utils::prelude::*;
use smithay::{
delegate_layer_shell,
desktop::{LayerSurface, PopupKind},
desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurfaceType},
output::Output,
reexports::wayland_server::protocol::wl_output::WlOutput,
wayland::shell::{
@ -14,6 +14,8 @@ use smithay::{
},
};
use super::screencopy::PendingScreencopyBuffers;
impl WlrLayerShellHandler for State {
fn shell_state(&mut self) -> &mut WlrLayerShellState {
&mut self.common.shell.layer_shell_state
@ -26,12 +28,11 @@ impl WlrLayerShellHandler for State {
_layer: Layer,
namespace: String,
) {
super::mark_dirty_on_drop(&self.common, surface.wl_surface());
let seat = self.common.last_active_seat.clone();
let seat = self.common.last_active_seat().clone();
let output = wl_output
.as_ref()
.and_then(Output::from_resource)
.unwrap_or_else(|| active_output(&seat, &self.common));
.unwrap_or_else(|| seat.active_output());
self.common.shell.pending_layers.push((
LayerSurface::new(surface, namespace),
output,
@ -51,6 +52,44 @@ impl WlrLayerShellHandler for State {
.unwrap();
}
}
fn layer_destroyed(&mut self, surface: WlrLayerSurface) {
let maybe_output = self
.common
.shell
.outputs()
.find(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
.is_some()
})
.cloned();
if let Some(output) = maybe_output {
{
let mut map = layer_map_for_output(&output);
let layer = map
.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
.unwrap()
.clone();
map.unmap_layer(&layer);
}
// collect screencopy sessions needing an update
let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface());
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>() {
scheduled_sessions
.get_or_insert_with(Vec::new)
.extend(sessions.borrow_mut().drain(..));
}
self.backend.schedule_render(
&self.common.event_loop_handle,
&output,
scheduled_sessions,
);
}
}
}
delegate_layer_shell!(State);

View file

@ -4,12 +4,13 @@ pub mod buffer;
pub mod compositor;
pub mod data_device;
pub mod dmabuf;
pub mod export_dmabuf;
pub mod keyboard_shortcuts_inhibit;
pub mod layer_shell;
pub mod output;
pub mod output_configuration;
pub mod presentation;
pub mod primary_selection;
pub mod screencopy;
pub mod seat;
pub mod shm;
pub mod toplevel_info;
@ -18,28 +19,3 @@ pub mod viewporter;
pub mod wl_drm;
pub mod workspace;
pub mod xdg_shell;
use crate::state::Common;
use smithay::{
reexports::wayland_server::protocol::wl_surface::WlSurface,
wayland::compositor::{add_destruction_hook, with_states},
};
fn mark_dirty_on_drop(state: &Common, wl_surface: &WlSurface) {
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
let dirty = state.dirty_flag.clone();
struct DirtyFlag(Arc<AtomicBool>);
with_states(wl_surface, |data| {
data.data_map.insert_if_missing(|| DirtyFlag(dirty));
});
add_destruction_hook(wl_surface, |data| {
if let Some(DirtyFlag(dirty)) = data.data_map.get::<DirtyFlag>() {
dirty.store(true, Ordering::SeqCst);
}
})
}

View file

@ -81,10 +81,12 @@ impl State {
}
}
let seats = self.common.seats().cloned().collect::<Vec<_>>();
if let Err(err) = self.backend.apply_config_for_output(
output,
test_only,
&mut self.common.shell,
seats.iter().cloned(),
&self.common.event_loop_handle,
) {
slog_scope::warn!(
@ -106,6 +108,7 @@ impl State {
output,
false,
&mut self.common.shell,
seats.iter().cloned(),
&self.common.event_loop_handle,
) {
slog_scope::error!(

View file

@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::State;
use smithay::delegate_presentation;
delegate_presentation!(State);

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::State;
use crate::{
shell::focus::target::{KeyboardFocusTarget, PointerFocusTarget},
state::State,
};
use smithay::{
delegate_seat,
input::{pointer::CursorImageStatus, SeatHandler, SeatState},
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource},
wayland::{data_device::set_data_device_focus, primary_selection::set_primary_focus},
reexports::wayland_server::Resource,
wayland::{
data_device::set_data_device_focus, primary_selection::set_primary_focus,
seat::WaylandFocus,
},
};
use std::cell::RefCell;
impl SeatHandler for State {
type KeyboardFocus = WlSurface;
type PointerFocus = WlSurface;
type KeyboardFocus = KeyboardFocusTarget;
type PointerFocus = PointerFocusTarget;
fn seat_state(&mut self) -> &mut SeatState<Self> {
&mut self.common.seat_state
@ -35,10 +41,12 @@ impl SeatHandler for State {
focused: Option<&Self::KeyboardFocus>,
) {
let dh = &self.common.display_handle;
if let Some(client) = focused.and_then(|s| dh.get_client(s.id()).ok()) {
set_data_device_focus(dh, seat, Some(client));
let client2 = focused.and_then(|s| dh.get_client(s.id()).ok()).unwrap();
set_primary_focus(dh, seat, Some(client2))
if let Some(client) = focused
.and_then(|t| t.wl_surface())
.and_then(|s| dh.get_client(s.id()).ok())
{
set_data_device_focus(dh, seat, Some(client.clone()));
set_primary_focus(dh, seat, Some(client))
}
}
}

View file

@ -19,18 +19,35 @@ impl ToplevelManagementHandler for State {
}
fn activate(&mut self, _dh: &DisplayHandle, window: &Window, seat: Option<Seat<Self>>) {
if let Some(idx) = self
for output in self
.common
.shell
.space_for_window(window.toplevel().wl_surface())
.map(|w| w.idx)
.outputs()
.cloned()
.collect::<Vec<_>>()
.iter()
{
let seat = seat.unwrap_or(self.common.last_active_seat.clone());
let output = active_output(&seat, &self.common);
if self.common.shell.active_space(&output).idx != idx {
self.common.shell.activate(&seat, &output, idx as usize);
let maybe = self
.common
.shell
.workspaces
.spaces_for_output(output)
.enumerate()
.find(|(_, w)| w.windows().any(|w| &w == window));
if let Some((idx, workspace)) = maybe {
let seat = seat.unwrap_or(self.common.last_active_seat().clone());
let mapped = workspace
.mapped()
.find(|m| m.windows().any(|(w, _)| &w == window))
.unwrap()
.clone();
std::mem::drop(workspace);
self.common.shell.activate(&output, idx as usize);
mapped.focus_window(window);
Common::set_focus(self, Some(&mapped.clone().into()), &seat, None);
return;
}
Common::set_focus(self, Some(window.toplevel().wl_surface()), &seat, None);
}
}

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
shell::WorkspaceMode,
state::ClientState,
utils::prelude::*,
wayland::protocols::workspace::{
@ -29,16 +30,22 @@ impl WorkspaceHandler for State {
for request in requests.into_iter() {
match request {
Request::Activate(handle) => {
if let Some(idx) = self
.common
.shell
.spaces
.iter()
.position(|w| w.handle == handle)
{
let seat = &self.common.last_active_seat;
let output = active_output(seat, &self.common);
self.common.shell.activate(seat, &output, idx);
let maybe = match &self.common.shell.workspaces {
WorkspaceMode::Global(set) => set
.workspaces
.iter()
.position(|w| w.handle == handle)
.map(|i| (self.common.last_active_seat().active_output(), i)),
WorkspaceMode::OutputBound(sets, _) => sets.iter().find_map(|(o, set)| {
set.workspaces
.iter()
.position(|w| w.handle == handle)
.map(|i| (o.clone(), i))
}),
};
if let Some((output, idx)) = maybe {
self.common.shell.activate(&output, idx);
}
}
_ => {}

View file

@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::utils::prelude::*;
use crate::{utils::prelude::*, wayland::protocols::screencopy::SessionType};
use smithay::{
delegate_xdg_shell,
desktop::{
find_popup_root_surface, Kind, PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab,
PopupUngrabStrategy, Window, WindowSurfaceType,
PopupUngrabStrategy, Window,
},
input::{
pointer::{Focus, GrabStartData as PointerGrabStartData},
@ -17,12 +17,17 @@ use smithay::{
wayland_server::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
},
utils::Serial,
wayland::shell::xdg::{
Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
wayland::{
seat::WaylandFocus,
shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
},
},
};
use std::cell::Cell;
use super::screencopy::PendingScreencopyBuffers;
pub mod popup;
pub type PopupGrabData = Cell<Option<PopupGrab<State>>>;
@ -33,21 +38,14 @@ impl XdgShellHandler for State {
}
fn new_toplevel(&mut self, surface: ToplevelSurface) {
super::mark_dirty_on_drop(&self.common, surface.wl_surface());
let seat = &self.common.last_active_seat;
let seat = self.common.last_active_seat().clone();
let window = Window::new(Kind::Xdg(surface));
self.common.shell.toplevel_info_state.new_toplevel(&window);
self.common
.shell
.pending_windows
.push((window, seat.clone()));
self.common.shell.pending_windows.push((window, seat));
// We will position the window after the first commit, when we know its size hints
}
fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
super::mark_dirty_on_drop(&self.common, surface.wl_surface());
surface.with_pending_state(|state| {
state.geometry = positioner.get_geometry();
state.positioner = positioner;
@ -67,35 +65,19 @@ impl XdgShellHandler for State {
}
}
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
if let Configure::Toplevel(configure) = configure {
// If we would re-position the window inside the grab we would get a weird jittery animation.
// We only want to resize once the client has acknoledged & commited the new size,
// so we need to carefully track the state through different handlers.
if let Some(window) =
self.common
.shell
.space_for_window(&surface)
.and_then(|workspace| {
workspace
.space
.window_for_surface(&surface, WindowSurfaceType::TOPLEVEL)
})
{
crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure(window, configure)
}
}
}
fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) {
let seat = Seat::from_resource(&seat).unwrap();
let kind = PopupKind::Xdg(surface);
if let Ok(root) = find_popup_root_surface(&kind) {
if let Some(root) = find_popup_root_surface(&kind)
.ok()
.and_then(|root| self.common.shell.element_for_surface(&root))
{
let target = root.clone().into();
let ret = self
.common
.shell
.popups
.grab_popup(root, kind, &seat, serial);
.grab_popup(target, kind, &seat, serial);
if let Ok(mut grab) = ret {
if let Some(keyboard) = seat.get_keyboard() {
@ -157,18 +139,36 @@ impl XdgShellHandler for State {
fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) {
let seat = Seat::from_resource(&seat).unwrap();
if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) {
let workspace = self
if let Some(mapped) = self
.common
.shell
.space_for_window_mut(surface.wl_surface())
.unwrap();
let window = workspace
.space
.window_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
.unwrap()
.clone();
Shell::move_request(self, &window, &seat, serial, start_data);
.element_for_surface(surface.wl_surface())
.cloned()
{
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let output = seat.active_output();
let (window, _) = mapped
.windows()
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
.unwrap();
if let Some(grab) =
workspace.move_request(&window, &seat, &output, serial, start_data)
{
let handle = workspace.handle;
self.common
.shell
.toplevel_info_state
.toplevel_leave_workspace(&window, &handle);
self.common
.shell
.toplevel_info_state
.toplevel_leave_output(&window, &output);
seat.get_pointer()
.unwrap()
.set_grab(self, grab, serial, Focus::Clear);
}
}
}
}
}
@ -181,35 +181,59 @@ impl XdgShellHandler for State {
) {
let seat = Seat::from_resource(&seat).unwrap();
if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) {
Workspace::resize_request(self, surface.wl_surface(), &seat, serial, start_data, edges);
if let Some(mapped) = self
.common
.shell
.element_for_surface(surface.wl_surface())
.cloned()
{
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
if let Some(grab) =
workspace.resize_request(&mapped, &seat, serial, start_data, edges)
{
seat.get_pointer()
.unwrap()
.set_grab(self, grab, serial, Focus::Clear);
}
}
}
}
}
fn maximize_request(&mut self, surface: ToplevelSurface) {
let surface = surface.wl_surface();
let seat = &self.common.last_active_seat;
let output = active_output(seat, &self.common);
let seat = self.common.last_active_seat();
let output = seat.active_output();
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
let window = workspace
.space
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
.unwrap()
.clone();
workspace.maximize_request(&window, &output)
if let Some(mapped) = self
.common
.shell
.element_for_surface(surface.wl_surface())
.cloned()
{
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped
.windows()
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
.unwrap();
workspace.maximize_request(&window, &output)
}
}
}
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
let surface = surface.wl_surface();
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
let window = workspace
.space
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
.unwrap()
.clone();
workspace.unmaximize_request(&window)
if let Some(mapped) = self
.common
.shell
.element_for_surface(surface.wl_surface())
.cloned()
{
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped
.windows()
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
.unwrap();
workspace.unmaximize_request(&window)
}
}
}
@ -218,30 +242,79 @@ impl XdgShellHandler for State {
.as_ref()
.and_then(Output::from_resource)
.unwrap_or_else(|| {
let seat = &self.common.last_active_seat;
active_output(seat, &self.common)
let seat = self.common.last_active_seat();
seat.active_output()
});
let surface = surface.wl_surface();
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
let window = workspace
.space
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
.unwrap()
.clone();
workspace.fullscreen_request(&window, &output)
if let Some(mapped) = self
.common
.shell
.element_for_surface(surface.wl_surface())
.cloned()
{
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped
.windows()
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
.unwrap();
workspace.fullscreen_request(&window, &output)
}
}
}
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
let surface = surface.wl_surface();
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
let window = workspace
.space
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
.unwrap()
.clone();
workspace.unfullscreen_request(&window)
if let Some(mapped) = self
.common
.shell
.element_for_surface(surface.wl_surface())
.cloned()
{
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped
.windows()
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
.unwrap();
workspace.unfullscreen_request(&window)
}
}
}
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
let outputs = self
.common
.shell
.visible_outputs_for_surface(surface.wl_surface())
.collect::<Vec<_>>();
for output in outputs.iter() {
self.common.shell.active_space_mut(output).refresh();
}
// screencopy
let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface());
for output in outputs.into_iter() {
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>() {
scheduled_sessions
.get_or_insert_with(Vec::new)
.extend(sessions.borrow_mut().drain(..));
}
self.backend.schedule_render(
&self.common.event_loop_handle,
&output,
scheduled_sessions.as_ref().map(|sessions| {
sessions
.iter()
.filter(|(s, _)| match s.session_type() {
SessionType::Output(o) | SessionType::Workspace(o, _)
if o == output =>
{
true
}
_ => false,
})
.cloned()
.collect::<Vec<_>>()
}),
);
}
}
}
@ -270,7 +343,6 @@ fn check_grab_preconditions(
.as_ref()
.unwrap()
.0
.id()
.same_client_as(&surface.id())
{
return None;

View file

@ -3,8 +3,7 @@
use crate::{shell::Shell, utils::prelude::*};
use smithay::{
desktop::{
layer_map_for_output, LayerSurface, PopupKind, PopupManager, Space, Window,
WindowSurfaceType,
layer_map_for_output, LayerSurface, PopupKind, PopupManager, Window, WindowSurfaceType,
},
output::Output,
reexports::{
@ -27,12 +26,19 @@ use std::sync::Mutex;
impl Shell {
pub fn unconstrain_popup(&self, surface: &PopupSurface, positioner: &PositionerState) {
if let Some(parent) = get_popup_toplevel(&surface) {
if let Some(workspace) = self.space_for_window(&parent) {
let window = workspace
.space
.window_for_surface(&parent, WindowSurfaceType::ALL)
if let Some(elem) = self.element_for_surface(&parent) {
let workspace = self.space_for(elem).unwrap();
let element_loc = workspace.element_geometry(elem).unwrap().loc;
let (window, offset) = elem
.windows()
.find(|(w, _)| w.toplevel().wl_surface() == &parent)
.unwrap();
unconstrain_xdg_popup(surface, positioner, &workspace.space, window);
let window_geo_offset = window.geometry().loc;
let window_loc = element_loc + offset + window_geo_offset;
let anchor_point = get_anchor_point(&positioner) + window_loc;
if let Some(output) = workspace.output_under(anchor_point) {
unconstrain_xdg_popup(surface, positioner, window_loc, output.geometry());
}
} else if let Some((output, layer_surface)) = self.outputs().find_map(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(&parent, WindowSurfaceType::ALL)
@ -42,15 +48,14 @@ impl Shell {
}
}
}
pub fn update_reactive_popups(&self, window: &Window) {
if let Some(workspace) = self.space_for_window(window.toplevel().wl_surface()) {
update_reactive_popups(&workspace.space, window);
}
}
}
pub fn update_reactive_popups(space: &Space, window: &Window) {
pub fn update_reactive_popups<'a>(
window: &Window,
loc: Point<i32, Logical>,
outputs: impl Iterator<Item = &'a Output>,
) {
let output_geo = outputs.map(|o| o.geometry()).collect::<Vec<_>>();
for (popup, _) in PopupManager::popups_for_surface(window.toplevel().wl_surface()) {
match popup {
PopupKind::Xdg(surface) => {
@ -64,12 +69,19 @@ pub fn update_reactive_popups(space: &Space, window: &Window) {
attributes.current.positioner.clone()
});
if positioner.reactive {
unconstrain_xdg_popup(&surface, &positioner, space, window);
if let Err(err) = surface.send_configure() {
slog_scope::warn!(
"Compositor bug: Unable to re-configure reactive popup: {}",
err
);
let anchor_point = get_anchor_point(&positioner) + loc;
if let Some(rect) = output_geo
.iter()
.find(|geo| geo.contains(anchor_point))
.copied()
{
unconstrain_xdg_popup(&surface, &positioner, loc, rect);
if let Err(err) = surface.send_configure() {
slog_scope::warn!(
"Compositor bug: Unable to re-configure reactive popup: {}",
err
);
}
}
}
}
@ -80,32 +92,18 @@ pub fn update_reactive_popups(space: &Space, window: &Window) {
fn unconstrain_xdg_popup(
surface: &PopupSurface,
positioner: &PositionerState,
space: &Space,
window: &Window,
window_loc: Point<i32, Logical>,
rect: Rectangle<i32, Logical>,
) {
let anchor_point = get_anchor_point(&positioner) + space.window_location(&window).unwrap();
if let Some(output_rect) = space
.outputs_for_window(window)
.into_iter()
.find(|o| {
space
.output_geometry(o)
.map(|rect| rect.contains(anchor_point))
.unwrap_or(false)
})
.map(|o| space.output_geometry(&o).unwrap())
{
// the output_rect represented relative to the parents coordinate system
let mut relative = output_rect;
relative.loc -= space.window_location(&window).unwrap();
let offset = check_constrained(&surface, positioner.get_geometry(), relative);
let mut relative = rect;
relative.loc -= window_loc;
let offset = check_constrained(&surface, positioner.get_geometry(), relative);
if offset.x != 0 || offset.y != 0 {
slog_scope::debug!("Unconstraining popup: {:?}", surface);
if !unconstrain_flip(&surface, &positioner, relative) {
if !unconstrain_slide(&surface, &positioner, relative) {
unconstrain_resize(&surface, &positioner, relative);
}
if offset.x != 0 || offset.y != 0 {
slog_scope::debug!("Unconstraining popup: {:?}", surface);
if !unconstrain_flip(&surface, &positioner, relative) {
if !unconstrain_slide(&surface, &positioner, relative) {
unconstrain_resize(&surface, &positioner, relative);
}
}
}
@ -319,7 +317,7 @@ fn get_anchor_point(positioner: &PositionerState) -> Point<i32, Logical> {
.into()
}
fn get_popup_toplevel(popup: &PopupSurface) -> Option<WlSurface> {
pub fn get_popup_toplevel(popup: &PopupSurface) -> Option<WlSurface> {
let mut parent = popup.get_parent_surface()?;
while get_role(&parent) == Some(XDG_POPUP_ROLE) {
parent = with_states(&parent, |states| {

View file

@ -1,308 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::{
backend::{
allocator::{dmabuf::Dmabuf, Buffer},
drm::DrmNode,
},
desktop::Window,
reexports::wayland_server::{
self, backend::GlobalId, protocol::wl_output::WlOutput, Client, Dispatch, DisplayHandle,
GlobalDispatch,
},
};
use std::{
fs::File,
io::{Seek, SeekFrom},
os::unix::io::{AsRawFd, FromRawFd, IntoRawFd},
time::Instant,
};
use cosmic_protocols::export_dmabuf::v1::server::{
zcosmic_export_dmabuf_frame_v1::{self, CancelReason, Flags, ZcosmicExportDmabufFrameV1},
zcosmic_export_dmabuf_manager_v1::{self, ZcosmicExportDmabufManagerV1},
};
use crate::wayland::protocols::{
toplevel_info::{window_from_handle, ToplevelInfoHandler},
workspace::{WorkspaceHandle, WorkspaceHandler},
};
/// Export Dmabuf global state
#[derive(Debug)]
pub struct ExportDmabufState {
global: GlobalId,
}
pub struct ExportDmabufGlobalData {
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
}
impl ExportDmabufState {
/// Create a new dmabuf global
pub fn new<D, F>(display: &DisplayHandle, client_filter: F) -> ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler
+ 'static,
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
{
ExportDmabufState {
global: display.create_global::<D, ZcosmicExportDmabufManagerV1, _>(
1,
ExportDmabufGlobalData {
filter: Box::new(client_filter),
},
),
}
}
/// Returns the export dmabuf global.
pub fn global(&self) -> GlobalId {
self.global.clone()
}
}
pub enum CaptureError {
Temporary(Box<dyn std::error::Error>),
Permanent(Box<dyn std::error::Error>),
Resizing,
}
pub struct Capture {
pub device: DrmNode,
pub dmabuf: Dmabuf,
pub presentation_time: Instant,
}
pub trait ExportDmabufHandler {
fn capture_output(
&mut self,
dh: &DisplayHandle,
output: WlOutput,
overlay_cursor: bool,
) -> Result<Capture, CaptureError>;
fn capture_workspace(
&mut self,
dh: &DisplayHandle,
workspace: WorkspaceHandle,
output: WlOutput,
overlay_cursor: bool,
) -> Result<Capture, CaptureError>;
fn capture_toplevel(
&mut self,
dh: &DisplayHandle,
toplevel: Window,
overlay_cursor: bool,
) -> Result<Capture, CaptureError>;
fn start_time(&mut self) -> Instant;
}
impl<D> GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData, D>
for ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler,
{
fn bind(
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: wayland_server::New<ZcosmicExportDmabufManagerV1>,
_global_data: &ExportDmabufGlobalData,
data_init: &mut wayland_server::DataInit<'_, D>,
) {
data_init.init(resource, ());
}
fn can_view(client: Client, global_data: &ExportDmabufGlobalData) -> bool {
(global_data.filter)(&client)
}
}
impl<D> Dispatch<ZcosmicExportDmabufManagerV1, (), D> for ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler
+ WorkspaceHandler
+ ToplevelInfoHandler,
{
fn request(
state: &mut D,
_client: &wayland_server::Client,
_resource: &ZcosmicExportDmabufManagerV1,
request: <ZcosmicExportDmabufManagerV1 as wayland_server::Resource>::Request,
_data: &(),
dhandle: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, D>,
) {
let start_time = state.start_time();
match request {
zcosmic_export_dmabuf_manager_v1::Request::CaptureOutput {
frame,
overlay_cursor,
output,
} => {
let frame = data_init.init(frame, ());
match state.capture_output(dhandle, output, overlay_cursor != 0) {
Ok(capture) => handle_capture(capture, frame, start_time),
Err(err) => frame.cancel(err.into()),
}
}
zcosmic_export_dmabuf_manager_v1::Request::CaptureWorkspace {
frame,
overlay_cursor,
workspace,
output,
} => {
let frame = data_init.init(frame, ());
match state.workspace_state().workspace_handle(&workspace) {
Some(workspace) => {
match state.capture_workspace(
dhandle,
workspace,
output,
overlay_cursor != 0,
) {
Ok(capture) => handle_capture(capture, frame, start_time),
Err(err) => frame.cancel(err.into()),
}
}
None => frame.cancel(CancelReason::Permanent),
}
}
zcosmic_export_dmabuf_manager_v1::Request::CaptureToplevel {
frame,
overlay_cursor,
toplevel,
} => {
let frame = data_init.init(frame, ());
match window_from_handle(toplevel) {
Some(window) => {
match state.capture_toplevel(dhandle, window, overlay_cursor != 0) {
Ok(capture) => handle_capture(capture, frame, start_time),
Err(err) => frame.cancel(err.into()),
}
}
None => frame.cancel(CancelReason::Permanent),
}
}
zcosmic_export_dmabuf_manager_v1::Request::Destroy => {}
_ => {}
}
}
}
impl From<CaptureError> for CancelReason {
fn from(err: CaptureError) -> Self {
match err {
CaptureError::Temporary(err) => {
slog_scope::debug!("Temporary Capture Error: {}", err);
CancelReason::Temporary
}
CaptureError::Permanent(err) => {
slog_scope::warn!("Permanent Capture Error: {}", err);
CancelReason::Permanent
}
CaptureError::Resizing => CancelReason::Resizing,
}
}
}
fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_time: Instant) {
let Capture {
device,
dmabuf,
presentation_time,
} = capture;
let format = dmabuf.format();
let modifier: u64 = format.modifier.into();
frame.device(Vec::from(device.dev_id().to_ne_bytes()));
frame.frame(
dmabuf.width(),
dmabuf.height(),
0,
0,
if dmabuf.y_inverted() { 1 } else { 0 },
Flags::Transient,
format.code as u32,
(modifier >> 32) as u32,
(modifier & 0xFFFFFFFF) as u32,
dmabuf.num_planes() as u32,
);
for (i, (handle, (offset, stride))) in dmabuf
.handles()
.zip(dmabuf.offsets().zip(dmabuf.strides()))
.enumerate()
{
// SAFETY: BorrowedFd is used for seeking
let mut file = unsafe { File::from_raw_fd(handle.as_raw_fd()) };
let size = match file.seek(SeekFrom::End(0)) {
Ok(size) => size,
Err(err) => {
slog_scope::debug!("Temporary Capture Error: {}", err);
frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary);
return;
}
};
if let Err(err) = file.rewind() {
slog_scope::debug!("Temporary Capture Error: {}", err);
frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary);
return;
}
// SAFETY: Converted back to raw_fd, no chance in ownership
let handle = file.into_raw_fd();
// FDs are dup'ed by wayland-rs before sending them
frame.object(i as u32, handle, size as u32, offset, stride, i as u32);
}
let duration = presentation_time.duration_since(start_time);
let (tv_sec, tv_nsec) = (duration.as_secs(), duration.subsec_nanos());
frame.ready((tv_sec >> 32) as u32, (tv_sec & 0xFFFFFFFF) as u32, tv_nsec);
}
impl<D> Dispatch<ZcosmicExportDmabufFrameV1, (), D> for ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler,
{
fn request(
_state: &mut D,
_client: &wayland_server::Client,
_resource: &ZcosmicExportDmabufFrameV1,
request: <ZcosmicExportDmabufFrameV1 as wayland_server::Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
_data_init: &mut wayland_server::DataInit<'_, D>,
) {
match request {
zcosmic_export_dmabuf_frame_v1::Request::Destroy => {}
_ => {}
}
}
}
macro_rules! delegate_export_dmabuf {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: $crate::wayland::protocols::export_dmabuf::ExportDmabufGlobalData
] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState);
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: ()
] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState);
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_frame_v1::ZcosmicExportDmabufFrameV1: ()
] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState);
};
}
pub(crate) use delegate_export_dmabuf;

View file

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
pub mod drm;
pub mod export_dmabuf;
//pub mod export_dmabuf;
pub mod output_configuration;
pub mod screencopy;
pub mod toplevel_info;
pub mod toplevel_management;
pub mod workspace;

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::{
output::{Mode, Output, OutputData},
output::{Mode, Output},
reexports::{
wayland_protocols_wlr::output_management::v1::server::{
zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1},
@ -17,6 +17,7 @@ use smithay::{
},
},
utils::{Logical, Physical, Point, Size, Transform},
wayland::output::WlOutputData,
};
use std::{
convert::{TryFrom, TryInto},
@ -498,7 +499,7 @@ where
impl<D> OutputConfigurationState<D>
where
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
+ GlobalDispatch<WlOutput, OutputData>
+ GlobalDispatch<WlOutput, WlOutputData>
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
+ Dispatch<ZwlrOutputHeadV1, Output>
+ Dispatch<ZwlrOutputModeV1, Mode>

View file

@ -0,0 +1,946 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use cosmic_protocols::screencopy::v1::server::{
zcosmic_screencopy_manager_v1::{self, CursorMode as WlCursorMode, ZcosmicScreencopyManagerV1},
zcosmic_screencopy_session_v1::{
self, BufferType, FailureReason, InputType, ZcosmicScreencopySessionV1,
},
};
use smithay::{
backend::{
allocator::Fourcc as DrmFourcc,
drm::{DrmNode, NodeType},
},
desktop::Window,
input::{Seat, SeatHandler},
output::Output,
reexports::wayland_server::{
protocol::{wl_buffer::WlBuffer, wl_output, wl_seat::WlSeat, wl_shm::Format as ShmFormat},
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
},
utils::{user_data::UserDataMap, Buffer, IsAlive, Physical, Point, Rectangle, Size, Transform},
};
use wayland_backend::{
protocol::WEnum,
server::{GlobalId, ObjectId},
};
use crate::state::State;
use super::{
toplevel_info::window_from_handle,
workspace::{WorkspaceHandle, WorkspaceHandler},
};
/// Screencopy global state
pub struct ScreencopyState {
global: GlobalId,
}
pub struct ScreencopyGlobalData {
cursor_modes: Vec<WlCursorMode>,
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
}
impl ScreencopyState {
/// Create a new screencopy global
pub fn new<D, I, F>(
display: &DisplayHandle,
cursor_modes: I,
client_filter: F,
) -> ScreencopyState
where
D: GlobalDispatch<ZcosmicScreencopyManagerV1, ScreencopyGlobalData>
+ Dispatch<ZcosmicScreencopyManagerV1, Vec<WlCursorMode>>
+ Dispatch<ZcosmicScreencopySessionV1, SessionData>
+ ScreencopyHandler
+ WorkspaceHandler
+ 'static,
I: IntoIterator<Item = WlCursorMode>,
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
{
ScreencopyState {
global: display.create_global::<D, ZcosmicScreencopyManagerV1, _>(
1,
ScreencopyGlobalData {
cursor_modes: Vec::from_iter(cursor_modes),
filter: Box::new(client_filter),
},
),
}
}
/// Returns the screencopy global id
pub fn global(&self) -> GlobalId {
self.global.clone()
}
}
#[derive(Debug)]
pub enum BufferInfo {
Shm {
format: ShmFormat,
size: Size<i32, Buffer>,
stride: u32,
},
Dmabuf {
node: DrmNode,
format: DrmFourcc,
size: Size<i32, Buffer>,
},
}
#[derive(Debug)]
pub struct SessionDataInnerInner {
gone: bool,
pending_buffer: Option<BufferParams>,
_type: SessionType,
aux: AuxData,
}
impl SessionDataInnerInner {
pub fn is_cursor(&self) -> bool {
match self.aux {
AuxData::Cursor { .. } => true,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SessionType {
Output(Output),
Workspace(Output, WorkspaceHandle),
Window(Window),
Cursor(Seat<State>),
#[doc(hidden)]
Unknown,
}
#[derive(Debug)]
enum AuxData {
Normal { cursor: CursorMode },
Cursor { seat: WlSeat },
}
#[derive(Debug, Clone, PartialEq)]
pub enum CursorMode {
Captured(Vec<CursorSession>),
Embedded,
None,
}
impl AuxData {
pub fn seat(&self) -> &WlSeat {
match self {
AuxData::Cursor { seat } => seat,
_ => unreachable!("Unwrapped seat from aux data"),
}
}
pub fn cursor(&self) -> &CursorMode {
match self {
AuxData::Normal { cursor } => &cursor,
_ => unreachable!("Unwrapped cursor from aux data"),
}
}
}
impl CursorMode {
pub fn sessions<'a>(&'a self) -> impl Iterator<Item = &'a CursorSession> {
match self {
CursorMode::Captured(sessions) => Some(sessions.iter()).into_iter().flatten(),
_ => None.into_iter().flatten(),
}
}
}
#[derive(Debug)]
pub struct SessionDataInner {
inner: Mutex<SessionDataInnerInner>,
user_data: UserDataMap,
}
pub type SessionData = Arc<SessionDataInner>;
#[derive(Debug, Clone)]
pub struct Session {
obj: SessionResource,
data: SessionData,
}
#[derive(Debug, Clone)]
enum SessionResource {
Alive(ZcosmicScreencopySessionV1),
Destroyed(ObjectId),
}
impl SessionResource {
fn client(&self) -> Option<Client> {
match self {
SessionResource::Alive(obj) => obj.client(),
_ => None,
}
}
fn buffer_info(
&self,
_type: BufferType,
node: Option<String>,
format: u32,
width: u32,
height: u32,
stride: u32,
) {
if let SessionResource::Alive(obj) = self {
obj.buffer_info(_type, node, format, width, height, stride)
}
}
fn init_done(&self) {
if let SessionResource::Alive(obj) = self {
obj.init_done()
}
}
fn transform(&self, transform: wl_output::Transform) {
if let SessionResource::Alive(obj) = self {
obj.transform(transform)
}
}
fn damage(&self, x: u32, y: u32, w: u32, h: u32) {
if let SessionResource::Alive(obj) = self {
obj.damage(x, y, w, h)
}
}
fn commit_time(&self, time_sec_hi: u32, time_sec_lo: u32, time_nsec: u32) {
if let SessionResource::Alive(obj) = self {
obj.commit_time(time_sec_hi, time_sec_lo, time_nsec)
}
}
fn ready(&self) {
if let SessionResource::Alive(obj) = self {
obj.ready()
}
}
fn failed(&self, reason: FailureReason) {
if let SessionResource::Alive(obj) = self {
obj.failed(reason)
}
}
fn cursor_enter(&self, wl_seat: &WlSeat, input_type: InputType) {
if let SessionResource::Alive(obj) = self {
obj.cursor_enter(wl_seat, input_type)
}
}
fn cursor_info(
&self,
wl_seat: &WlSeat,
input_type: InputType,
x: i32,
y: i32,
w: i32,
h: i32,
dx: i32,
dy: i32,
) {
if let SessionResource::Alive(obj) = self {
obj.cursor_info(wl_seat, input_type, x, y, w, h, dx, dy)
}
}
fn cursor_leave(&self, wl_seat: &WlSeat, input_type: InputType) {
if let SessionResource::Alive(obj) = self {
obj.cursor_leave(wl_seat, input_type)
}
}
}
impl PartialEq for SessionResource {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(SessionResource::Alive(obj1), SessionResource::Alive(obj2)) => obj1 == obj2,
(SessionResource::Alive(obj), SessionResource::Destroyed(id))
| (SessionResource::Destroyed(id), SessionResource::Alive(obj)) => obj.id() == *id,
(SessionResource::Destroyed(id1), SessionResource::Destroyed(id2)) => id1 == id2,
}
}
}
impl PartialEq for Session {
fn eq(&self, other: &Self) -> bool {
self.obj == other.obj
}
}
// TODO: Handle Alive
// TODO: Better errors
impl Session {
pub fn cursor_enter<D: SeatHandler + 'static>(&self, seat: &Seat<D>, input_type: InputType) {
if !self.alive() {
return;
}
if let Some(client) = self.obj.client() {
for wl_seat in seat.client_seats(&client) {
self.obj.cursor_enter(&wl_seat, input_type)
}
}
}
pub fn cursor_info<D: SeatHandler + 'static>(
&self,
seat: &Seat<D>,
input_type: InputType,
geometry: Rectangle<i32, Buffer>,
offset: Point<i32, Buffer>,
) {
if !self.alive() {
return;
}
if let Some(client) = self.obj.client() {
for wl_seat in seat.client_seats(&client) {
self.obj.cursor_info(
&wl_seat,
input_type,
geometry.loc.x,
geometry.loc.y,
geometry.size.w,
geometry.size.h,
offset.x,
offset.y,
);
let data = self.data.inner.lock().unwrap();
for cursor_session in data.aux.cursor().sessions() {
cursor_session.obj.cursor_info(
&wl_seat,
input_type,
geometry.loc.x,
geometry.loc.y,
geometry.size.w,
geometry.size.h,
offset.x,
offset.y,
);
}
}
}
}
pub fn cursor_leave<D: SeatHandler + 'static>(&self, seat: &Seat<D>, input_type: InputType) {
if !self.alive() {
return;
}
if let Some(client) = self.obj.client() {
for wl_seat in seat.client_seats(&client) {
self.obj.cursor_leave(&wl_seat, input_type)
}
}
}
pub fn cursor_sessions(&self) -> impl Iterator<Item = CursorSession> {
if !self.alive() {
return Vec::new().into_iter();
}
self.data
.inner
.lock()
.unwrap()
.aux
.cursor()
.sessions()
.cloned()
.collect::<Vec<_>>()
.into_iter()
}
pub fn commit_buffer(
&self,
transform: Transform,
damage: Vec<Rectangle<i32, Physical>>,
time: Option<Duration>,
) {
if !self.alive() {
return;
}
self.obj.transform(transform.into());
for rect in damage {
self.obj.damage(
rect.loc.x as u32,
rect.loc.y as u32,
rect.size.w as u32,
rect.size.h as u32,
);
}
if let Some(time) = time {
let tv_sec_hi = (time.as_secs() >> 32) as u32;
let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32;
self.obj
.commit_time(tv_sec_hi, tv_sec_lo, time.subsec_nanos());
}
self.obj.ready()
}
pub fn failed(&self, reason: FailureReason) {
if !self.alive() {
return;
}
self.obj.failed(reason);
self.data.inner.lock().unwrap().gone = true;
}
pub fn user_data(&self) -> &UserDataMap {
&self.data.user_data
}
pub fn session_type(&self) -> SessionType {
self.data.inner.lock().unwrap()._type.clone()
}
pub fn cursor_mode(&self) -> CursorMode {
self.data.inner.lock().unwrap().aux.cursor().clone()
}
}
impl IsAlive for Session {
fn alive(&self) -> bool {
!self.data.inner.lock().unwrap().gone
}
}
#[derive(Debug, Clone)]
pub struct CursorSession {
obj: SessionResource,
data: SessionData,
}
impl PartialEq for CursorSession {
fn eq(&self, other: &Self) -> bool {
self.obj == other.obj
}
}
impl CursorSession {
pub fn seat(&self) -> WlSeat {
self.data.inner.lock().unwrap().aux.seat().clone()
}
pub fn buffer_waiting(&self) -> Option<BufferParams> {
self.data.inner.lock().unwrap().pending_buffer.take()
}
pub fn commit_buffer<'a>(
&self,
transform: Transform,
damage: impl Iterator<Item = &'a Rectangle<i32, Buffer>> + 'a,
) {
self.obj.transform(transform.into());
for rect in damage {
self.obj.damage(
rect.loc.x as u32,
rect.loc.y as u32,
rect.size.w as u32,
rect.size.h as u32,
);
}
}
pub fn failed(&self, reason: FailureReason) {
self.obj.failed(reason);
self.data.inner.lock().unwrap().gone = true;
}
pub fn user_data(&self) -> &UserDataMap {
&self.data.user_data
}
}
impl IsAlive for CursorSession {
fn alive(&self) -> bool {
!self.data.inner.lock().unwrap().gone
}
}
#[derive(Debug, Clone)]
pub struct BufferParams {
pub buffer: WlBuffer,
pub node: Option<DrmNode>,
pub age: u32,
}
pub trait ScreencopyHandler {
fn capture_output(&mut self, output: Output, session: Session) -> Vec<BufferInfo>;
fn capture_workspace(
&mut self,
workspace: WorkspaceHandle,
output: Output,
session: Session,
) -> Vec<BufferInfo>;
fn capture_toplevel(&mut self, toplevel: Window, session: Session) -> Vec<BufferInfo>;
fn capture_cursor(&mut self, session: CursorSession) -> Vec<BufferInfo>;
fn buffer_attached(&mut self, session: Session, buffer: BufferParams, on_damage: bool);
fn cursor_session_destroyed(&mut self, session: CursorSession) {
let _ = session;
}
fn session_destroyed(&mut self, session: Session) {
let _ = session;
}
}
impl<D> GlobalDispatch<ZcosmicScreencopyManagerV1, ScreencopyGlobalData, D> for ScreencopyState
where
D: GlobalDispatch<ZcosmicScreencopyManagerV1, ScreencopyGlobalData>
+ Dispatch<ZcosmicScreencopyManagerV1, Vec<WlCursorMode>>
+ Dispatch<ZcosmicScreencopySessionV1, SessionData>
+ ScreencopyHandler
+ WorkspaceHandler
+ 'static,
{
fn bind(
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: New<ZcosmicScreencopyManagerV1>,
global_data: &ScreencopyGlobalData,
data_init: &mut DataInit<'_, D>,
) {
let global = data_init.init(resource, global_data.cursor_modes.clone());
for mode in &global_data.cursor_modes {
global.supported_cursor_mode(*mode);
}
}
fn can_view(client: Client, global_data: &ScreencopyGlobalData) -> bool {
(global_data.filter)(&client)
}
}
fn check_cursor(
cursor: WEnum<WlCursorMode>,
supported: &[WlCursorMode],
resource: &ZcosmicScreencopyManagerV1,
) -> Option<WlCursorMode> {
match cursor.into_result() {
Ok(mode) => {
if !supported.contains(&mode) {
slog_scope::warn!("Client did send unsupported cursor mode: {:?}", mode);
resource.post_error(
zcosmic_screencopy_manager_v1::Error::InvalidCursorMode,
"Unsupported cursor mode",
);
return None;
}
Some(mode)
}
Err(err) => {
slog_scope::warn!("Client did send unknown cursor mode: {}", err);
resource.post_error(
zcosmic_screencopy_manager_v1::Error::InvalidCursorMode,
"Unknown cursor mode, wrong protocol version?",
);
None
}
}
}
fn init_session<D>(
data_init: &mut DataInit<'_, D>,
session: New<ZcosmicScreencopySessionV1>,
cursor: WlCursorMode,
_type: SessionType,
) -> Option<Session>
where
D: GlobalDispatch<ZcosmicScreencopyManagerV1, ScreencopyGlobalData>
+ Dispatch<ZcosmicScreencopyManagerV1, Vec<WlCursorMode>>
+ Dispatch<ZcosmicScreencopySessionV1, SessionData>
+ ScreencopyHandler
+ WorkspaceHandler
+ 'static,
{
let data = Arc::new(SessionDataInner {
inner: Mutex::new(SessionDataInnerInner {
gone: false,
pending_buffer: None,
aux: AuxData::Normal {
cursor: match cursor {
WlCursorMode::Capture => CursorMode::Captured(Vec::new()),
WlCursorMode::Embedded => CursorMode::Embedded,
_ => CursorMode::None,
},
},
_type,
}),
user_data: UserDataMap::new(),
});
let session = data_init.init(session, data.clone());
let session = Session {
obj: SessionResource::Alive(session),
data,
};
Some(session)
}
impl<D> Dispatch<ZcosmicScreencopyManagerV1, Vec<WlCursorMode>, D> for ScreencopyState
where
D: GlobalDispatch<ZcosmicScreencopyManagerV1, ScreencopyGlobalData>
+ Dispatch<ZcosmicScreencopyManagerV1, Vec<WlCursorMode>>
+ Dispatch<ZcosmicScreencopySessionV1, SessionData>
+ ScreencopyHandler
+ WorkspaceHandler
+ 'static,
{
fn request(
state: &mut D,
_client: &Client,
resource: &ZcosmicScreencopyManagerV1,
request: <ZcosmicScreencopyManagerV1 as smithay::reexports::wayland_server::Resource>::Request,
data: &Vec<WlCursorMode>,
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
match request {
zcosmic_screencopy_manager_v1::Request::CaptureOutput {
session,
output,
cursor,
} => {
let Some(cursor) = check_cursor(cursor, &data, resource) else { return; };
match Output::from_resource(&output) {
Some(output) => {
let session = match init_session(
data_init,
session,
cursor,
SessionType::Output(output.clone()),
) {
Some(result) => result,
None => {
return;
}
};
let formats = state.capture_output(output, session.clone());
if !session.data.inner.lock().unwrap().gone {
send_formats(&session.obj, formats);
}
}
None => {
let session =
match init_session(data_init, session, cursor, SessionType::Unknown) {
Some(result) => result,
None => {
return;
}
};
session.failed(FailureReason::InvalidOutput);
return;
}
}
}
zcosmic_screencopy_manager_v1::Request::CaptureToplevel {
session,
toplevel,
cursor,
} => {
let Some(cursor) = check_cursor(cursor, &data, resource) else { return; };
match window_from_handle(toplevel) {
Some(window) => {
let session = match init_session(
data_init,
session,
cursor,
SessionType::Window(window.clone()),
) {
Some(result) => result,
None => {
return;
}
};
let formats = state.capture_toplevel(window, session.clone());
if !session.data.inner.lock().unwrap().gone {
send_formats(&session.obj, formats);
}
}
None => {
let session =
match init_session(data_init, session, cursor, SessionType::Unknown) {
Some(result) => result,
None => {
return;
}
};
session.obj.failed(FailureReason::InvalidToplevel);
return;
}
}
}
zcosmic_screencopy_manager_v1::Request::CaptureWorkspace {
session,
workspace,
output,
cursor,
} => {
let Some(cursor) = check_cursor(cursor, &data, resource) else { return; };
match Output::from_resource(&output) {
Some(output) => match state.workspace_state().workspace_handle(&workspace) {
Some(handle) => {
let session = match init_session(
data_init,
session,
cursor,
SessionType::Workspace(output.clone(), handle.clone()),
) {
Some(result) => result,
None => {
return;
}
};
let formats = state.capture_workspace(handle, output, session.clone());
if !session.data.inner.lock().unwrap().gone {
send_formats(&session.obj, formats);
}
}
None => {
let session = match init_session(
data_init,
session,
cursor,
SessionType::Unknown,
) {
Some(result) => result,
None => {
return;
}
};
session.failed(FailureReason::InvalidWorkspace);
return;
}
},
None => {
let session =
match init_session(data_init, session, cursor, SessionType::Unknown) {
Some(result) => result,
None => {
return;
}
};
session.failed(FailureReason::InvalidOutput);
return;
}
}
}
_ => {}
}
}
}
impl<D> Dispatch<ZcosmicScreencopySessionV1, SessionData, D> for ScreencopyState
where
D: GlobalDispatch<ZcosmicScreencopyManagerV1, ScreencopyGlobalData>
+ Dispatch<ZcosmicScreencopyManagerV1, Vec<WlCursorMode>>
+ Dispatch<ZcosmicScreencopySessionV1, SessionData>
+ ScreencopyHandler
+ WorkspaceHandler
+ 'static,
{
fn request(
state: &mut D,
_client: &Client,
resource: &ZcosmicScreencopySessionV1,
request: <ZcosmicScreencopySessionV1 as Resource>::Request,
data: &SessionData,
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
match request {
zcosmic_screencopy_session_v1::Request::CaptureCursor {
session,
seat: wl_seat,
} => match Seat::from_resource(&wl_seat) {
Some(seat) => {
{
let resource_data = data.inner.lock().unwrap();
if resource_data.is_cursor() || resource_data.gone {
resource.failed(FailureReason::Unspec);
return;
}
}
let data = Arc::new(SessionDataInner {
inner: Mutex::new(SessionDataInnerInner {
gone: false,
pending_buffer: None,
aux: AuxData::Cursor { seat: wl_seat },
_type: SessionType::Cursor(seat),
}),
user_data: UserDataMap::new(),
});
let session = data_init.init(session, data.clone());
let cursor_session = CursorSession {
obj: SessionResource::Alive(session),
data,
};
let formats = state.capture_cursor(cursor_session.clone());
if !cursor_session.data.inner.lock().unwrap().gone {
send_formats(&cursor_session.obj, formats);
}
}
None => {
let session = match init_session(
data_init,
session,
WlCursorMode::Capture,
SessionType::Unknown,
) {
Some(result) => result,
None => {
return;
}
};
session.failed(FailureReason::InvalidSeat);
return;
}
},
zcosmic_screencopy_session_v1::Request::AttachBuffer { buffer, node, age } => {
if data.inner.lock().unwrap().gone {
resource.failed(FailureReason::Unspec);
return;
}
let params = BufferParams {
buffer,
node: node.and_then(|p| DrmNode::from_path(p).ok()),
age,
};
data.inner.lock().unwrap().pending_buffer = Some(params);
}
zcosmic_screencopy_session_v1::Request::Commit { options } => {
let buffer = {
let mut resource_data = data.inner.lock().unwrap();
if resource_data.is_cursor() || resource_data.gone {
resource.failed(FailureReason::Unspec);
return;
}
resource_data.pending_buffer.take()
};
if let Some(buffer) = buffer {
let session = Session {
obj: SessionResource::Alive(resource.clone()),
data: data.clone(),
};
state.buffer_attached(
session,
buffer,
options
.into_result()
.ok()
.map(|v| v.contains(zcosmic_screencopy_session_v1::Options::OnDamage))
.unwrap_or(false),
);
} else {
resource.failed(FailureReason::InvalidBuffer);
}
}
zcosmic_screencopy_session_v1::Request::Destroy => {
data.inner.lock().unwrap().gone = true;
}
_ => {}
}
}
fn destroyed(
state: &mut D,
_client: wayland_backend::server::ClientId,
resource: wayland_backend::server::ObjectId,
data: &SessionData,
) {
if data.inner.lock().unwrap().is_cursor() {
let session = CursorSession {
obj: SessionResource::Destroyed(resource),
data: data.clone(),
};
state.cursor_session_destroyed(session)
} else {
let session = Session {
obj: SessionResource::Destroyed(resource),
data: data.clone(),
};
state.session_destroyed(session)
}
}
}
fn send_formats(session: &SessionResource, formats: Vec<BufferInfo>) {
for format in formats {
match format {
BufferInfo::Dmabuf { node, format, size } => {
if let Some(node_path) = node
.dev_path_with_type(NodeType::Render)
.or_else(|| node.dev_path())
{
session.buffer_info(
zcosmic_screencopy_session_v1::BufferType::Dmabuf,
Some(node_path.as_os_str().to_string_lossy().into_owned()),
format as u32,
size.w as u32,
size.h as u32,
0,
);
}
}
BufferInfo::Shm {
format,
size,
stride,
} => session.buffer_info(
zcosmic_screencopy_session_v1::BufferType::WlShm,
None,
format as u32,
size.w as u32,
size.h as u32,
stride,
),
}
}
session.init_done();
}
macro_rules! delegate_screencopy {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: $crate::wayland::protocols::screencopy::ScreencopyGlobalData
] => $crate::wayland::protocols::screencopy::ScreencopyState);
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: std::vec::Vec<cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode>
] => $crate::wayland::protocols::screencopy::ScreencopyState);
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1: $crate::wayland::protocols::screencopy::SessionData
] => $crate::wayland::protocols::screencopy::ScreencopyState);
};
}
pub(crate) use delegate_screencopy;

View file

@ -413,9 +413,9 @@ fn send_toplevel_to_client<D>(
.iter()
.filter(|o| !handle_state.outputs.contains(o))
{
new_output.with_client_outputs(dh, &client, |_dh, wl_output| {
instance.output_enter(wl_output);
});
for wl_output in new_output.client_outputs(&client) {
instance.output_enter(&wl_output);
}
changed = true;
}
for old_output in handle_state
@ -423,9 +423,9 @@ fn send_toplevel_to_client<D>(
.iter()
.filter(|o| !state.outputs.contains(o))
{
old_output.with_client_outputs(dh, &client, |_dh, wl_output| {
instance.output_leave(wl_output);
});
for wl_output in old_output.client_outputs(&client) {
instance.output_leave(&wl_output);
}
changed = true;
}
handle_state.outputs = state.outputs.clone();

View file

@ -182,12 +182,12 @@ where
let window = window_from_handle(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_id() {
if let Some(client) = surface.client() {
if width == 0 && height == 0 {
toplevel_state.rectangles.remove(&client);
toplevel_state.rectangles.remove(&client.id());
} else {
toplevel_state.rectangles.insert(
client,
client.id(),
(
surface,
Rectangle::from_loc_and_size((x, y), (width, height)),
@ -207,7 +207,7 @@ where
if !mng_state
.instances
.iter()
.any(|i| i.client_id().map(|c| c == client).unwrap_or(false))
.any(|i| i.client().map(|c| c.id() == client).unwrap_or(false))
{
for toplevel in state.toplevel_info_state_mut().toplevels.iter() {
if let Some(toplevel_state) = toplevel.user_data().get::<ToplevelState>() {

View file

@ -821,9 +821,9 @@ where
.iter()
.filter(|o| !handle_state.outputs.contains(o))
{
new_output.with_client_outputs(dh, &client, |_dh, wl_output| {
instance.output_enter(wl_output);
});
for wl_output in new_output.client_outputs(&client) {
instance.output_enter(&wl_output);
}
changed = true;
}
for old_output in handle_state
@ -831,9 +831,9 @@ where
.iter()
.filter(|o| !group.outputs.contains(o))
{
old_output.with_client_outputs(dh, &client, |_dh, wl_output| {
instance.output_leave(wl_output);
});
for wl_output in old_output.client_outputs(&client) {
instance.output_leave(&wl_output);
}
changed = true;
}
handle_state.outputs = group.outputs.clone();