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 = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
sendfd = "0.4.1" sendfd = "0.4.1"
egui = { version = "0.18.1", optional = true } egui = { version = "0.19.0", optional = true }
edid-rs = { version = "0.1" } edid-rs = { version = "0.1" }
png = "0.17.5" png = "0.17.5"
lazy_static = "1.4.0" lazy_static = "1.4.0"
@ -28,28 +28,27 @@ xkbcommon = "0.4"
indexmap = "1.8.0" indexmap = "1.8.0"
xdg = "^2.1" xdg = "^2.1"
ron = "0.7" ron = "0.7"
atomic_float = "0.1"
libsystemd = "0.5" libsystemd = "0.5"
wayland-backend = "=0.1.0-beta.10" wayland-backend = "=0.1.0-beta.13"
wayland-scanner = "=0.30.0-beta.10" wayland-scanner = "=0.30.0-beta.13"
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] }
[dependencies.smithay] [dependencies.smithay]
version = "0.3" version = "0.3"
git = "https://github.com/Smithay/smithay.git" git = "https://github.com/Smithay/smithay.git"
rev = "606d2d5c" rev = "b297c93edc"
default-features = false 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] [dependencies.smithay-egui]
git = "https://github.com/Smithay/smithay-egui.git" git = "https://github.com/Smithay/smithay-egui.git"
rev = "939febaf" rev = "9fe1fa5e01"
features = ["svg"]
optional = true optional = true
[features] [features]
default = [] default = []
debug = ["egui", "smithay-egui"] debug = ["egui", "smithay-egui"]
experimental = []
[profile.dev] [profile.dev]
lto = "thin" lto = "thin"
@ -62,4 +61,4 @@ debug = true
lto = "fat" lto = "fat"
[patch."https://github.com/Smithay/smithay.git"] [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: { key_bindings: {
(modifiers: [Logo, Shift], key: "Escape"): Terminate, (modifiers: [Super, Shift], key: "Escape"): Terminate,
(modifiers: [Logo], key: "Escape"): Debug, (modifiers: [Super], key: "Escape"): Debug,
(modifiers: [Logo], key: "q"): Close, (modifiers: [Super], key: "q"): Close,
(modifiers: [Logo], key: "1"): Workspace(1),
(modifiers: [Logo], key: "2"): Workspace(2), (modifiers: [Super], key: "1"): Workspace(1),
(modifiers: [Logo], key: "3"): Workspace(3), (modifiers: [Super], key: "2"): Workspace(2),
(modifiers: [Logo], key: "4"): Workspace(4), (modifiers: [Super], key: "3"): Workspace(3),
(modifiers: [Logo], key: "5"): Workspace(5), (modifiers: [Super], key: "4"): Workspace(4),
(modifiers: [Logo], key: "6"): Workspace(6), (modifiers: [Super], key: "5"): Workspace(5),
(modifiers: [Logo], key: "7"): Workspace(7), (modifiers: [Super], key: "6"): Workspace(6),
(modifiers: [Logo], key: "8"): Workspace(8), (modifiers: [Super], key: "7"): Workspace(7),
(modifiers: [Logo], key: "9"): Workspace(9), (modifiers: [Super], key: "8"): Workspace(8),
(modifiers: [Logo], key: "0"): Workspace(0), (modifiers: [Super], key: "9"): Workspace(9),
(modifiers: [Logo, Shift], key: "1"): MoveToWorkspace(1), (modifiers: [Super], key: "0"): LastWorkspace,
(modifiers: [Logo, Shift], key: "2"): MoveToWorkspace(2), (modifiers: [Super, Shift], key: "1"): MoveToWorkspace(1),
(modifiers: [Logo, Shift], key: "3"): MoveToWorkspace(3), (modifiers: [Super, Shift], key: "2"): MoveToWorkspace(2),
(modifiers: [Logo, Shift], key: "4"): MoveToWorkspace(4), (modifiers: [Super, Shift], key: "3"): MoveToWorkspace(3),
(modifiers: [Logo, Shift], key: "5"): MoveToWorkspace(5), (modifiers: [Super, Shift], key: "4"): MoveToWorkspace(4),
(modifiers: [Logo, Shift], key: "6"): MoveToWorkspace(6), (modifiers: [Super, Shift], key: "5"): MoveToWorkspace(5),
(modifiers: [Logo, Shift], key: "7"): MoveToWorkspace(7), (modifiers: [Super, Shift], key: "6"): MoveToWorkspace(6),
(modifiers: [Logo, Shift], key: "8"): MoveToWorkspace(8), (modifiers: [Super, Shift], key: "7"): MoveToWorkspace(7),
(modifiers: [Logo, Shift], key: "9"): MoveToWorkspace(9), (modifiers: [Super, Shift], key: "8"): MoveToWorkspace(8),
(modifiers: [Logo, Shift], key: "0"): MoveToWorkspace(0), (modifiers: [Super, Shift], key: "9"): MoveToWorkspace(9),
(modifiers: [Logo], key: "Left"): Focus(Left), (modifiers: [Super, Shift], key: "0"): MoveToLastWorkspace,
(modifiers: [Logo], key: "Right"): Focus(Right),
(modifiers: [Logo], key: "Up"): Focus(Up), // TODO: Depends on workspace orientation
(modifiers: [Logo], key: "Down"): Focus(Down), (modifiers: [Super, Ctrl], key: "Right"): NextWorkspace,
(modifiers: [Logo], key: "h"): Focus(Left), (modifiers: [Super, Ctrl], key: "Left"): PreviousWorkspace,
(modifiers: [Logo], key: "j"): Focus(Down), (modifiers: [Super, Ctrl, Shift], key: "Right"): MoveToNextWorkspace,
(modifiers: [Logo], key: "k"): Focus(Up), (modifiers: [Super, Ctrl, Shift], key: "Left"): MoveToPreviousWorkspace,
(modifiers: [Logo], key: "l"): Focus(Right), (modifiers: [Super, Ctrl], key: "l"): NextWorkspace,
//TODO: automatic orientation with Logo+o toggling (modifiers: [Super, Ctrl], key: "h"): PreviousWorkspace,
(modifiers: [Logo], key: "v"): Orientation(Vertical), (modifiers: [Super, Ctrl, Shift], key: "l"): MoveToNextWorkspace,
(modifiers: [Logo], key: "o"): Orientation(Horizontal), (modifiers: [Super, Ctrl, Shift], key: "h"): MoveToPreviousWorkspace,
(modifiers: [Logo], key: "y"): ToggleTiling,
(modifiers: [Logo], key: "g"): ToggleWindowFloating, (modifiers: [Super, Ctrl], key: "Down"): NextOutput,
(modifiers: [Logo, Shift], key: "f"): Fullscreen, (modifiers: [Super, Ctrl], key: "Up"): PreviousOutput,
(modifiers: [Logo, Shift], key: "s"): Screenshot, (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 //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 //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 //TODO: ability to select default terminal
(modifiers: [Logo], key: "t"): Spawn("gnome-terminal"), (modifiers: [Super], 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: "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: "XF86AudioRaiseVolume"): Spawn("amixer sset Master 5%+"),
(modifiers: [], key: "XF86AudioLowerVolume"): Spawn("amixer sset Master 5%-"), (modifiers: [], key: "XF86AudioLowerVolume"): Spawn("amixer sset Master 5%-"),
(modifiers: [], key: "XF86AudioMute"): Spawn("amixer sset Master toggle"), (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"), (modifiers: [], key: "XF86MonBrightnessDown"): Spawn("busctl --user call com.system76.CosmicSettingsDaemon /com/system76/CosmicSettingsDaemon com.system76.CosmicSettingsDaemon DecreaseDisplayBrightness"),
}, },
workspace_mode: OutputBound, workspace_mode: OutputBound,
workspace_amount: Dynamic,
floating_default: false, 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] [toolchain]
channel = "1.63" channel = "1.65"

View file

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

View file

@ -2,14 +2,20 @@
use crate::utils::prelude::*; use crate::utils::prelude::*;
use smithay::{ use smithay::{
backend::renderer::{Frame, ImportAll, ImportMem, Renderer, Texture}, backend::renderer::{
desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree}, element::{
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
texture::{TextureBuffer, TextureRenderElement},
},
ImportAll, ImportMem, Renderer,
},
input::{ input::{
pointer::{CursorImageAttributes, CursorImageStatus}, pointer::{CursorImageAttributes, CursorImageStatus},
Seat, Seat,
}, },
reexports::wayland_server::protocol::wl_surface, 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}, wayland::compositor::{get_role, with_states},
}; };
use std::{ use std::{
@ -18,6 +24,7 @@ use std::{
collections::HashMap, collections::HashMap,
io::Read, io::Read,
sync::Mutex, sync::Mutex,
time::Duration,
}; };
use xcursor::{ use xcursor::{
parser::{parse_xcursor, Image}, 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) parse_xcursor(&cursor_data).ok_or(Error::Parse)
} }
pub fn draw_surface_cursor( render_elements! {
surface: wl_surface::WlSurface, 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>>, location: impl Into<Point<i32, Logical>>,
) -> SurfaceTree scale: impl Into<Scale<f64>>,
) -> Vec<CursorRenderElement<R>>
where where
{ {
let mut position = location.into(); let mut position = location.into();
let scale = scale.into();
let h = with_states(&surface, |states| { let h = with_states(&surface, |states| {
states states
.data_map .data_map
@ -136,129 +151,30 @@ where
.hotspot .hotspot
}); });
position -= h; position -= h;
SurfaceTree {
surface, render_elements_from_surface_tree(surface, position.to_physical_precise_round(scale), scale)
position,
z_index: 100,
}
} }
pub fn draw_dnd_icon( pub fn draw_dnd_icon<R: Renderer + ImportAll>(
surface: wl_surface::WlSurface, surface: &wl_surface::WlSurface,
location: impl Into<Point<i32, Logical>>, location: impl Into<Point<i32, Logical>>,
) -> SurfaceTree { scale: impl Into<Scale<f64>>,
) -> Vec<CursorRenderElement<R>> {
if get_role(&surface) != Some("dnd_icon") { if get_role(&surface) != Some("dnd_icon") {
slog_scope::warn!( slog_scope::warn!(
"Trying to display as a dnd icon a surface that does not have the DndIcon role." "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, surface,
position: location.into(), location.into().to_physical_precise_round(scale),
z_index: 100, scale,
} )
} }
pub struct PointerElement<T: Texture> { pub struct CursorState {
seat_id: usize, pub cursor: Cursor,
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,
current_image: RefCell<Option<Image>>, current_image: RefCell<Option<Image>>,
image_cache: RefCell<HashMap<(TypeId, usize), Vec<(Image, Box<dyn Any + 'static>)>>>, 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, renderer: &mut R,
seat: &Seat<State>, seat: &Seat<State>,
location: Point<f64, Logical>, location: Point<f64, Logical>,
start_time: &std::time::Instant, scale: Scale<f64>,
time: Time<Monotonic>,
draw_default: bool, draw_default: bool,
) -> Option<I> ) -> Vec<CursorRenderElement<R>>
where where
I: From<SurfaceTree> + From<PointerElement<<R as Renderer>::TextureId>>, R: Renderer + ImportMem + ImportAll,
R: Renderer + ImportAll + ImportMem,
<R as Renderer>::TextureId: Clone + 'static, <R as Renderer>::TextureId: Clone + 'static,
{ {
// draw the cursor as relevant // draw the cursor as relevant
{ // reset the cursor if the surface is no longer alive
// reset the cursor if the surface is no longer alive let cursor_status = seat
let cursor_status = seat .user_data()
.user_data() .get::<RefCell<CursorImageStatus>>()
.get::<RefCell<CursorImageStatus>>() .map(|cell| {
.map(|cell| { let mut cursor_status = cell.borrow_mut();
let mut cursor_status = cell.borrow_mut(); if let CursorImageStatus::Surface(ref surface) = *cursor_status {
if let CursorImageStatus::Surface(ref surface) = *cursor_status { if !surface.alive() {
if !surface.alive() { *cursor_status = CursorImageStatus::Default;
*cursor_status = CursorImageStatus::Default;
}
} }
cursor_status.clone() }
}) cursor_status.clone()
.unwrap_or(CursorImageStatus::Default); })
.unwrap_or(CursorImageStatus::Default);
if let CursorImageStatus::Surface(wl_surface) = cursor_status { if let CursorImageStatus::Surface(ref wl_surface) = cursor_status {
Some(draw_surface_cursor(wl_surface.clone(), location.to_i32_round()).into()) return draw_surface_cursor(wl_surface, location.to_i32_round(), scale);
} else if draw_default { } else if draw_default && CursorImageStatus::Default == cursor_status {
let seat_userdata = seat.user_data(); let integer_scale = scale.x.max(scale.y).ceil() as u32;
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);
let mut cache = state.image_cache.borrow_mut(); let seat_userdata = seat.user_data();
let pointer_images = cache seat_userdata.insert_if_missing(CursorState::default);
.entry((TypeId::of::<<R as Renderer>::TextureId>(), renderer.id())) let state = seat_userdata.get::<CursorState>().unwrap();
.or_default(); let frame = state.cursor.get_image(
let pointer_image = pointer_images integer_scale,
.iter() Into::<Duration>::into(time).as_millis() as u32,
.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);
Some( let mut cache = state.image_cache.borrow_mut();
PointerElement::new(seat, pointer_image.clone(), location - hotspot, new_frame) let pointer_images = cache
.into(), .entry((TypeId::of::<TextureBuffer<R::TextureId>>(), renderer.id()))
) .or_default();
} else {
None 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 // SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
use crate::{debug::fps_ui, utils::prelude::*};
use crate::{ use crate::{
debug::{debug_ui, fps_ui, log_ui, EguiFrame}, shell::{layout::floating::SeatMoveGrabState, CosmicMappedRenderElement},
state::Fps, state::{Common, Fps},
utils::prelude::*, wayland::{
}; handlers::{data_device::get_dnd_icon, screencopy::render_session},
use crate::{ protocols::{
shell::grabs::{MoveGrabRenderElement, SeatMoveGrabState}, screencopy::{
state::Common, BufferParams, CursorMode as ScreencopyCursorMode, Session as ScreencopySession,
wayland::handlers::data_device::get_dnd_icon, },
}; workspace::WorkspaceHandle,
use slog::Logger;
use smithay::{
backend::{
drm::DrmNode,
renderer::{
gles2::{Gles2Renderbuffer, Gles2Renderer, Gles2Texture},
multigpu::{egl::EglGlesBackend, Error as MultiError, MultiFrame, MultiRenderer},
Frame, ImportAll, Renderer,
}, },
}, },
desktop::{ };
draw_layer_popups, draw_layer_surface, draw_window, draw_window_popups,
layer_map_for_output, use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason;
space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree}, use smithay::{
utils::damage_from_surface_tree, backend::{
Window, 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, output::Output,
utils::{Physical, Point, Rectangle, Scale, Transform}, utils::{Physical, Rectangle},
wayland::shell::wlr_layer::Layer as WlrLayer, wayland::dmabuf::get_dmabuf,
}; };
pub mod cursor; pub mod cursor;
use self::cursor::PointerElement; use self::cursor::CursorRenderElement;
pub mod element;
use self::element::{AsGles2Frame, AsGlowRenderer, CosmicElement};
pub type GlMultiRenderer<'a> = pub type GlMultiRenderer<'a> = MultiRenderer<
MultiRenderer<'a, 'a, EglGlesBackend, EglGlesBackend, Gles2Renderbuffer>; 'a,
pub type GlMultiFrame = MultiFrame<EglGlesBackend, EglGlesBackend>; '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! { #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub CustomElem<=Gles2Renderer>; pub enum CursorMode {
SurfaceTree=SurfaceTree, None,
PointerElement=PointerElement::<Gles2Texture>, NotDefault,
MoveGrabRenderElement=MoveGrabRenderElement, All,
#[cfg(feature = "debug")]
EguiFrame=EguiFrame,
} }
// TODO: due to the lifetime of MultiRenderer, we cannot be generic over CustomElem's renderer pub fn cursor_elements<E, R>(
// 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>(
renderer: &mut R, renderer: &mut R,
state: &Common, state: &Common,
output: &Output, output: &Output,
hardware_cursor: bool, mode: CursorMode,
) -> Vec<CustomElem> ) -> Vec<E>
where 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() { let pointer = match seat.get_pointer() {
Some(ptr) => ptr, Some(ptr) => ptr,
None => continue, None => continue,
}; };
let location = state let location = pointer.current_location() - output.current_location().to_f64();
.shell
.space_relative_output_geometry(pointer.current_location().to_i32_round(), output);
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() .user_data()
.get::<SeatMoveGrabState>() .get::<SeatMoveGrabState>()
.unwrap() .unwrap()
.borrow() .borrow()
.as_ref() .as_ref()
.and_then(|state| state.render(seat, output)) .map(|state| state.render::<E, R>(seat, output))
{ {
custom_elements.push(grab); elements.extend(grab_elements);
}
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)
} }
} }
custom_elements elements
} }
pub fn render_output<R>( pub fn render_output<R, Target, OffTarget, Source>(
gpu: Option<&DrmNode>, gpu: Option<&DrmNode>,
renderer: &mut R, renderer: &mut R,
age: u8, target: Target,
damage_tracker: &mut DamageTrackedRenderer,
age: usize,
state: &mut Common, state: &mut Common,
output: &Output, output: &Output,
hardware_cursor: bool, cursor_mode: CursorMode,
#[cfg(feature = "debug")] mut fps: Option<&mut Fps>, screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>> fps: Option<&mut Fps>,
) -> Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), RenderError<R>>
where 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, <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( render_workspace(
gpu, gpu,
renderer, renderer,
target,
damage_tracker,
age, age,
state, state,
workspace,
output, output,
hardware_cursor, &handle,
cursor_mode,
screencopy,
fps,
) )
} }
pub fn render_workspace<R>( pub fn render_workspace<R, Target, OffTarget, Source>(
gpu: Option<&DrmNode>, gpu: Option<&DrmNode>,
renderer: &mut R, renderer: &mut R,
age: u8, target: Target,
damage_tracker: &mut DamageTrackedRenderer,
age: usize,
state: &mut Common, state: &mut Common,
space_idx: u8,
output: &Output, output: &Output,
hardware_cursor: bool, handle: &WorkspaceHandle,
#[cfg(feature = "debug")] mut fps: Option<&mut Fps>, mut cursor_mode: CursorMode,
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>> screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
mut fps: Option<&mut Fps>,
) -> Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), RenderError<R>>
where 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, <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 { if let Some(ref mut fps) = fps {
fps.start(); fps.start();
} }
let space_idx = space_idx as usize; let workspace = state.shell.space_for_handle(&handle).ok_or(OutputNoMode)?;
let workspace = &mut state.shell.spaces[space_idx];
let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned();
let res = if let Some(window) = maybe_fullscreen_window { let screencopy_contains_embedded = screencopy.as_ref().map_or(false, |(_, sessions)| {
#[cfg(not(feature = "debug"))] sessions
{ .iter()
render_fullscreen(gpu, renderer, window, state, output, hardware_cursor) .any(|(s, _)| s.cursor_mode() == ScreencopyCursorMode::Embedded)
} });
#[cfg(feature = "debug")] // 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?
render_fullscreen( // It seems we would need to render to an offscreen buffer in those cases (and do multiple renders, which messes with damage tracking).
gpu, // So for now, we just pick the worst mode (embedded), if any requires it.
renderer, //
window, // 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).
state, if screencopy_contains_embedded {
output, cursor_mode = CursorMode::All;
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 mut elements: Vec<CosmicElement<R>> = cursor_elements(renderer, state, output, cursor_mode);
#[cfg(feature = "debug")] #[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 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, input::Devices,
state::{BackendData, Common, Data}, state::{BackendData, Common, Data},
utils::prelude::*, utils::prelude::*,
wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession},
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use smithay::{ use smithay::{
backend::{ backend::{
renderer::{ImportDma, ImportEgl}, renderer::{
damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, ImportDma,
ImportEgl,
},
winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice}, winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice},
}, },
desktop::layer_map_for_output, desktop::layer_map_for_output,
output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
reexports::{ reexports::{
calloop::{ping, EventLoop}, calloop::{ping, EventLoop},
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::DisplayHandle, wayland_server::DisplayHandle,
}, },
utils::Transform, utils::Transform,
@ -26,52 +31,71 @@ use std::cell::RefCell;
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
use crate::state::Fps; use crate::state::Fps;
use super::render::CursorMode;
pub struct WinitState { pub struct WinitState {
// The winit backend currently has no notion of multiple windows // The winit backend currently has no notion of multiple windows
pub backend: WinitGraphicsBackend, pub backend: WinitGraphicsBackend<GlowRenderer>,
output: Output, output: Output,
age_reset: u8, damage_tracker: DamageTrackedRenderer,
screencopy: Vec<(ScreencopySession, BufferParams)>,
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
fps: Fps, fps: Fps,
} }
impl WinitState { impl WinitState {
pub fn render_output(&mut self, state: &mut Common) -> Result<()> { pub fn render_output(&mut self, state: &mut Common) -> Result<()> {
if render::needs_buffer_reset(&self.output, state) {
self.reset_buffers();
}
self.backend self.backend
.bind() .bind()
.with_context(|| "Failed to bind buffer")?; .with_context(|| "Failed to bind buffer")?;
let age = if self.age_reset > 0 { let age = self.backend.buffer_age().unwrap_or(0);
self.age_reset -= 1;
0
} else {
self.backend.buffer_age().unwrap_or(0)
};
match render::render_output( let surface = self.backend.egl_surface();
match render::render_output::<_, _, Gles2Renderbuffer, _>(
None, None,
self.backend.renderer(), self.backend.renderer(),
age as u8, surface.clone(),
&mut self.damage_tracker,
age,
state, state,
&self.output, &self.output,
true, CursorMode::NotDefault,
if !self.screencopy.is_empty() {
Some((surface, &self.screencopy))
} else {
None
},
#[cfg(not(feature = "debug"))]
None,
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
Some(&mut self.fps), Some(&mut self.fps),
) { ) {
Ok(damage) => { Ok((damage, states)) => {
state self.screencopy.clear();
.shell
.active_space_mut(&self.output)
.space
.send_frames(state.start_time.elapsed().as_millis() as u32);
self.backend self.backend
.submit(damage.as_ref().map(|x| &**x)) .submit(damage.as_deref())
.with_context(|| "Failed to submit buffer for display")?; .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) => { Err(err) => {
for (session, params) in self.screencopy.drain(..) {
state.still_pending(session, params)
}
anyhow::bail!("Rendering failed: {}", err); anyhow::bail!("Rendering failed: {}", err);
} }
}; };
@ -105,8 +129,10 @@ impl WinitState {
} }
} }
pub fn reset_buffers(&mut self) { pub fn pending_screencopy(&mut self, new: Option<Vec<(ScreencopySession, BufferParams)>>) {
self.age_reset = 3; if let Some(sessions) = new {
self.screencopy.extend(sessions);
}
} }
} }
@ -133,7 +159,6 @@ pub fn init_backend(
refresh: 60_000, refresh: 60_000,
}; };
let output = Output::new(name, props, None); let output = Output::new(name, props, None);
let _global = output.create_global::<State>(dh);
output.add_mode(mode); output.add_mode(mode);
output.set_preferred(mode); output.set_preferred(mode);
output.change_current_state( output.change_current_state(
@ -180,8 +205,7 @@ pub fn init_backend(
.handle() .handle()
.insert_source(event_source, move |_, _, data| { .insert_source(event_source, move |_, _, data| {
match input.dispatch_new_events(|event| { match input.dispatch_new_events(|event| {
data.state data.state.process_winit_event(event, &render_ping_handle)
.process_winit_event(&data.display.handle(), event, &render_ping_handle)
}) { }) {
Ok(_) => { Ok(_) => {
event_ping_handle.ping(); event_ping_handle.ping();
@ -189,7 +213,11 @@ pub fn init_backend(
} }
Err(winit::WinitError::WindowClosed) => { Err(winit::WinitError::WindowClosed) => {
let output = data.state.backend.winit().output.clone(); 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() { if let Some(token) = token.take() {
event_loop_handle.remove(token); event_loop_handle.remove(token);
} }
@ -199,27 +227,30 @@ pub fn init_backend(
.map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?; .map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?;
event_ping.ping(); event_ping.ping();
#[cfg(feature = "debug")]
let fps = Fps::new(backend.renderer());
state.backend = BackendData::Winit(WinitState { state.backend = BackendData::Winit(WinitState {
backend, backend,
output: output.clone(), output: output.clone(),
damage_tracker: DamageTrackedRenderer::from_output(&output),
screencopy: Vec::new(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
fps: Fps::default(), fps,
age_reset: 0,
}); });
state state
.common .common
.output_configuration_state .output_configuration_state
.add_heads(std::iter::once(&output)); .add_heads(std::iter::once(&output));
state.common.output_configuration_state.update();
state.common.shell.add_output(&output); state.common.shell.add_output(&output);
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.config.read_outputs( state.common.config.read_outputs(
std::iter::once(&output), &mut state.common.output_configuration_state,
&mut state.backend, &mut state.backend,
&mut state.common.shell, &mut state.common.shell,
seats.iter().cloned(),
&state.common.event_loop_handle, &state.common.event_loop_handle,
); );
state.common.shell.refresh_outputs();
state.common.config.write_outputs(std::iter::once(&output));
Ok(()) Ok(())
} }
@ -227,7 +258,7 @@ pub fn init_backend(
fn init_egl_client_side( fn init_egl_client_side(
dh: &DisplayHandle, dh: &DisplayHandle,
state: &mut State, state: &mut State,
renderer: &mut WinitGraphicsBackend, renderer: &mut WinitGraphicsBackend<GlowRenderer>,
) -> Result<()> { ) -> Result<()> {
let bind_result = renderer.renderer().bind_wl_display(dh); let bind_result = renderer.renderer().bind_wl_display(dh);
match bind_result { match bind_result {
@ -250,19 +281,14 @@ fn init_egl_client_side(
} }
impl State { impl State {
pub fn process_winit_event( pub fn process_winit_event(&mut self, event: WinitEvent, render_ping: &ping::Ping) {
&mut self,
dh: &DisplayHandle,
event: WinitEvent,
render_ping: &ping::Ping,
) {
// here we can handle special cases for winit inputs // here we can handle special cases for winit inputs
match event { match event {
WinitEvent::Focus(true) => { 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(); let devices = seat.user_data().get::<Devices>().unwrap();
if devices.has_device(&WinitVirtualDevice) { if devices.has_device(&WinitVirtualDevice) {
set_active_output(seat, &self.backend.winit().output); seat.set_active_output(&self.backend.winit().output);
break; break;
} }
} }
@ -286,7 +312,7 @@ impl State {
output.delete_mode(output.current_mode().unwrap()); output.delete_mode(output.current_mode().unwrap());
output.set_preferred(mode); output.set_preferred(mode);
output.change_current_state(Some(mode), None, None, None); 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.output_configuration_state.update();
self.common.shell.refresh_outputs(); self.common.shell.refresh_outputs();
render_ping.ping(); render_ping.ping();

View file

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

View file

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

View file

@ -1,22 +1,35 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use std::collections::HashMap;
use crate::state::{Common, Fps}; use crate::state::{Common, Fps};
use egui::{Color32, Vec2};
use smithay::{ use smithay::{
backend::drm::DrmNode, backend::{
desktop::layer_map_for_output, drm::DrmNode,
reexports::wayland_server::Resource, renderer::{
utils::{IsAlive, Physical, Rectangle}, 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( pub fn fps_ui(
gpu: Option<&DrmNode>, gpu: Option<&DrmNode>,
state: &Common, state: &Common,
renderer: &mut GlowRenderer,
fps: &mut Fps, fps: &mut Fps,
area: Rectangle<f64, Physical>, area: Rectangle<i32, Logical>,
scale: f64, scale: f64,
) -> EguiFrame { ) -> Result<TextureRenderElement<Gles2Texture>, Gles2Error> {
use egui::widgets::plot::{Bar, BarChart, HLine, Legend, Plot}; use egui::widgets::plot::{Bar, BarChart, Legend, Plot};
let (max, min, avg, avg_fps) = ( let (max, min, avg, avg_fps) = (
fps.max_frametime().as_secs_f64(), fps.max_frametime().as_secs_f64(),
@ -24,404 +37,154 @@ pub fn fps_ui(
fps.avg_frametime().as_secs_f64(), fps.avg_frametime().as_secs_f64(),
fps.avg_fps(), 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 .frames
.iter() .iter()
.rev() .rev()
.take(30) .take(amount)
.rev() .rev()
.enumerate() .enumerate()
.map(|(i, (_, d))| { .map(|(i, frame)| {
let value = d.as_secs_f64(); let elements_val = frame.duration_elements.as_secs_f64();
let transformed = ((value - min) / (max - min) * 255.0).round() as u8; let render_val = frame.duration_render.as_secs_f64();
Bar::new(i as f64, transformed as f64).fill(egui::Color32::from_rgb( let screencopy_val = frame
transformed, .duration_screencopy
255 - transformed, .as_ref()
0, .map(|val| val.as_secs_f64())
)) .unwrap_or(0.0);
}) let displayed_val = frame.duration_displayed.as_secs_f64();
.collect();
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| { |ctx| {
egui::Area::new("main") egui::Area::new("main")
.anchor(egui::Align2::LEFT_TOP, (10.0, 10.0)) .anchor(egui::Align2::LEFT_TOP, (10.0, 10.0))
.show(ctx, |ui| { .show(ctx, |ui| {
let label_res = ui.label(format!( ui.label(format!(
"cosmic-comp version {}", "cosmic-comp version {}",
std::env!("CARGO_PKG_VERSION") std::env!("CARGO_PKG_VERSION")
)); ));
if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..8)) { if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) {
ui.label(hash); ui.label(format!(": {hash}"));
} }
if !state.egui.active { if !state.egui.active {
ui.label("Press Mod+Escape for debug menu"); ui.label("Press Super+Escape for debug overlay");
} else { } else {
ui.set_max_width(label_res.rect.min.x + label_res.rect.width()); ui.set_max_width(300.0);
ui.separator(); ui.separator();
if let Some(gpu) = gpu { 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(egui::RichText::new(format!("FPS: {:>7.3}", avg_fps)).heading());
ui.label("Frame Times:"); ui.label("Frame Times:");
ui.label(egui::RichText::new(format!("avg: {:>7.6}", avg)).code()); 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!("min: {:>7.6}", min)).code());
ui.label(egui::RichText::new(format!("max: {:>7.6}", max)).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") Plot::new("FPS")
.legend(Legend::default()) .legend(Legend::default())
.view_aspect(33.0) .view_aspect(50.0)
.include_x(0.0) .include_x(0.0)
.include_x(30.0) .include_x(amount as f64)
.include_y(0.0) .include_y(0.0)
.include_y(300.0) .include_y(300)
.show_x(false) .show_x(false)
.show(ui, |plot_ui| { .show(ui, |plot_ui| {
plot_ui.bar_chart(fps_chart); plot_ui.bar_chart(elements_chart);
plot_ui.hline( plot_ui.bar_chart(render_chart);
HLine::new(avg) plot_ui.bar_chart(screencopy_chart);
.highlight(true) plot_ui.bar_chart(display_chart);
.color(egui::Color32::LIGHT_BLUE),
);
}); });
} }
}); });
}, },
renderer,
area, area,
scale, scale,
1.0, 0.8,
&state.start_time, state.clock.now().into(),
fps.modifiers.clone(),
) )
} }
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 // SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "debug")]
use std::{
collections::VecDeque,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};
use anyhow::Result; use anyhow::Result;
use slog::Drain; 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 { pub struct LogState {
_guard: slog_scope::GlobalLoggerGuard, _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> { pub fn init_logger() -> Result<LogState> {
let decorator = slog_term::TermDecorator::new().stderr().build(); 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, // 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. // to make sure we do not miss any in-flight messages, when we crash.
#[cfg(not(feature = "debug"))]
let logger = slog::Logger::root( let logger = slog::Logger::root(
std::sync::Mutex::new( std::sync::Mutex::new(
slog_term::CompactFormat::new(decorator) slog_term::CompactFormat::new(decorator)
@ -108,21 +20,6 @@ pub fn init_logger() -> Result<LogState> {
.fuse(), .fuse(),
slog::o!(), 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); let _guard = slog_scope::set_global_logger(logger);
slog_stdlog::init().unwrap(); slog_stdlog::init().unwrap();
@ -135,11 +32,5 @@ pub fn init_logger() -> Result<LogState> {
); );
} }
Ok(LogState { Ok(LogState { _guard })
_guard,
#[cfg(feature = "debug")]
debug_buffer,
#[cfg(feature = "debug")]
dirty_flag,
})
} }

View file

@ -9,11 +9,7 @@ use smithay::{
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use std::{ use std::{ffi::OsString, os::unix::prelude::AsRawFd, sync::Arc};
ffi::OsString,
os::unix::prelude::AsRawFd,
sync::{atomic::Ordering, Arc},
};
pub mod backend; pub mod backend;
pub mod config; pub mod config;
@ -68,18 +64,9 @@ fn main() -> Result<()> {
} }
// trigger routines // trigger routines
data.state.common.shell.refresh(&data.display.handle()); data.state.common.shell.refresh();
state::Common::refresh_focus(&mut data.state); 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 // send out events
let _ = data.display.flush_clients(); 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::{ use crate::{
shell::{OutputBoundState, Shell, Workspace, WorkspaceMode}, shell::{element::CosmicMapped, Shell, Workspace},
state::Common, state::Common,
utils::prelude::*, utils::prelude::*,
wayland::handlers::xdg_shell::PopupGrabData, wayland::handlers::xdg_shell::PopupGrabData,
}; };
use indexmap::IndexSet; use indexmap::IndexSet;
use smithay::{ use smithay::{
desktop::{layer_map_for_output, PopupUngrabStrategy, Window, WindowSurfaceType}, desktop::{layer_map_for_output, PopupUngrabStrategy},
input::Seat, input::Seat,
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{IsAlive, Serial, SERIAL_COUNTER}, 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; pub mod target;
@ -32,80 +26,59 @@ pub enum FocusDirection {
Out, Out,
} }
pub struct FocusStack<'a>(Ref<'a, IndexSet<Window>>); pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet<CosmicMapped>>);
pub struct FocusStackMut<'a>(RefMut<'a, IndexSet<Window>>); pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet<CosmicMapped>);
impl<'a> FocusStack<'a> { impl<'a> FocusStack<'a> {
pub fn last(&self) -> Option<Window> { pub fn last(&self) -> Option<&CosmicMapped> {
self.0.iter().rev().find(|w| w.toplevel().alive()).cloned() self.0
.as_ref()
.and_then(|set| set.iter().rev().find(|w| w.alive()))
} }
pub fn iter(&self) -> impl Iterator<Item = &'_ Window> { pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
self.0.iter().rev().filter(|w| w.toplevel().alive()) self.0
.iter()
.flat_map(|set| set.iter().rev().filter(|w| w.alive()))
} }
} }
impl<'a> FocusStackMut<'a> { impl<'a> FocusStackMut<'a> {
pub fn append(&mut self, window: &Window) { pub fn append(&mut self, window: &CosmicMapped) {
self.0.retain(|w| w.toplevel().alive()); self.0.retain(|w| w.alive());
self.0.shift_remove(window); self.0.shift_remove(window);
self.0.insert(window.clone()); self.0.insert(window.clone());
} }
pub fn last(&self) -> Option<Window> { pub fn last(&self) -> Option<&CosmicMapped> {
self.0.iter().rev().find(|w| w.toplevel().alive()).cloned() self.0.iter().rev().find(|w| w.alive())
} }
pub fn iter(&self) -> impl Iterator<Item = &'_ Window> { pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
self.0.iter().rev().filter(|w| w.toplevel().alive()) self.0.iter().rev().filter(|w| w.alive())
} }
} }
type FocusStackData = RefCell<(HashMap<u8, IndexSet<Window>>, IndexSet<Window>)>; impl Workspace {}
impl Workspace { pub struct ActiveFocus(RefCell<Option<KeyboardFocusTarget>>);
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>>);
impl ActiveFocus { impl ActiveFocus {
fn set(seat: &Seat<State>, surface: Option<WlSurface>) { fn set(seat: &Seat<State>, target: Option<KeyboardFocusTarget>) {
if !seat if !seat
.user_data() .user_data()
.insert_if_missing(|| ActiveFocus(RefCell::new(surface.clone()))) .insert_if_missing(|| ActiveFocus(RefCell::new(target.clone())))
{ {
*seat *seat
.user_data() .user_data()
.get::<ActiveFocus>() .get::<ActiveFocus>()
.unwrap() .unwrap()
.0 .0
.borrow_mut() = surface; .borrow_mut() = target;
} }
} }
fn get(seat: &Seat<State>) -> Option<WlSurface> { fn get(seat: &Seat<State>) -> Option<KeyboardFocusTarget> {
seat.user_data() seat.user_data()
.get::<ActiveFocus>() .get::<ActiveFocus>()
.and_then(|a| a.0.borrow().clone()) .and_then(|a| a.0.borrow().clone())
@ -115,30 +88,25 @@ impl ActiveFocus {
impl Shell { impl Shell {
pub fn set_focus<'a>( pub fn set_focus<'a>(
state: &mut State, state: &mut State,
surface: Option<&WlSurface>, target: Option<&KeyboardFocusTarget>,
active_seat: &Seat<State>, active_seat: &Seat<State>,
serial: Option<Serial>, serial: Option<Serial>,
) { ) {
// update FocusStack and notify layouts about new focus (if any window) // update FocusStack and notify layouts about new focus (if any window)
if let Some(surface) = surface { if let Some(KeyboardFocusTarget::Element(mapped)) = target {
if let Some(workspace) = state.common.shell.space_for_window_mut(surface) { if let Some(workspace) = state.common.shell.space_for_mut(mapped) {
if let Some(window) = workspace let mut focus_stack = workspace.focus_stack.get_mut(active_seat);
.space if Some(mapped) != focus_stack.last() {
.window_for_surface(surface, WindowSurfaceType::ALL) slog_scope::debug!("Focusing window: {:?}", mapped);
{ focus_stack.append(mapped);
let mut focus_stack = workspace.focus_stack_mut(active_seat); // also remove popup grabs, if we are switching focus
if Some(window) != focus_stack.last().as_ref() { if let Some(mut popup_grab) = active_seat
slog_scope::debug!("Focusing window: {:?}", window); .user_data()
focus_stack.append(window); .get::<PopupGrabData>()
// also remove popup grabs, if we are switching focus .and_then(|x| x.take())
if let Some(mut popup_grab) = active_seat {
.user_data() if !popup_grab.has_ended() {
.get::<PopupGrabData>() popup_grab.ungrab(PopupUngrabStrategy::All);
.and_then(|x| x.take())
{
if !popup_grab.has_ended() {
popup_grab.ungrab(PopupUngrabStrategy::All);
}
} }
} }
} }
@ -147,10 +115,10 @@ impl Shell {
// update keyboard focus // update keyboard focus
if let Some(keyboard) = active_seat.get_keyboard() { if let Some(keyboard) = active_seat.get_keyboard() {
ActiveFocus::set(active_seat, surface.cloned()); ActiveFocus::set(active_seat, target.cloned());
keyboard.set_focus( keyboard.set_focus(
state, state,
surface.cloned(), target.cloned(),
serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()), serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()),
); );
} }
@ -160,30 +128,23 @@ impl Shell {
// update activate status // update activate status
let focused_windows = seats let focused_windows = seats
.flat_map(|seat| { .flat_map(|seat| {
self.outputs self.outputs.iter().flat_map(|o| {
.iter() let space = self.active_space(o);
.flat_map(|o| self.active_space(o).focus_stack(seat).last().clone()) let stack = space.focus_stack.get(seat);
stack.last().cloned()
})
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for output in self.outputs.iter() { for output in self.outputs.iter() {
let workspace = match &self.workspace_mode { let workspace = self.workspaces.active_mut(output);
WorkspaceMode::OutputBound => {
let active = output
.user_data()
.get::<OutputBoundState>()
.unwrap()
.active
.get();
&mut self.spaces[active]
}
WorkspaceMode::Global { active, .. } => &mut self.spaces[*active],
};
for focused in focused_windows.iter() { 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() { for window in workspace.mapped() {
window.set_activated(focused_windows.contains(window)); window.set_activated(focused_windows.contains(&window));
window.configure(); window.configure();
} }
} }
@ -193,55 +154,54 @@ impl Shell {
impl Common { impl Common {
pub fn set_focus( pub fn set_focus(
state: &mut State, state: &mut State,
surface: Option<&WlSurface>, target: Option<&KeyboardFocusTarget>,
active_seat: &Seat<State>, active_seat: &Seat<State>,
serial: Option<Serial>, serial: Option<Serial>,
) { ) {
Shell::set_focus(state, surface, active_seat, serial); Shell::set_focus(state, target, active_seat, serial);
state.common.shell.update_active(state.common.seats.iter()); let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.shell.update_active(seats.iter());
} }
pub fn refresh_focus(state: &mut State) { 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 { 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); let last_known_focus = ActiveFocus::get(&seat);
if let Some(surface) = last_known_focus { if let Some(target) = last_known_focus {
if surface.alive() { if target.alive() {
let is_toplevel = matches!(get_role(&surface), Some(XDG_TOPLEVEL_ROLE)); match target {
let is_layer = matches!(get_role(&surface), Some(LAYER_SURFACE_ROLE)); KeyboardFocusTarget::Element(mapped) => {
let workspace = state.common.shell.active_space(&output);
if let Some(popup) = state.common.shell.popups.find_popup(&surface) { let focus_stack = workspace.focus_stack.get(&seat);
if popup.alive() { if focus_stack.last().map(|m| m == &mapped).unwrap_or(false) {
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) {
continue; // Focus is valid continue; // Focus is valid
} else { } else {
slog_scope::debug!("Wrong Window, focus fixup"); slog_scope::debug!("Wrong Window, focus fixup");
} }
} else {
slog_scope::debug!("Different workspaces Window, focus fixup");
} }
} else { KeyboardFocusTarget::LayerSurface(layer) => {
// unknown surface type, fixup if layer_map_for_output(&output).layers().any(|l| l == &layer) {
slog_scope::debug!("Surface unmapped, focus fixup"); 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 { } else {
slog_scope::debug!("Surface dead, focus fixup"); slog_scope::debug!("Surface dead, focus fixup");
} }
@ -263,21 +223,24 @@ impl Common {
} }
// update keyboard focus // update keyboard focus
let surface = state let target = state
.common .common
.shell .shell
.active_space(&output) .active_space(&output)
.focus_stack(&seat) .focus_stack
.get(&seat)
.last() .last()
.map(|w| w.toplevel().wl_surface().clone()); .cloned()
.map(KeyboardFocusTarget::from);
if let Some(keyboard) = seat.get_keyboard() { if let Some(keyboard) = seat.get_keyboard() {
slog_scope::info!("restoring focus to: {:?}", surface.as_ref()); slog_scope::info!("restoring focus to: {:?}", target.as_ref());
keyboard.set_focus(state, surface.clone(), SERIAL_COUNTER.next_serial()); keyboard.set_focus(state, target.clone(), SERIAL_COUNTER.next_serial());
ActiveFocus::set(&seat, surface); 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::*; use std::sync::Weak;
pub use smithay::{
use crate::{shell::element::CosmicMapped, utils::prelude::*};
use id_tree::NodeId;
use smithay::{
backend::input::KeyState, backend::input::KeyState,
desktop::{LayerSurface, PopupKind, Window}, desktop::{LayerSurface, PopupKind, Window},
input::{ input::{
@ -7,83 +10,131 @@ pub use smithay::{
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget},
Seat, Seat,
}, },
output::WeakOutput,
reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource}, reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource},
utils::{IsAlive, Serial}, utils::{IsAlive, Serial},
wayland::seat::WaylandFocus, wayland::seat::WaylandFocus,
}; };
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum FocusTarget { pub enum PointerFocusTarget {
Window(Window), Element(CosmicMapped),
Fullscreen(Window),
LayerSurface(LayerSurface), LayerSurface(LayerSurface),
Popup(PopupKind), Popup(PopupKind),
} }
impl IsAlive for FocusTarget { #[derive(Debug, Clone, PartialEq)]
fn alive(&self) -> bool { pub enum KeyboardFocusTarget {
match self { Element(CosmicMapped),
FocusTarget::Window(w) => w.alive(), Fullscreen(Window),
FocusTarget::LayerSurface(l) => l.alive(), Group(WindowGroup),
FocusTarget::Popup(p) => p.alive(), 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) { fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
match self { match self {
FocusTarget::Window(w) => { PointerFocusTarget::Element(w) => PointerTarget::enter(w, seat, data, event),
PointerTarget::enter(w.toplevel().wl_surface(), seat, data, event) PointerFocusTarget::Fullscreen(w) => PointerTarget::enter(w, seat, data, event),
} PointerFocusTarget::LayerSurface(l) => PointerTarget::enter(l, seat, data, event),
FocusTarget::LayerSurface(l) => PointerTarget::enter(l.wl_surface(), seat, data, event), PointerFocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event),
FocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event),
} }
} }
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) { fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
match self { match self {
FocusTarget::Window(w) => { PointerFocusTarget::Element(w) => PointerTarget::motion(w, seat, data, event),
PointerTarget::motion(w.toplevel().wl_surface(), 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) { fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
match self { match self {
FocusTarget::Window(w) => { PointerFocusTarget::Element(w) => PointerTarget::button(w, seat, data, event),
PointerTarget::button(w.toplevel().wl_surface(), 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) { fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
match self { match self {
FocusTarget::Window(w) => { PointerFocusTarget::Element(w) => PointerTarget::axis(w, seat, data, frame),
PointerTarget::axis(w.toplevel().wl_surface(), seat, data, frame) PointerFocusTarget::Fullscreen(w) => PointerTarget::axis(w, seat, data, frame),
} PointerFocusTarget::LayerSurface(l) => PointerTarget::axis(l, seat, data, frame),
FocusTarget::LayerSurface(l) => PointerTarget::axis(l.wl_surface(), seat, data, frame), PointerFocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame),
FocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame),
} }
} }
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) { fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
match self { match self {
FocusTarget::Window(w) => { PointerFocusTarget::Element(w) => PointerTarget::leave(w, seat, data, serial, time),
PointerTarget::leave(w.toplevel().wl_surface(), 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) => { PointerFocusTarget::Popup(p) => {
PointerTarget::leave(l.wl_surface(), seat, data, serial, time) 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( fn enter(
&self, &self,
seat: &Seat<State>, seat: &Seat<State>,
@ -92,26 +143,28 @@ impl KeyboardTarget<State> for FocusTarget {
serial: Serial, serial: Serial,
) { ) {
match self { match self {
FocusTarget::Window(w) => { KeyboardFocusTarget::Element(w) => KeyboardTarget::enter(w, seat, data, keys, serial),
KeyboardTarget::enter(w.toplevel().wl_surface(), seat, data, keys, serial) KeyboardFocusTarget::Fullscreen(w) => {
KeyboardTarget::enter(w, seat, data, keys, serial)
} }
FocusTarget::LayerSurface(l) => { KeyboardFocusTarget::Group(_) => {}
KeyboardTarget::enter(l.wl_surface(), seat, data, keys, serial) 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) KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial)
} }
} }
} }
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial) { fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial) {
match self { match self {
FocusTarget::Window(w) => { KeyboardFocusTarget::Element(w) => KeyboardTarget::leave(w, seat, data, serial),
KeyboardTarget::leave(w.toplevel().wl_surface(), 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( fn key(
@ -124,19 +177,17 @@ impl KeyboardTarget<State> for FocusTarget {
time: u32, time: u32,
) { ) {
match self { match self {
FocusTarget::Window(w) => KeyboardTarget::key( KeyboardFocusTarget::Element(w) => {
w.toplevel().wl_surface(), KeyboardTarget::key(w, seat, data, key, state, serial, time)
seat,
data,
key,
state,
serial,
time,
),
FocusTarget::LayerSurface(l) => {
KeyboardTarget::key(l.wl_surface(), 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) KeyboardTarget::key(p.wl_surface(), seat, data, key, state, serial, time)
} }
} }
@ -149,50 +200,113 @@ impl KeyboardTarget<State> for FocusTarget {
serial: Serial, serial: Serial,
) { ) {
match self { match self {
FocusTarget::Window(w) => { KeyboardFocusTarget::Element(w) => {
KeyboardTarget::modifiers(w.toplevel().wl_surface(), seat, data, modifiers, serial) KeyboardTarget::modifiers(w, seat, data, modifiers, serial)
} }
FocusTarget::LayerSurface(l) => { KeyboardFocusTarget::Fullscreen(w) => {
KeyboardTarget::modifiers(l.wl_surface(), seat, data, modifiers, serial) 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) KeyboardTarget::modifiers(p.wl_surface(), seat, data, modifiers, serial)
} }
} }
} }
} }
impl WaylandFocus for FocusTarget { impl WaylandFocus for KeyboardFocusTarget {
fn wl_surface(&self) -> Option<&WlSurface> { fn wl_surface(&self) -> Option<WlSurface> {
Some(match self { match self {
FocusTarget::Window(w) => w.toplevel().wl_surface(), KeyboardFocusTarget::Element(w) => WaylandFocus::wl_surface(w),
FocusTarget::LayerSurface(l) => l.wl_surface(), KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w),
FocusTarget::Popup(p) => p.wl_surface(), 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 { fn same_client_as(&self, object_id: &ObjectId) -> bool {
match self { match self {
FocusTarget::Window(w) => w.toplevel().wl_surface().id().same_client_as(object_id), KeyboardFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id),
FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id),
FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(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 { 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 { 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 { 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::{ use smithay::{
backend::renderer::{ImportAll, Renderer}, input::pointer::{
desktop::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
draw_window, PointerInnerHandle,
space::{RenderElement, SpaceOutputTuple},
Kind, Window,
}, },
input::{ reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
pointer::{ utils::{Logical, Point},
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},
}; };
use std::cell::RefCell;
impl Shell { use crate::state::State;
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;
}
let pos = pointer.current_location(); use super::{
let output = match workspace focus::target::PointerFocusTarget,
.space layout::{floating::ResizeSurfaceGrab, tiling::ResizeForkGrab},
.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();
match &window.toplevel() { bitflags::bitflags! {
Kind::Xdg(surface) => { pub struct ResizeEdge: u32 {
// If surface is maximized then unmaximize it const TOP = 0b0001;
let current_state = surface.current_state(); const BOTTOM = 0b0010;
if current_state.states.contains(XdgState::Maximized) { const LEFT = 0b0100;
workspace const RIGHT = 0b1000;
.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;
initial_window_location = new_size const TOP_LEFT = Self::TOP.bits | Self::LEFT.bits;
.map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into()) const BOTTOM_LEFT = Self::BOTTOM.bits | Self::LEFT.bits;
.unwrap_or_else(|| pos)
.to_i32_round();
}
}
};
let was_tiled = if workspace.tiling_layer.windows.contains(&window) { const TOP_RIGHT = Self::TOP.bits | Self::RIGHT.bits;
workspace const BOTTOM_RIGHT = Self::BOTTOM.bits | Self::RIGHT.bits;
.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);
}
}
}
} }
} }
pub type SeatMoveGrabState = RefCell<Option<MoveGrabState>>; impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
#[inline]
pub struct MoveGrabState { fn from(x: xdg_toplevel::ResizeEdge) -> Self {
window: Window, Self::from_bits(x.into()).unwrap()
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 MoveGrabState { impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
pub fn render<I>(&self, seat: &Seat<State>, output: &Output) -> Option<I> #[inline]
where fn from(x: ResizeEdge) -> Self {
I: From<MoveGrabRenderElement>, Self::try_from(x.bits()).unwrap()
{
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(),
}))
} }
} }
pub struct MoveSurfaceGrab { pub enum ResizeGrab {
window: Window, Floating(ResizeSurfaceGrab),
start_data: PointerGrabStartData<State>, Tiling(ResizeForkGrab),
seat: Seat<State>,
} }
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( fn motion(
&mut self, &mut self,
state: &mut State, data: &mut State,
handle: &mut PointerInnerHandle<'_, State>, handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(WlSurface, Point<i32, Logical>)>, focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
event: &MotionEvent, event: &MotionEvent,
) { ) {
// While the grab is active, no client has pointer focus match self {
handle.motion(state, None, event); ResizeGrab::Floating(grab) => grab.motion(data, handle, focus, event),
if !self.window.alive() { ResizeGrab::Tiling(grab) => grab.motion(data, handle, focus, event),
self.ungrab(state, handle, event.serial, event.time);
} }
} }
fn button( fn button(
&mut self, &mut self,
state: &mut State, data: &mut State,
handle: &mut PointerInnerHandle<'_, State>, handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent, event: &ButtonEvent,
) { ) {
handle.button(state, event); match self {
if handle.current_pressed().is_empty() { ResizeGrab::Floating(grab) => grab.button(data, handle, event),
self.ungrab(state, handle, event.serial, event.time); ResizeGrab::Tiling(grab) => grab.button(data, handle, event),
} }
} }
fn axis( fn axis(
&mut self, &mut self,
state: &mut State, data: &mut State,
handle: &mut PointerInnerHandle<'_, State>, handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame, details: AxisFrame,
) { ) {
handle.axis(state, details); match self {
} ResizeGrab::Floating(grab) => grab.axis(data, handle, details),
ResizeGrab::Tiling(grab) => grab.axis(data, handle, 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(),
} }
} }
fn ungrab( fn start_data(&self) -> &PointerGrabStartData<State> {
&mut self, match self {
state: &mut State, ResizeGrab::Floating(grab) => grab.start_data(),
handle: &mut PointerInnerHandle<'_, State>, ResizeGrab::Tiling(grab) => grab.start_data(),
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);
} }
} }

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 // SPDX-License-Identifier: GPL-3.0-only
use smithay::{ use smithay::{
desktop::{layer_map_for_output, space::RenderZindex, Kind, Space, Window}, backend::renderer::{element::RenderElement, ImportAll, Renderer},
input::{ desktop::{layer_map_for_output, space::SpaceElement, Space, Window},
pointer::{Focus, GrabStartData as PointerGrabStartData}, input::{pointer::GrabStartData as PointerGrabStartData, Seat},
Seat,
},
output::Output, output::Output,
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{ utils::{Logical, Point, Rectangle, Serial},
ResizeEdge, State as XdgState,
},
utils::{IsAlive, Logical, Point, Rectangle, Serial},
wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes},
}; };
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; mod grabs;
pub use self::grabs::*; pub use self::grabs::*;
pub const FLOATING_INDEX: u8 = RenderZindex::Shell as u8 + 1; #[derive(Debug)]
#[derive(Debug, Default)]
pub struct FloatingLayout { pub struct FloatingLayout {
pending_windows: Vec<Window>, pub(in crate::shell) space: Space<CosmicMapped>,
pub windows: HashSet<Window>,
} }
#[derive(Default)] impl Default for FloatingLayout {
pub struct WindowUserDataInner { fn default() -> Self {
last_geometry: Rectangle<i32, Logical>, FloatingLayout {
space: Space::new(None),
}
}
} }
pub type WindowUserData = Mutex<WindowUserDataInner>;
impl FloatingLayout { impl FloatingLayout {
pub fn new() -> FloatingLayout { pub fn new() -> FloatingLayout {
Default::default() 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, &mut self,
space: &mut Space, mapped: impl Into<CosmicMapped>,
window: Window,
seat: &Seat<State>, seat: &Seat<State>,
position: impl Into<Option<Point<i32, Logical>>>, position: impl Into<Option<Point<i32, Logical>>>,
) { ) {
if let Some(output) = super::output_from_seat(Some(seat), space) { let mapped = mapped.into();
self.map_window_internal(space, window, &output, position.into()); let output = seat.active_output();
} else { let position = position.into();
self.pending_windows.push(window);
} self.map_internal(mapped, &output, position)
} }
pub fn refresh(&mut self, space: &mut Space) { pub(in crate::shell) fn map_internal(
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(
&mut self, &mut self,
space: &mut Space, mapped: CosmicMapped,
window: Window,
output: &Output, output: &Output,
position: Option<Point<i32, Logical>>, position: Option<Point<i32, Logical>>,
) { ) {
let last_geometry = window let mut win_geo = mapped.geometry();
.user_data()
.get::<WindowUserData>()
.map(|u| u.lock().unwrap().last_geometry);
let mut win_geo = window.geometry();
let layers = layer_map_for_output(&output); let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone(); let geometry = layers.non_exclusive_zone();
let last_geometry = mapped.last_geometry.lock().unwrap().clone();
let mut geo_updated = false; let mut geo_updated = false;
if let Some(size) = last_geometry.clone().map(|g| g.size) { if let Some(size) = last_geometry.map(|g| g.size) {
geo_updated = win_geo.size == size; geo_updated = win_geo.size != size;
win_geo.size = size; win_geo.size = size;
} }
{ {
let (min_size, max_size) = with_states(window.toplevel().wl_surface(), |states| { let (min_size, max_size) = (mapped.min_size(), mapped.max_size());
let attrs = states
.data_map
.get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap();
(attrs.min_size, attrs.max_size)
});
if win_geo.size.w > geometry.size.w / 3 * 2 { if win_geo.size.w > geometry.size.w / 3 * 2 {
// try a more reasonable size // try a more reasonable size
let mut width = geometry.size.w / 3 * 2; let mut width = geometry.size.w / 3 * 2;
@ -136,124 +124,188 @@ impl FloatingLayout {
.into() .into()
}); });
#[allow(irrefutable_let_patterns)] mapped.set_tiled(false);
if let Kind::Xdg(xdg) = &window.toplevel() { if geo_updated {
xdg.with_pending_state(|state| { mapped.set_size(win_geo.size);
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.configure();
space.map_window(&window, position, FLOATING_INDEX, false); self.space.map_element(mapped, position, false);
self.windows.insert(window);
} }
pub fn unmap_window(&mut self, space: &mut Space, window: &Window) { pub fn unmap(&mut self, window: &CosmicMapped) -> bool {
#[allow(irrefutable_let_patterns)] #[allow(irrefutable_let_patterns)]
let is_maximized = match &window.toplevel() { let is_maximized = window.is_maximized();
Kind::Xdg(surface) => {
surface.with_pending_state(|state| state.states.contains(XdgState::Maximized))
}
};
if !is_maximized { if !is_maximized {
if let Some(location) = space.window_location(window) { if let Some(location) = self.space.element_location(window) {
let user_data = window.user_data(); *window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
user_data.insert_if_missing(|| WindowUserData::default()); location,
user_data window.geometry().size,
.get::<WindowUserData>() ));
.unwrap()
.lock()
.unwrap()
.last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size);
} }
} }
space.unmap_window(window); let was_unmaped = self.space.elements().any(|e| e == window);
self.pending_windows.retain(|w| w != window); self.space.unmap_elem(&window);
self.windows.remove(window); was_unmaped
} }
pub fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) { pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Logical>> {
let layers = layer_map_for_output(&output); self.space.element_geometry(elem)
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 unmaximize_request(&mut self, space: &mut Space, window: &Window) { pub fn maximize_request(&mut self, window: &Window) {
let last_geometry = window if let Some(mapped) = self
.user_data() .space
.get::<WindowUserData>() .elements()
.map(|u| u.lock().unwrap().last_geometry); .find(|m| m.windows().any(|(w, _)| &w == window))
match window.toplevel() { {
Kind::Xdg(toplevel) => { if let Some(location) = self.space.element_location(mapped) {
toplevel.with_pending_state(|state| { *mapped.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
state.states.unset(XdgState::Maximized); location,
state.size = last_geometry.map(|g| g.size); mapped.geometry().size,
}); ));
toplevel.send_configure();
} }
} }
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( pub fn resize_request(
state: &mut State, &mut self,
window: &Window, mapped: &CosmicMapped,
seat: &Seat<State>, seat: &Seat<State>,
serial: Serial, _serial: Serial,
start_data: PointerGrabStartData<State>, start_data: PointerGrabStartData<State>,
edges: ResizeEdge, edges: ResizeEdge,
) { ) -> Option<ResizeSurfaceGrab> {
// it is so stupid, that we have to do this here. TODO: Refactor grabs if seat.get_pointer().is_some() {
let workspace = state let location = self.space.element_location(&mapped).unwrap();
.common let size = mapped.geometry().size;
.shell
.space_for_window_mut(window.toplevel().wl_surface())
.unwrap();
let space = &mut workspace.space;
if let Some(pointer) = seat.get_pointer() { Some(grabs::ResizeSurfaceGrab::new(
let location = space.window_location(&window).unwrap(); start_data,
let size = window.geometry().size; mapped.clone(),
edges,
let grab = location,
grabs::ResizeSurfaceGrab::new(start_data, window.clone(), edges, location, size); size,
))
pointer.set_grab(state, grab, serial, Focus::Clear); } 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 // SPDX-License-Identifier: GPL-3.0-only
use crate::{input::ActiveOutput, state::State};
use regex::RegexSet; use regex::RegexSet;
use smithay::{ use smithay::{
desktop::{Space, Window}, desktop::Window,
input::Seat,
output::Output,
wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes}, wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes},
}; };
use std::sync::Mutex; use std::sync::Mutex;
@ -19,6 +16,16 @@ pub enum Orientation {
Vertical, 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! { lazy_static::lazy_static! {
static ref EXCEPTIONS_APPID: RegexSet = RegexSet::new(&[ static ref EXCEPTIONS_APPID: RegexSet = RegexSet::new(&[
r"Authy Desktop", r"Authy Desktop",
@ -118,17 +125,3 @@ pub fn should_be_floating(window: &Window) -> bool {
false 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 // SPDX-License-Identifier: GPL-3.0-only
use crate::{shell::layout::Orientation, utils::prelude::*}; use crate::{
use atomic_float::AtomicF64; shell::{focus::target::PointerFocusTarget, layout::Orientation},
utils::prelude::*,
};
use id_tree::NodeId;
use smithay::{ use smithay::{
input::pointer::{ input::pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle, PointerInnerHandle,
}, },
reexports::wayland_server::protocol::wl_surface::WlSurface, output::{Output, WeakOutput},
utils::{Logical, Point, Size}, utils::{Logical, Point},
}; };
use std::sync::{atomic::Ordering, Arc};
use super::Data;
pub struct ResizeForkGrab { pub struct ResizeForkGrab {
pub start_data: PointerGrabStartData<State>, start_data: PointerGrabStartData<State>,
pub orientation: Orientation, idx: usize,
pub initial_size: Size<i32, Logical>, initial_size_upleft: i32,
pub initial_ratio: f64, initial_size_downright: i32,
pub ratio: Arc<AtomicF64>, 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 { impl PointerGrab<State> for ResizeForkGrab {
@ -25,21 +54,139 @@ impl PointerGrab<State> for ResizeForkGrab {
&mut self, &mut self,
data: &mut State, data: &mut State,
handle: &mut PointerInnerHandle<'_, State>, handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(WlSurface, Point<i32, Logical>)>, _focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
event: &MotionEvent, event: &MotionEvent,
) { ) {
// While the grab is active, no client has pointer focus // While the grab is active, no client has pointer focus
handle.motion(data, None, event); handle.motion(data, None, event);
let delta = event.location - self.start_data.location; let delta = event.location - self.start_data.location;
let delta = match self.orientation {
Orientation::Vertical => delta.x / self.initial_size.w as f64, if let Some(output) = self.output.upgrade() {
Orientation::Horizontal => delta.y / self.initial_size.h as f64, let tiling_layer = &mut data.common.shell.active_space_mut(&output).tiling_layer;
}; if let Some(tree) = tiling_layer.trees.get_mut(&output) {
self.ratio.store( if tree.get(&self.node).is_ok() {
0.9f64.min(0.1f64.max(self.initial_ratio + delta)), let orientation = tree.get(&self.node).unwrap().data().orientation();
Ordering::SeqCst, 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( 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::{ use crate::{
shell::layout::{floating::FloatingLayout, tiling::TilingLayout}, backend::render::element::{AsGles2Frame, AsGlowRenderer},
shell::layout::{
floating::{FloatingLayout, MoveSurfaceGrab},
tiling::TilingLayout,
},
state::State, 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::{ 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}, input::{pointer::GrabStartData as PointerGrabStartData, Seat},
output::Output, output::Output,
reexports::{ reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, 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 std::collections::HashMap;
use super::{
element::CosmicMapped,
focus::{FocusStack, FocusStackMut},
grabs::ResizeGrab,
CosmicMappedRenderElement,
};
#[derive(Debug)]
pub struct Workspace { pub struct Workspace {
pub idx: u8,
pub space: Space,
pub tiling_layer: TilingLayout, pub tiling_layer: TilingLayout,
pub floating_layer: FloatingLayout, pub floating_layer: FloatingLayout,
tiling_enabled: bool, pub tiling_enabled: bool,
pub fullscreen: HashMap<String, Window>, pub fullscreen: HashMap<Output, Window>,
pub handle: WorkspaceHandle, 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 { impl Workspace {
pub fn new(idx: u8, handle: WorkspaceHandle) -> Workspace { pub fn new(handle: WorkspaceHandle) -> Workspace {
Workspace { Workspace {
idx,
space: Space::new(None),
tiling_layer: TilingLayout::new(), tiling_layer: TilingLayout::new(),
floating_layer: FloatingLayout::new(), floating_layer: FloatingLayout::new(),
tiling_enabled: true, tiling_enabled: true,
fullscreen: HashMap::new(), fullscreen: HashMap::new(),
handle, handle,
focus_stack: FocusStacks::default(),
pending_buffers: Vec::new(),
screencopy_sessions: Vec::new(),
} }
} }
pub fn refresh(&mut self, dh: &DisplayHandle) { pub fn refresh(&mut self) {
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);
}
self.fullscreen.retain(|_, w| w.alive()); self.fullscreen.retain(|_, w| w.alive());
self.floating_layer.refresh(&mut self.space); self.floating_layer.refresh();
self.tiling_layer.refresh(&mut self.space); self.tiling_layer.refresh();
self.space.refresh(dh); }
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) { 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; 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) { pub fn unmaximize_request(&mut self, window: &Window) {
if self.fullscreen.values().any(|w| w == window) { if self.fullscreen.values().any(|w| w == window) {
return self.unfullscreen_request(window); self.unfullscreen_request(window);
} self.floating_layer.unmaximize_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)
} }
} }
pub fn fullscreen_request(&mut self, window: &Window, output: &Output) { pub fn fullscreen_request(&mut self, window: &Window, output: &Output) {
if self.fullscreen.contains_key(&output.name()) { if self.fullscreen.contains_key(output) {
return; return;
} }
@ -111,6 +223,24 @@ impl Workspace {
if let Kind::Xdg(xdg) = &window.toplevel() { if let Kind::Xdg(xdg) = &window.toplevel() {
xdg.with_pending_state(|state| { xdg.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen); 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( state.size = Some(
output output
.current_mode() .current_mode()
@ -123,8 +253,8 @@ impl Workspace {
}); });
xdg.send_configure(); 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) { pub fn unfullscreen_request(&mut self, window: &Window) {
@ -133,45 +263,127 @@ impl Workspace {
if let Kind::Xdg(xdg) = &window.toplevel() { if let Kind::Xdg(xdg) = &window.toplevel() {
xdg.with_pending_state(|state| { xdg.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen); state.states.unset(xdg_toplevel::State::Fullscreen);
state.states.unset(xdg_toplevel::State::Maximized);
state.size = None; state.size = None;
}); });
self.floating_layer.refresh(&mut self.space); self.floating_layer.refresh();
self.tiling_layer.refresh(&mut self.space); self.tiling_layer.refresh();
xdg.send_configure(); xdg.send_configure();
} }
self.fullscreen.retain(|_, w| w != window); self.fullscreen.retain(|_, w| w != window);
} }
} }
pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) { pub fn maximize_toggle(&mut self, window: &Window, output: &Output) {
if self.fullscreen.contains_key(&output.name()) { if self.fullscreen.contains_key(output) {
self.unfullscreen_request(window) self.unmaximize_request(window)
} else { } else {
self.fullscreen_request(window, output) self.maximize_request(window, output)
} }
} }
pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> { 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; 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>) { pub fn toggle_tiling(&mut self, seat: &Seat<State>) {
if self.tiling_enabled { if self.tiling_enabled {
for window in self.tiling_layer.windows.clone().into_iter() { for window in self
self.tiling_layer.unmap_window(&mut self.space, &window); .tiling_layer
self.floating_layer .mapped()
.map_window(&mut self.space, window, seat, None); .map(|(_, m, _)| m.clone())
.collect::<Vec<_>>()
.into_iter()
{
self.tiling_layer.unmap(&window);
self.floating_layer.map(window, seat, None);
} }
self.tiling_enabled = false; self.tiling_enabled = false;
} else { } else {
let focus_stack = self.focus_stack(seat); let focus_stack = self.focus_stack.get(seat);
for window in self.floating_layer.windows.clone().into_iter() { for window in self
self.floating_layer.unmap_window(&mut self.space, &window); .floating_layer
self.tiling_layer .mapped()
.map_window(&mut self.space, window, seat, focus_stack.iter()) .cloned()
.collect::<Vec<_>>()
.into_iter()
{
self.floating_layer.unmap(&window);
self.tiling_layer.map(window, seat, focus_stack.iter())
} }
self.tiling_enabled = true; self.tiling_enabled = true;
} }
@ -179,18 +391,290 @@ impl Workspace {
pub fn toggle_floating_window(&mut self, seat: &Seat<State>) { pub fn toggle_floating_window(&mut self, seat: &Seat<State>) {
if self.tiling_enabled { if self.tiling_enabled {
if let Some(window) = self.focus_stack(seat).iter().next().cloned() { if let Some(window) = self.focus_stack.get(seat).iter().next().cloned() {
if self.tiling_layer.windows.contains(&window) { if self.tiling_layer.mapped().any(|(_, m, _)| m == &window) {
self.tiling_layer.unmap_window(&mut self.space, &window); self.tiling_layer.unmap(&window);
self.floating_layer self.floating_layer.map(window, seat, None);
.map_window(&mut self.space, window, seat, None); } else if self.floating_layer.mapped().any(|w| w == &window) {
} else if self.floating_layer.windows.contains(&window) { let focus_stack = self.focus_stack.get(seat);
let focus_stack = self.focus_stack(seat); self.floating_layer.unmap(&window);
self.floating_layer.unmap_window(&mut self.space, &window); self.tiling_layer.map(window, seat, focus_stack.iter())
self.tiling_layer
.map_window(&mut self.space, 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, shell::Shell,
utils::prelude::*, utils::prelude::*,
wayland::protocols::{ wayland::protocols::{
drm::WlDrmState, export_dmabuf::ExportDmabufState, drm::WlDrmState,
output_configuration::OutputConfigurationState, workspace::WorkspaceClientState, output_configuration::OutputConfigurationState,
screencopy::{BufferParams, ScreencopyState, Session as ScreencopySession},
workspace::WorkspaceClientState,
}, },
}; };
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode;
use smithay::{ 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}, input::{Seat, SeatState},
output::{Mode as OutputMode, Output, Scale}, output::{Mode as OutputMode, Output, Scale},
reexports::{ reexports::{
calloop::{LoopHandle, LoopSignal}, calloop::{LoopHandle, LoopSignal},
wayland_server::{ wayland_server::{
backend::{ClientData, ClientId, DisconnectReason}, backend::{ClientData, ClientId, DisconnectReason},
protocol::wl_shm,
Display, DisplayHandle, Display, DisplayHandle,
}, },
}, },
utils::{Buffer, Size}, utils::{Clock, Monotonic},
wayland::{ wayland::{
compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState, compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState,
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState,
primary_selection::PrimarySelectionState, shm::ShmState, viewporter::ViewporterState, presentation::PresentationState, primary_selection::PrimarySelectionState, shm::ShmState,
viewporter::ViewporterState,
}, },
}; };
use std::{ use std::{cell::RefCell, ffi::OsString, time::Duration};
cell::RefCell, use std::{collections::VecDeque, time::Instant};
ffi::OsString,
sync::{atomic::AtomicBool, Arc},
time::Instant,
};
#[cfg(feature = "debug")]
use std::{collections::VecDeque, time::Duration};
pub struct ClientState { pub struct ClientState {
pub workspace_client_state: WorkspaceClientState, pub workspace_client_state: WorkspaceClientState,
@ -69,12 +78,11 @@ pub struct Common {
//pub output_conf: ConfigurationManager, //pub output_conf: ConfigurationManager,
pub shell: Shell, pub shell: Shell,
pub dirty_flag: Arc<AtomicBool>,
pub seats: Vec<Seat<State>>, seats: Vec<Seat<State>>,
pub last_active_seat: Seat<State>, last_active_seat: Option<Seat<State>>,
pub start_time: Instant, pub clock: Clock<Monotonic>,
pub should_stop: bool, pub should_stop: bool,
pub log: LogState, pub log: LogState,
@ -85,34 +93,18 @@ pub struct Common {
pub compositor_state: CompositorState, pub compositor_state: CompositorState,
pub data_device_state: DataDeviceState, pub data_device_state: DataDeviceState,
pub dmabuf_state: DmabufState, pub dmabuf_state: DmabufState,
pub export_dmabuf_state: ExportDmabufState,
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
pub output_state: OutputManagerState, pub output_state: OutputManagerState,
pub output_configuration_state: OutputConfigurationState<State>, pub output_configuration_state: OutputConfigurationState<State>,
pub presentation_state: PresentationState,
pub primary_selection_state: PrimarySelectionState, pub primary_selection_state: PrimarySelectionState,
pub screencopy_state: ScreencopyState,
pub seat_state: SeatState<State>, pub seat_state: SeatState<State>,
pub shm_state: ShmState, pub shm_state: ShmState,
pub wl_drm_state: WlDrmState, pub wl_drm_state: WlDrmState,
pub viewporter_state: ViewporterState, 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 { pub enum BackendData {
X11(X11State), X11(X11State),
Winit(WinitState), Winit(WinitState),
@ -149,11 +141,12 @@ impl BackendData {
output: &Output, output: &Output,
test_only: bool, test_only: bool,
shell: &mut Shell, shell: &mut Shell,
seats: impl Iterator<Item = Seat<State>>,
loop_handle: &LoopHandle<'_, Data>, loop_handle: &LoopHandle<'_, Data>,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
let result = match self { let result = match self {
BackendData::Kms(ref mut state) => { 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::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), BackendData::X11(ref mut state) => state.apply_config_for_output(output, test_only),
@ -187,102 +180,25 @@ impl BackendData {
result 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 { 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. // 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. // 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) => { 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); slog_scope::crit!("Failed to schedule event, are we shutting down? {:?}", err);
} }
} }
_ => unreachable!("No backend was initialized"), _ => 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 { impl State {
@ -293,31 +209,31 @@ impl State {
signal: LoopSignal, signal: LoopSignal,
log: LogState, log: LogState,
) -> State { ) -> State {
let clock = Clock::new().expect("Failed to initialize clock");
let config = Config::load(); let config = Config::load();
let compositor_state = CompositorState::new::<Self, _>(dh, None); let compositor_state = CompositorState::new::<Self, _>(dh, None);
let data_device_state = DataDeviceState::new::<Self, _>(dh, None); let data_device_state = DataDeviceState::new::<Self, _>(dh, None);
let dmabuf_state = DmabufState::new(); 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 keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::<Self>(dh);
let output_state = OutputManagerState::new_with_xdg_output::<Self>(dh); let output_state = OutputManagerState::new_with_xdg_output::<Self>(dh);
let output_configuration_state = OutputConfigurationState::new(dh, |_| true); 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 primary_selection_state = PrimarySelectionState::new::<Self, _>(dh, None);
let shm_state = ShmState::new::<Self, _>(dh, vec![], None); let screencopy_state = ScreencopyState::new::<Self, _, _>(
let mut seat_state = SeatState::<Self>::new(); 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 viewporter_state = ViewporterState::new::<Self, _>(dh, None);
let wl_drm_state = WlDrmState; let wl_drm_state = WlDrmState;
let shell = Shell::new(&config, dh); 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 { State {
common: Common { common: Common {
@ -328,38 +244,27 @@ impl State {
event_loop_signal: signal, event_loop_signal: signal,
shell, shell,
dirty_flag,
seats: vec![initial_seat.clone()], seats: Vec::new(),
last_active_seat: initial_seat, last_active_seat: None,
start_time: Instant::now(), clock,
should_stop: false, should_stop: false,
log, log,
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
egui: Egui { egui: Egui { active: false },
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,
},
compositor_state, compositor_state,
data_device_state, data_device_state,
dmabuf_state, dmabuf_state,
export_dmabuf_state, screencopy_state,
shm_state, shm_state,
seat_state, seat_state,
keyboard_shortcuts_inhibit_state, keyboard_shortcuts_inhibit_state,
output_state, output_state,
output_configuration_state, output_configuration_state,
presentation_state,
primary_selection_state, primary_selection_state,
viewporter_state, viewporter_state,
wl_drm_state, wl_drm_state,
@ -376,10 +281,9 @@ impl State {
match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) { match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) {
Ok(val) if val == "y" || val == "yes" || val == "true" => Some( Ok(val) if val == "y" || val == "yes" || val == "true" => Some(
kms_state kms_state
.target_node_for_output(&active_output( .target_node_for_output(
&self.common.last_active_seat, &self.common.last_active_seat().active_output(),
&self.common, )
))
.unwrap_or(kms_state.primary), .unwrap_or(kms_state.primary),
), ),
_ => Some(kms_state.primary), _ => Some(kms_state.primary),
@ -415,49 +319,275 @@ impl State {
} }
} }
#[cfg(feature = "debug")] impl Common {
impl Fps { pub fn add_seat(&mut self, seat: Seat<State>) {
const WINDOW_SIZE: usize = 100; if self.seats.is_empty() {
self.last_active_seat = Some(seat.clone());
pub fn start(&mut self) { }
self.start = Instant::now(); self.seats.push(seat);
} }
pub fn end(&mut self) { pub fn remove_seat(&mut self, seat: &Seat<State>) {
let frame_time = Instant::now().duration_since(self.start); self.seats.retain(|s| s != seat);
if self.seats.is_empty() {
self.frames.push_back((self.start, frame_time)); self.last_active_seat = None;
if self.frames.len() > Fps::WINDOW_SIZE { } else if self.last_active_seat() == seat {
self.frames.pop_front(); self.last_active_seat = Some(self.seats[0].clone());
} }
} }
pub fn max_frametime(&self) -> &Duration { pub fn seats(&self) -> impl Iterator<Item = &Seat<State>> {
self.frames self.seats.iter()
.iter()
.map(|(_, f)| f)
.max()
.unwrap_or(&Duration::ZERO)
} }
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 self.frames
.iter() .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() .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 { pub fn avg_frametime(&self) -> Duration {
if self.frames.is_empty() { if self.frames.is_empty() {
return Duration::ZERO; 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 self.frames
.iter() .iter()
.map(|(_, f)| f) .take(window)
.cloned() .map(|f| f.render_time())
.sum::<Duration>() .sum::<Duration>()
/ (self.frames.len() as u32) / window as u32
} }
pub fn avg_fps(&self) -> f64 { pub fn avg_fps(&self) -> f64 {
@ -465,7 +595,9 @@ impl Fps {
return 0.0; return 0.0;
} }
let secs = match (self.frames.front(), self.frames.back()) { 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, _ => Duration::ZERO,
} }
.as_secs_f64(); .as_secs_f64();
@ -474,25 +606,44 @@ impl Fps {
} }
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
impl Default for Fps { static INTEL_LOGO: &'static [u8] = include_bytes!("../resources/icons/intel.svg");
fn default() -> Fps { #[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 { Fps {
state: { #[cfg(feature = "debug")]
let mut state = smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous); state,
let mut visuals: egui::style::Visuals = Default::default(); pending_frame: None,
visuals.window_shadow.extrusion = 0.0;
state.context().set_visuals(visuals);
state.set_zindex(110); // always render on top
state
},
modifiers: Default::default(),
frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1), 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 { pub fn avg_fps<'a>(iter: impl Iterator<Item = &'a Duration>) -> f64 {
let sum_secs = iter.map(|d| d.as_secs_f64()).sum::<f64>(); let sum_secs = iter.map(|d| d.as_secs_f64()).sum::<f64>();
1.0 / (sum_secs / Fps::WINDOW_SIZE as f64) 1.0 / (sum_secs / Fps::WINDOW_SIZE as f64)

View file

@ -1,10 +1,19 @@
use crate::input::{ActiveOutput, SeatId}; use std::{cell::RefCell, sync::Mutex, time::Duration};
use smithay::{
input::Seat, use crate::{
output::Output, backend::render::cursor::CursorState,
utils::{Logical, Rectangle, Transform}, 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::shell::{Shell, Workspace};
pub use crate::state::{Common, State}; pub use crate::state::{Common, State};
@ -32,38 +41,93 @@ impl OutputExt for Output {
pub trait SeatExt { pub trait SeatExt {
fn id(&self) -> usize; 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> { impl SeatExt for Seat<State> {
fn id(&self) -> usize { fn id(&self) -> usize {
self.user_data().get::<SeatId>().unwrap().0 self.user_data().get::<SeatId>().unwrap().0
} }
}
pub fn active_output(seat: &Seat<State>, state: &Common) -> Output { fn active_output(&self) -> Output {
seat.user_data() self.user_data()
.get::<ActiveOutput>() .get::<ActiveOutput>()
.map(|x| x.0.borrow().clone()) .map(|x| x.0.borrow().clone())
.unwrap_or_else(|| { .unwrap()
state }
.shell
.outputs()
.next()
.cloned()
.expect("Backend has no outputs?")
})
}
pub fn set_active_output(seat: &Seat<State>, output: &Output) { fn set_active_output(&self, output: &Output) {
if !seat *self
.user_data()
.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone())))
{
*seat
.user_data() .user_data()
.get::<ActiveOutput>() .get::<ActiveOutput>()
.unwrap() .unwrap()
.0 .0
.borrow_mut() = output.clone(); .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 // 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::{ use smithay::{
backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state}, backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state},
delegate_compositor, delegate_compositor,
@ -18,11 +18,13 @@ use smithay::{
}; };
use std::sync::Mutex; use std::sync::Mutex;
use super::screencopy::PendingScreencopyBuffers;
impl State { impl State {
fn early_import_surface(&mut self, surface: &WlSurface) { fn early_import_surface(&mut self, surface: &WlSurface) {
let mut import_nodes = std::collections::HashSet::new(); let mut import_nodes = std::collections::HashSet::new();
let dh = &self.common.display_handle; 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 BackendData::Kms(ref mut kms_state) = &mut self.backend {
if let Some(target) = kms_state.target_node_for_output(&output) { if let Some(target) = kms_state.target_node_for_output(&output) {
if import_nodes.insert(target) { if import_nodes.insert(target) {
@ -90,7 +92,7 @@ impl State {
if !initial_configure_sent { if !initial_configure_sent {
// compute initial dimensions by mapping // compute initial dimensions by mapping
Shell::map_layer(self, &surface); Shell::map_layer(self, &surface);
// this will also send a configure surface.layer_surface().send_configure();
} }
initial_configure_sent initial_configure_sent
} }
@ -121,7 +123,7 @@ impl CompositorHandler for State {
state.wl_buffer().is_some() state.wl_buffer().is_some()
}) })
{ {
let output = active_output(&seat, &self.common); let output = seat.active_output();
Shell::map_window(self, &window, &output); Shell::map_window(self, &window, &output);
} else { } else {
return; 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. // 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, // 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. // so we need to carefully track the state through different handlers.
if let Some((space, window)) = if let Some(element) = self.common.shell.element_for_surface(surface).cloned() {
self.common if let Some(workspace) = self.common.shell.space_for_mut(&element) {
.shell crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_to_location(
.space_for_window_mut(surface) element.clone(),
.and_then(|workspace| { 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(location) = new_location { workspace.commit(surface);
space.map_window(
&window,
location,
crate::shell::layout::floating::FLOATING_INDEX,
true,
);
for window in space.windows() {
update_reactive_popups(space, window);
}
} }
} }
//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, // 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. // so call this only after every potential surface map operation has been done.
self.early_import_surface(surface); self.early_import_surface(surface);
// and refresh smithays internal state // and refresh smithays internal state
self.common.shell.popups.commit(surface); 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) // re-arrange layer-surfaces (commits may change size and positioning)
if let Some(output) = self.common.shell.outputs().find(|o| { 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) map.layer_for_surface(surface, WindowSurfaceType::ALL)
.is_some() .is_some()
}) { }) {
let dh = &self.common.display_handle; layer_map_for_output(output).arrange();
layer_map_for_output(output).arrange(dh);
} }
let mut scheduled_sessions = self.schedule_workspace_sessions(surface);
// schedule a new render // schedule a new render
for output in self.common.shell.outputs_for_surface(surface) { for output in self.common.shell.visible_outputs_for_surface(surface) {
self.backend if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>() {
.schedule_render(&self.common.event_loop_handle, &output); 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 crate::utils::prelude::*;
use smithay::{ use smithay::{
delegate_layer_shell, delegate_layer_shell,
desktop::{LayerSurface, PopupKind}, desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurfaceType},
output::Output, output::Output,
reexports::wayland_server::protocol::wl_output::WlOutput, reexports::wayland_server::protocol::wl_output::WlOutput,
wayland::shell::{ wayland::shell::{
@ -14,6 +14,8 @@ use smithay::{
}, },
}; };
use super::screencopy::PendingScreencopyBuffers;
impl WlrLayerShellHandler for State { impl WlrLayerShellHandler for State {
fn shell_state(&mut self) -> &mut WlrLayerShellState { fn shell_state(&mut self) -> &mut WlrLayerShellState {
&mut self.common.shell.layer_shell_state &mut self.common.shell.layer_shell_state
@ -26,12 +28,11 @@ impl WlrLayerShellHandler for State {
_layer: Layer, _layer: Layer,
namespace: String, 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 let output = wl_output
.as_ref() .as_ref()
.and_then(Output::from_resource) .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(( self.common.shell.pending_layers.push((
LayerSurface::new(surface, namespace), LayerSurface::new(surface, namespace),
output, output,
@ -51,6 +52,44 @@ impl WlrLayerShellHandler for State {
.unwrap(); .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); delegate_layer_shell!(State);

View file

@ -4,12 +4,13 @@ pub mod buffer;
pub mod compositor; pub mod compositor;
pub mod data_device; pub mod data_device;
pub mod dmabuf; pub mod dmabuf;
pub mod export_dmabuf;
pub mod keyboard_shortcuts_inhibit; pub mod keyboard_shortcuts_inhibit;
pub mod layer_shell; pub mod layer_shell;
pub mod output; pub mod output;
pub mod output_configuration; pub mod output_configuration;
pub mod presentation;
pub mod primary_selection; pub mod primary_selection;
pub mod screencopy;
pub mod seat; pub mod seat;
pub mod shm; pub mod shm;
pub mod toplevel_info; pub mod toplevel_info;
@ -18,28 +19,3 @@ pub mod viewporter;
pub mod wl_drm; pub mod wl_drm;
pub mod workspace; pub mod workspace;
pub mod xdg_shell; 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( if let Err(err) = self.backend.apply_config_for_output(
output, output,
test_only, test_only,
&mut self.common.shell, &mut self.common.shell,
seats.iter().cloned(),
&self.common.event_loop_handle, &self.common.event_loop_handle,
) { ) {
slog_scope::warn!( slog_scope::warn!(
@ -106,6 +108,7 @@ impl State {
output, output,
false, false,
&mut self.common.shell, &mut self.common.shell,
seats.iter().cloned(),
&self.common.event_loop_handle, &self.common.event_loop_handle,
) { ) {
slog_scope::error!( 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 // SPDX-License-Identifier: GPL-3.0-only
use crate::state::State; use crate::{
shell::focus::target::{KeyboardFocusTarget, PointerFocusTarget},
state::State,
};
use smithay::{ use smithay::{
delegate_seat, delegate_seat,
input::{pointer::CursorImageStatus, SeatHandler, SeatState}, input::{pointer::CursorImageStatus, SeatHandler, SeatState},
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource}, reexports::wayland_server::Resource,
wayland::{data_device::set_data_device_focus, primary_selection::set_primary_focus}, wayland::{
data_device::set_data_device_focus, primary_selection::set_primary_focus,
seat::WaylandFocus,
},
}; };
use std::cell::RefCell; use std::cell::RefCell;
impl SeatHandler for State { impl SeatHandler for State {
type KeyboardFocus = WlSurface; type KeyboardFocus = KeyboardFocusTarget;
type PointerFocus = WlSurface; type PointerFocus = PointerFocusTarget;
fn seat_state(&mut self) -> &mut SeatState<Self> { fn seat_state(&mut self) -> &mut SeatState<Self> {
&mut self.common.seat_state &mut self.common.seat_state
@ -35,10 +41,12 @@ impl SeatHandler for State {
focused: Option<&Self::KeyboardFocus>, focused: Option<&Self::KeyboardFocus>,
) { ) {
let dh = &self.common.display_handle; let dh = &self.common.display_handle;
if let Some(client) = focused.and_then(|s| dh.get_client(s.id()).ok()) { if let Some(client) = focused
set_data_device_focus(dh, seat, Some(client)); .and_then(|t| t.wl_surface())
let client2 = focused.and_then(|s| dh.get_client(s.id()).ok()).unwrap(); .and_then(|s| dh.get_client(s.id()).ok())
set_primary_focus(dh, seat, Some(client2)) {
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>>) { fn activate(&mut self, _dh: &DisplayHandle, window: &Window, seat: Option<Seat<Self>>) {
if let Some(idx) = self for output in self
.common .common
.shell .shell
.space_for_window(window.toplevel().wl_surface()) .outputs()
.map(|w| w.idx) .cloned()
.collect::<Vec<_>>()
.iter()
{ {
let seat = seat.unwrap_or(self.common.last_active_seat.clone()); let maybe = self
let output = active_output(&seat, &self.common); .common
if self.common.shell.active_space(&output).idx != idx { .shell
self.common.shell.activate(&seat, &output, idx as usize); .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 // SPDX-License-Identifier: GPL-3.0-only
use crate::{ use crate::{
shell::WorkspaceMode,
state::ClientState, state::ClientState,
utils::prelude::*, utils::prelude::*,
wayland::protocols::workspace::{ wayland::protocols::workspace::{
@ -29,16 +30,22 @@ impl WorkspaceHandler for State {
for request in requests.into_iter() { for request in requests.into_iter() {
match request { match request {
Request::Activate(handle) => { Request::Activate(handle) => {
if let Some(idx) = self let maybe = match &self.common.shell.workspaces {
.common WorkspaceMode::Global(set) => set
.shell .workspaces
.spaces .iter()
.iter() .position(|w| w.handle == handle)
.position(|w| w.handle == handle) .map(|i| (self.common.last_active_seat().active_output(), i)),
{ WorkspaceMode::OutputBound(sets, _) => sets.iter().find_map(|(o, set)| {
let seat = &self.common.last_active_seat; set.workspaces
let output = active_output(seat, &self.common); .iter()
self.common.shell.activate(seat, &output, idx); .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 // SPDX-License-Identifier: GPL-3.0-only
use crate::utils::prelude::*; use crate::{utils::prelude::*, wayland::protocols::screencopy::SessionType};
use smithay::{ use smithay::{
delegate_xdg_shell, delegate_xdg_shell,
desktop::{ desktop::{
find_popup_root_surface, Kind, PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab, find_popup_root_surface, Kind, PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab,
PopupUngrabStrategy, Window, WindowSurfaceType, PopupUngrabStrategy, Window,
}, },
input::{ input::{
pointer::{Focus, GrabStartData as PointerGrabStartData}, pointer::{Focus, GrabStartData as PointerGrabStartData},
@ -17,12 +17,17 @@ use smithay::{
wayland_server::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface}, wayland_server::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
}, },
utils::Serial, utils::Serial,
wayland::shell::xdg::{ wayland::{
Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, seat::WaylandFocus,
shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
},
}, },
}; };
use std::cell::Cell; use std::cell::Cell;
use super::screencopy::PendingScreencopyBuffers;
pub mod popup; pub mod popup;
pub type PopupGrabData = Cell<Option<PopupGrab<State>>>; pub type PopupGrabData = Cell<Option<PopupGrab<State>>>;
@ -33,21 +38,14 @@ impl XdgShellHandler for State {
} }
fn new_toplevel(&mut self, surface: ToplevelSurface) { fn new_toplevel(&mut self, surface: ToplevelSurface) {
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;
let window = Window::new(Kind::Xdg(surface)); let window = Window::new(Kind::Xdg(surface));
self.common.shell.toplevel_info_state.new_toplevel(&window); self.common.shell.toplevel_info_state.new_toplevel(&window);
self.common self.common.shell.pending_windows.push((window, seat));
.shell
.pending_windows
.push((window, seat.clone()));
// We will position the window after the first commit, when we know its size hints // We will position the window after the first commit, when we know its size hints
} }
fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) { fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
super::mark_dirty_on_drop(&self.common, surface.wl_surface());
surface.with_pending_state(|state| { surface.with_pending_state(|state| {
state.geometry = positioner.get_geometry(); state.geometry = positioner.get_geometry();
state.positioner = positioner; 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) { fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) {
let seat = Seat::from_resource(&seat).unwrap(); let seat = Seat::from_resource(&seat).unwrap();
let kind = PopupKind::Xdg(surface); 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 let ret = self
.common .common
.shell .shell
.popups .popups
.grab_popup(root, kind, &seat, serial); .grab_popup(target, kind, &seat, serial);
if let Ok(mut grab) = ret { if let Ok(mut grab) = ret {
if let Some(keyboard) = seat.get_keyboard() { 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) { fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) {
let seat = Seat::from_resource(&seat).unwrap(); let seat = Seat::from_resource(&seat).unwrap();
if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) {
let workspace = self if let Some(mapped) = self
.common .common
.shell .shell
.space_for_window_mut(surface.wl_surface()) .element_for_surface(surface.wl_surface())
.unwrap(); .cloned()
let window = workspace {
.space if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
.window_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) let output = seat.active_output();
.unwrap() let (window, _) = mapped
.clone(); .windows()
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
Shell::move_request(self, &window, &seat, serial, start_data); .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(); let seat = Seat::from_resource(&seat).unwrap();
if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { 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) { fn maximize_request(&mut self, surface: ToplevelSurface) {
let surface = surface.wl_surface(); let seat = self.common.last_active_seat();
let seat = &self.common.last_active_seat; let output = seat.active_output();
let output = active_output(seat, &self.common);
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { if let Some(mapped) = self
let window = workspace .common
.space .shell
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL) .element_for_surface(surface.wl_surface())
.unwrap() .cloned()
.clone(); {
workspace.maximize_request(&window, &output) 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) { fn unmaximize_request(&mut self, surface: ToplevelSurface) {
let surface = surface.wl_surface(); if let Some(mapped) = self
.common
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { .shell
let window = workspace .element_for_surface(surface.wl_surface())
.space .cloned()
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL) {
.unwrap() if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
.clone(); let (window, _) = mapped
workspace.unmaximize_request(&window) .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() .as_ref()
.and_then(Output::from_resource) .and_then(Output::from_resource)
.unwrap_or_else(|| { .unwrap_or_else(|| {
let seat = &self.common.last_active_seat; let seat = self.common.last_active_seat();
active_output(seat, &self.common) seat.active_output()
}); });
let surface = surface.wl_surface(); if let Some(mapped) = self
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { .common
let window = workspace .shell
.space .element_for_surface(surface.wl_surface())
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL) .cloned()
.unwrap() {
.clone(); if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
workspace.fullscreen_request(&window, &output) 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) { fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
let surface = surface.wl_surface(); if let Some(mapped) = self
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { .common
let window = workspace .shell
.space .element_for_surface(surface.wl_surface())
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL) .cloned()
.unwrap() {
.clone(); if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
workspace.unfullscreen_request(&window) 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() .as_ref()
.unwrap() .unwrap()
.0 .0
.id()
.same_client_as(&surface.id()) .same_client_as(&surface.id())
{ {
return None; return None;

View file

@ -3,8 +3,7 @@
use crate::{shell::Shell, utils::prelude::*}; use crate::{shell::Shell, utils::prelude::*};
use smithay::{ use smithay::{
desktop::{ desktop::{
layer_map_for_output, LayerSurface, PopupKind, PopupManager, Space, Window, layer_map_for_output, LayerSurface, PopupKind, PopupManager, Window, WindowSurfaceType,
WindowSurfaceType,
}, },
output::Output, output::Output,
reexports::{ reexports::{
@ -27,12 +26,19 @@ use std::sync::Mutex;
impl Shell { impl Shell {
pub fn unconstrain_popup(&self, surface: &PopupSurface, positioner: &PositionerState) { pub fn unconstrain_popup(&self, surface: &PopupSurface, positioner: &PositionerState) {
if let Some(parent) = get_popup_toplevel(&surface) { if let Some(parent) = get_popup_toplevel(&surface) {
if let Some(workspace) = self.space_for_window(&parent) { if let Some(elem) = self.element_for_surface(&parent) {
let window = workspace let workspace = self.space_for(elem).unwrap();
.space let element_loc = workspace.element_geometry(elem).unwrap().loc;
.window_for_surface(&parent, WindowSurfaceType::ALL) let (window, offset) = elem
.windows()
.find(|(w, _)| w.toplevel().wl_surface() == &parent)
.unwrap(); .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| { } else if let Some((output, layer_surface)) = self.outputs().find_map(|o| {
let map = layer_map_for_output(o); let map = layer_map_for_output(o);
map.layer_for_surface(&parent, WindowSurfaceType::ALL) 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()) { for (popup, _) in PopupManager::popups_for_surface(window.toplevel().wl_surface()) {
match popup { match popup {
PopupKind::Xdg(surface) => { PopupKind::Xdg(surface) => {
@ -64,12 +69,19 @@ pub fn update_reactive_popups(space: &Space, window: &Window) {
attributes.current.positioner.clone() attributes.current.positioner.clone()
}); });
if positioner.reactive { if positioner.reactive {
unconstrain_xdg_popup(&surface, &positioner, space, window); let anchor_point = get_anchor_point(&positioner) + loc;
if let Err(err) = surface.send_configure() { if let Some(rect) = output_geo
slog_scope::warn!( .iter()
"Compositor bug: Unable to re-configure reactive popup: {}", .find(|geo| geo.contains(anchor_point))
err .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( fn unconstrain_xdg_popup(
surface: &PopupSurface, surface: &PopupSurface,
positioner: &PositionerState, positioner: &PositionerState,
space: &Space, window_loc: Point<i32, Logical>,
window: &Window, rect: Rectangle<i32, Logical>,
) { ) {
let anchor_point = get_anchor_point(&positioner) + space.window_location(&window).unwrap(); let mut relative = rect;
if let Some(output_rect) = space relative.loc -= window_loc;
.outputs_for_window(window) let offset = check_constrained(&surface, positioner.get_geometry(), relative);
.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);
if offset.x != 0 || offset.y != 0 { if offset.x != 0 || offset.y != 0 {
slog_scope::debug!("Unconstraining popup: {:?}", surface); slog_scope::debug!("Unconstraining popup: {:?}", surface);
if !unconstrain_flip(&surface, &positioner, relative) { if !unconstrain_flip(&surface, &positioner, relative) {
if !unconstrain_slide(&surface, &positioner, relative) { if !unconstrain_slide(&surface, &positioner, relative) {
unconstrain_resize(&surface, &positioner, relative); unconstrain_resize(&surface, &positioner, relative);
}
} }
} }
} }
@ -319,7 +317,7 @@ fn get_anchor_point(positioner: &PositionerState) -> Point<i32, Logical> {
.into() .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()?; let mut parent = popup.get_parent_surface()?;
while get_role(&parent) == Some(XDG_POPUP_ROLE) { while get_role(&parent) == Some(XDG_POPUP_ROLE) {
parent = with_states(&parent, |states| { 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 // SPDX-License-Identifier: GPL-3.0-only
pub mod drm; pub mod drm;
pub mod export_dmabuf; //pub mod export_dmabuf;
pub mod output_configuration; pub mod output_configuration;
pub mod screencopy;
pub mod toplevel_info; pub mod toplevel_info;
pub mod toplevel_management; pub mod toplevel_management;
pub mod workspace; pub mod workspace;

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use smithay::{ use smithay::{
output::{Mode, Output, OutputData}, output::{Mode, Output},
reexports::{ reexports::{
wayland_protocols_wlr::output_management::v1::server::{ wayland_protocols_wlr::output_management::v1::server::{
zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1},
@ -17,6 +17,7 @@ use smithay::{
}, },
}, },
utils::{Logical, Physical, Point, Size, Transform}, utils::{Logical, Physical, Point, Size, Transform},
wayland::output::WlOutputData,
}; };
use std::{ use std::{
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
@ -498,7 +499,7 @@ where
impl<D> OutputConfigurationState<D> impl<D> OutputConfigurationState<D>
where where
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData> D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
+ GlobalDispatch<WlOutput, OutputData> + GlobalDispatch<WlOutput, WlOutputData>
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData> + Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
+ Dispatch<ZwlrOutputHeadV1, Output> + Dispatch<ZwlrOutputHeadV1, Output>
+ Dispatch<ZwlrOutputModeV1, Mode> + 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() .iter()
.filter(|o| !handle_state.outputs.contains(o)) .filter(|o| !handle_state.outputs.contains(o))
{ {
new_output.with_client_outputs(dh, &client, |_dh, wl_output| { for wl_output in new_output.client_outputs(&client) {
instance.output_enter(wl_output); instance.output_enter(&wl_output);
}); }
changed = true; changed = true;
} }
for old_output in handle_state for old_output in handle_state
@ -423,9 +423,9 @@ fn send_toplevel_to_client<D>(
.iter() .iter()
.filter(|o| !state.outputs.contains(o)) .filter(|o| !state.outputs.contains(o))
{ {
old_output.with_client_outputs(dh, &client, |_dh, wl_output| { for wl_output in old_output.client_outputs(&client) {
instance.output_leave(wl_output); instance.output_leave(&wl_output);
}); }
changed = true; changed = true;
} }
handle_state.outputs = state.outputs.clone(); handle_state.outputs = state.outputs.clone();

View file

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

View file

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