commit
3161aab097
56 changed files with 10856 additions and 5418 deletions
775
Cargo.lock
generated
775
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
17
Cargo.toml
17
Cargo.toml
|
|
@ -16,7 +16,7 @@ slog-stdlog = "4.1"
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sendfd = "0.4.1"
|
||||
egui = { version = "0.18.1", optional = true }
|
||||
egui = { version = "0.19.0", optional = true }
|
||||
edid-rs = { version = "0.1" }
|
||||
png = "0.17.5"
|
||||
lazy_static = "1.4.0"
|
||||
|
|
@ -28,28 +28,27 @@ xkbcommon = "0.4"
|
|||
indexmap = "1.8.0"
|
||||
xdg = "^2.1"
|
||||
ron = "0.7"
|
||||
atomic_float = "0.1"
|
||||
libsystemd = "0.5"
|
||||
wayland-backend = "=0.1.0-beta.10"
|
||||
wayland-scanner = "=0.30.0-beta.10"
|
||||
wayland-backend = "=0.1.0-beta.13"
|
||||
wayland-scanner = "=0.30.0-beta.13"
|
||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] }
|
||||
|
||||
[dependencies.smithay]
|
||||
version = "0.3"
|
||||
git = "https://github.com/Smithay/smithay.git"
|
||||
rev = "606d2d5c"
|
||||
rev = "b297c93edc"
|
||||
default-features = false
|
||||
features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_gl", "renderer_multi", "wayland_frontend", "slog-stdlog"]
|
||||
features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_glow", "renderer_multi", "wayland_frontend", "slog-stdlog"]
|
||||
|
||||
[dependencies.smithay-egui]
|
||||
git = "https://github.com/Smithay/smithay-egui.git"
|
||||
rev = "939febaf"
|
||||
rev = "9fe1fa5e01"
|
||||
features = ["svg"]
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
debug = ["egui", "smithay-egui"]
|
||||
experimental = []
|
||||
|
||||
[profile.dev]
|
||||
lto = "thin"
|
||||
|
|
@ -62,4 +61,4 @@ debug = true
|
|||
lto = "fat"
|
||||
|
||||
[patch."https://github.com/Smithay/smithay.git"]
|
||||
smithay = { git = "https://github.com/Smithay//smithay", rev = "625cbca5" }
|
||||
smithay = { git = "https://github.com/pop-os/smithay", rev = "c8aaa059e8" }
|
||||
|
|
|
|||
125
config.ron
125
config.ron
|
|
@ -1,51 +1,89 @@
|
|||
(
|
||||
key_bindings: {
|
||||
(modifiers: [Logo, Shift], key: "Escape"): Terminate,
|
||||
(modifiers: [Logo], key: "Escape"): Debug,
|
||||
(modifiers: [Logo], key: "q"): Close,
|
||||
(modifiers: [Logo], key: "1"): Workspace(1),
|
||||
(modifiers: [Logo], key: "2"): Workspace(2),
|
||||
(modifiers: [Logo], key: "3"): Workspace(3),
|
||||
(modifiers: [Logo], key: "4"): Workspace(4),
|
||||
(modifiers: [Logo], key: "5"): Workspace(5),
|
||||
(modifiers: [Logo], key: "6"): Workspace(6),
|
||||
(modifiers: [Logo], key: "7"): Workspace(7),
|
||||
(modifiers: [Logo], key: "8"): Workspace(8),
|
||||
(modifiers: [Logo], key: "9"): Workspace(9),
|
||||
(modifiers: [Logo], key: "0"): Workspace(0),
|
||||
(modifiers: [Logo, Shift], key: "1"): MoveToWorkspace(1),
|
||||
(modifiers: [Logo, Shift], key: "2"): MoveToWorkspace(2),
|
||||
(modifiers: [Logo, Shift], key: "3"): MoveToWorkspace(3),
|
||||
(modifiers: [Logo, Shift], key: "4"): MoveToWorkspace(4),
|
||||
(modifiers: [Logo, Shift], key: "5"): MoveToWorkspace(5),
|
||||
(modifiers: [Logo, Shift], key: "6"): MoveToWorkspace(6),
|
||||
(modifiers: [Logo, Shift], key: "7"): MoveToWorkspace(7),
|
||||
(modifiers: [Logo, Shift], key: "8"): MoveToWorkspace(8),
|
||||
(modifiers: [Logo, Shift], key: "9"): MoveToWorkspace(9),
|
||||
(modifiers: [Logo, Shift], key: "0"): MoveToWorkspace(0),
|
||||
(modifiers: [Logo], key: "Left"): Focus(Left),
|
||||
(modifiers: [Logo], key: "Right"): Focus(Right),
|
||||
(modifiers: [Logo], key: "Up"): Focus(Up),
|
||||
(modifiers: [Logo], key: "Down"): Focus(Down),
|
||||
(modifiers: [Logo], key: "h"): Focus(Left),
|
||||
(modifiers: [Logo], key: "j"): Focus(Down),
|
||||
(modifiers: [Logo], key: "k"): Focus(Up),
|
||||
(modifiers: [Logo], key: "l"): Focus(Right),
|
||||
//TODO: automatic orientation with Logo+o toggling
|
||||
(modifiers: [Logo], key: "v"): Orientation(Vertical),
|
||||
(modifiers: [Logo], key: "o"): Orientation(Horizontal),
|
||||
(modifiers: [Logo], key: "y"): ToggleTiling,
|
||||
(modifiers: [Logo], key: "g"): ToggleWindowFloating,
|
||||
(modifiers: [Logo, Shift], key: "f"): Fullscreen,
|
||||
(modifiers: [Logo, Shift], key: "s"): Screenshot,
|
||||
(modifiers: [Super, Shift], key: "Escape"): Terminate,
|
||||
(modifiers: [Super], key: "Escape"): Debug,
|
||||
(modifiers: [Super], key: "q"): Close,
|
||||
|
||||
(modifiers: [Super], key: "1"): Workspace(1),
|
||||
(modifiers: [Super], key: "2"): Workspace(2),
|
||||
(modifiers: [Super], key: "3"): Workspace(3),
|
||||
(modifiers: [Super], key: "4"): Workspace(4),
|
||||
(modifiers: [Super], key: "5"): Workspace(5),
|
||||
(modifiers: [Super], key: "6"): Workspace(6),
|
||||
(modifiers: [Super], key: "7"): Workspace(7),
|
||||
(modifiers: [Super], key: "8"): Workspace(8),
|
||||
(modifiers: [Super], key: "9"): Workspace(9),
|
||||
(modifiers: [Super], key: "0"): LastWorkspace,
|
||||
(modifiers: [Super, Shift], key: "1"): MoveToWorkspace(1),
|
||||
(modifiers: [Super, Shift], key: "2"): MoveToWorkspace(2),
|
||||
(modifiers: [Super, Shift], key: "3"): MoveToWorkspace(3),
|
||||
(modifiers: [Super, Shift], key: "4"): MoveToWorkspace(4),
|
||||
(modifiers: [Super, Shift], key: "5"): MoveToWorkspace(5),
|
||||
(modifiers: [Super, Shift], key: "6"): MoveToWorkspace(6),
|
||||
(modifiers: [Super, Shift], key: "7"): MoveToWorkspace(7),
|
||||
(modifiers: [Super, Shift], key: "8"): MoveToWorkspace(8),
|
||||
(modifiers: [Super, Shift], key: "9"): MoveToWorkspace(9),
|
||||
(modifiers: [Super, Shift], key: "0"): MoveToLastWorkspace,
|
||||
|
||||
// TODO: Depends on workspace orientation
|
||||
(modifiers: [Super, Ctrl], key: "Right"): NextWorkspace,
|
||||
(modifiers: [Super, Ctrl], key: "Left"): PreviousWorkspace,
|
||||
(modifiers: [Super, Ctrl, Shift], key: "Right"): MoveToNextWorkspace,
|
||||
(modifiers: [Super, Ctrl, Shift], key: "Left"): MoveToPreviousWorkspace,
|
||||
(modifiers: [Super, Ctrl], key: "l"): NextWorkspace,
|
||||
(modifiers: [Super, Ctrl], key: "h"): PreviousWorkspace,
|
||||
(modifiers: [Super, Ctrl, Shift], key: "l"): MoveToNextWorkspace,
|
||||
(modifiers: [Super, Ctrl, Shift], key: "h"): MoveToPreviousWorkspace,
|
||||
|
||||
(modifiers: [Super, Ctrl], key: "Down"): NextOutput,
|
||||
(modifiers: [Super, Ctrl], key: "Up"): PreviousOutput,
|
||||
(modifiers: [Super, Ctrl, Alt], key: "Down"): NextOutput,
|
||||
(modifiers: [Super, Ctrl, Alt], key: "Up"): PreviousOutput,
|
||||
(modifiers: [Super, Ctrl], key: "j"): NextOutput,
|
||||
(modifiers: [Super, Ctrl], key: "k"): PreviousOutput,
|
||||
(modifiers: [Super, Ctrl, Alt], key: "j"): NextOutput,
|
||||
(modifiers: [Super, Ctrl, Alt], key: "k"): PreviousOutput,
|
||||
|
||||
(modifiers: [Super], key: "Period"): NextOutput,
|
||||
(modifiers: [Super], key: "Comma"): PreviousOutput,
|
||||
(modifiers: [Super, Shift], key: "Period"): MoveToNextOutput,
|
||||
(modifiers: [Super, Shift], key: "Comma"): MoveToPreviousOutput,
|
||||
|
||||
(modifiers: [Super], key: "Left"): Focus(Left),
|
||||
(modifiers: [Super], key: "Right"): Focus(Right),
|
||||
(modifiers: [Super], key: "Up"): Focus(Up),
|
||||
(modifiers: [Super], key: "Down"): Focus(Down),
|
||||
(modifiers: [Super], key: "h"): Focus(Left),
|
||||
(modifiers: [Super], key: "j"): Focus(Down),
|
||||
(modifiers: [Super], key: "k"): Focus(Up),
|
||||
(modifiers: [Super], key: "l"): Focus(Right),
|
||||
|
||||
(modifiers: [Super, Shift], key: "Left"): Move(Left),
|
||||
(modifiers: [Super, Shift], key: "Right"): Move(Right),
|
||||
(modifiers: [Super, Shift], key: "Up"): Move(Up),
|
||||
(modifiers: [Super, Shift], key: "Down"): Move(Down),
|
||||
(modifiers: [Super, Shift], key: "h"): Move(Left),
|
||||
(modifiers: [Super, Shift], key: "j"): Move(Down),
|
||||
(modifiers: [Super, Shift], key: "k"): Move(Up),
|
||||
(modifiers: [Super, Shift], key: "l"): Move(Right),
|
||||
|
||||
(modifiers: [Super], key: "o"): ToggleOrientation,
|
||||
|
||||
(modifiers: [Super], key: "y"): ToggleTiling,
|
||||
(modifiers: [Super], key: "g"): ToggleWindowFloating,
|
||||
|
||||
(modifiers: [Super], key: "m"): Maximize,
|
||||
|
||||
//TODO: ability to select default web browser
|
||||
(modifiers: [Logo], key: "b"): Spawn("firefox"),
|
||||
(modifiers: [Super], key: "b"): Spawn("firefox"),
|
||||
//TODO: ability to select default file browser
|
||||
(modifiers: [Logo], key: "f"): Spawn("nautilus"),
|
||||
(modifiers: [Super], key: "f"): Spawn("nautilus"),
|
||||
//TODO: ability to select default terminal
|
||||
(modifiers: [Logo], key: "t"): Spawn("gnome-terminal"),
|
||||
(modifiers: [Logo], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"),
|
||||
(modifiers: [Logo], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"),
|
||||
(modifiers: [Super], key: "t"): Spawn("gnome-terminal"),
|
||||
|
||||
(modifiers: [Super], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"),
|
||||
(modifiers: [Super], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"),
|
||||
|
||||
(modifiers: [], key: "XF86AudioRaiseVolume"): Spawn("amixer sset Master 5%+"),
|
||||
(modifiers: [], key: "XF86AudioLowerVolume"): Spawn("amixer sset Master 5%-"),
|
||||
(modifiers: [], key: "XF86AudioMute"): Spawn("amixer sset Master toggle"),
|
||||
|
|
@ -53,5 +91,6 @@
|
|||
(modifiers: [], key: "XF86MonBrightnessDown"): Spawn("busctl --user call com.system76.CosmicSettingsDaemon /com/system76/CosmicSettingsDaemon com.system76.CosmicSettingsDaemon DecreaseDisplayBrightness"),
|
||||
},
|
||||
workspace_mode: OutputBound,
|
||||
workspace_amount: Dynamic,
|
||||
floating_default: false,
|
||||
)
|
||||
|
|
|
|||
1
resources/icons/amd.svg
Normal file
1
resources/icons/amd.svg
Normal 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
12
resources/icons/intel.svg
Normal 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 |
1
resources/icons/nvidia.svg
Normal file
1
resources/icons/nvidia.svg
Normal 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 |
|
|
@ -1,2 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "1.63"
|
||||
channel = "1.65"
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
use crate::state::Fps;
|
||||
|
||||
use crate::{
|
||||
backend::render,
|
||||
config::OutputConfig,
|
||||
shell::Shell,
|
||||
state::{BackendData, ClientState, Common, Data},
|
||||
state::{BackendData, ClientState, Common, Data, Fps},
|
||||
utils::prelude::*,
|
||||
wayland::{
|
||||
handlers::screencopy::{PendingScreencopyBuffers, UserdataExt},
|
||||
protocols::screencopy::{BufferParams, Session as ScreencopySession},
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
|
@ -20,13 +21,16 @@ use smithay::{
|
|||
input::InputEvent,
|
||||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||
renderer::{
|
||||
damage::DamageTrackedRenderer,
|
||||
gles2::Gles2Renderbuffer,
|
||||
glow::GlowRenderer,
|
||||
multigpu::{egl::EglGlesBackend, GpuManager},
|
||||
Bind,
|
||||
},
|
||||
session::{auto::AutoSession, Session, Signal},
|
||||
udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent},
|
||||
},
|
||||
desktop::utils::OutputPresentationFeedback,
|
||||
input::Seat,
|
||||
output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
calloop::{
|
||||
|
|
@ -36,6 +40,7 @@ use smithay::{
|
|||
drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags},
|
||||
input::Libinput,
|
||||
nix::{fcntl::OFlag, sys::stat::dev_t},
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource},
|
||||
},
|
||||
utils::{
|
||||
|
|
@ -51,7 +56,7 @@ use std::{
|
|||
os::unix::io::{FromRawFd, OwnedFd},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod drm_helpers;
|
||||
|
|
@ -60,9 +65,13 @@ mod socket;
|
|||
use session_fd::*;
|
||||
use socket::*;
|
||||
|
||||
use super::render::{CursorMode, GlMultiRenderer};
|
||||
// for now we assume we need at least 3ms
|
||||
const MIN_RENDER_TIME: Duration = Duration::from_millis(3);
|
||||
|
||||
pub struct KmsState {
|
||||
devices: HashMap<DrmNode, Device>,
|
||||
pub api: GpuManager<EglGlesBackend>,
|
||||
pub api: GpuManager<EglGlesBackend<GlowRenderer>>,
|
||||
pub primary: DrmNode,
|
||||
session: AutoSession,
|
||||
signaler: Signaler<Signal>,
|
||||
|
|
@ -82,16 +91,21 @@ pub struct Device {
|
|||
}
|
||||
|
||||
pub struct Surface {
|
||||
surface: Option<GbmBufferedSurface<Rc<RefCell<GbmDevice<SessionFd>>>, SessionFd>>,
|
||||
surface: Option<
|
||||
GbmBufferedSurface<
|
||||
Rc<RefCell<GbmDevice<SessionFd>>>,
|
||||
SessionFd,
|
||||
Option<OutputPresentationFeedback>,
|
||||
>,
|
||||
>,
|
||||
damage_tracker: DamageTrackedRenderer,
|
||||
connector: connector::Handle,
|
||||
output: Output,
|
||||
last_render: Option<(Dmabuf, Instant)>,
|
||||
last_submit: Option<DrmEventTime>,
|
||||
refresh_rate: u32,
|
||||
vrr: bool,
|
||||
pending: bool,
|
||||
dirty: bool,
|
||||
render_timer_token: Option<RegistrationToken>,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: Fps,
|
||||
}
|
||||
|
||||
|
|
@ -120,12 +134,12 @@ pub fn init_backend(
|
|||
}
|
||||
data.state.process_input_event(event);
|
||||
for output in data.state.common.shell.outputs() {
|
||||
if let Err(err) = data
|
||||
.state
|
||||
.backend
|
||||
.kms()
|
||||
.schedule_render(&data.state.common.event_loop_handle, output)
|
||||
{
|
||||
if let Err(err) = data.state.backend.kms().schedule_render(
|
||||
&data.state.common.event_loop_handle,
|
||||
output,
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
slog_scope::crit!(
|
||||
"Error scheduling event loop for output {}: {:?}",
|
||||
output.name(),
|
||||
|
|
@ -142,7 +156,8 @@ pub fn init_backend(
|
|||
.map_err(|err| err.error)
|
||||
.context("Failed to initialize session event source")?;
|
||||
|
||||
let api = GpuManager::new(EglGlesBackend, None).context("Failed to initialize renderers")?;
|
||||
let api = GpuManager::new(EglGlesBackend::<GlowRenderer>::default(), None)
|
||||
.context("Failed to initialize renderers")?;
|
||||
|
||||
// TODO get this info from system76-power, if available and setup a watcher
|
||||
let primary = if let Some(path) = std::env::var("COSMIC_RENDER_DEVICE")
|
||||
|
|
@ -238,20 +253,15 @@ pub fn init_backend(
|
|||
}
|
||||
}
|
||||
}
|
||||
data.state.common.output_configuration_state.update();
|
||||
|
||||
let seats = data.state.common.seats().cloned().collect::<Vec<_>>();
|
||||
data.state.common.config.read_outputs(
|
||||
data.state.common.output_configuration_state.outputs(),
|
||||
&mut data.state.common.output_configuration_state,
|
||||
&mut data.state.backend,
|
||||
&mut data.state.common.shell,
|
||||
seats.into_iter(),
|
||||
&data.state.common.event_loop_handle,
|
||||
);
|
||||
data.state.common.shell.refresh_outputs();
|
||||
data.state
|
||||
.common
|
||||
.config
|
||||
.write_outputs(data.state.common.output_configuration_state.outputs());
|
||||
|
||||
for surface in data
|
||||
.state
|
||||
.backend
|
||||
|
|
@ -263,12 +273,17 @@ pub fn init_backend(
|
|||
surface.pending = false;
|
||||
}
|
||||
for output in data.state.common.shell.outputs() {
|
||||
if let Err(err) = data
|
||||
.state
|
||||
.backend
|
||||
.kms()
|
||||
.schedule_render(&data.state.common.event_loop_handle, output)
|
||||
{
|
||||
let sessions = output.pending_buffers().collect::<Vec<_>>();
|
||||
if let Err(err) = data.state.backend.kms().schedule_render(
|
||||
&data.state.common.event_loop_handle,
|
||||
output,
|
||||
None,
|
||||
if !sessions.is_empty() {
|
||||
Some(sessions)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
) {
|
||||
slog_scope::crit!(
|
||||
"Error scheduling event loop for output {}: {:?}",
|
||||
output.name(),
|
||||
|
|
@ -364,27 +379,87 @@ impl State {
|
|||
let dispatcher =
|
||||
Dispatcher::new(drm, move |event, metadata, data: &mut Data| match event {
|
||||
DrmEvent::VBlank(crtc) => {
|
||||
if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) {
|
||||
if let Some(surface) = device.surfaces.get_mut(&crtc) {
|
||||
match surface.surface.as_mut().map(|x| x.frame_submitted()) {
|
||||
Some(Ok(_)) => {
|
||||
surface.last_submit = metadata.take().map(|data| data.time);
|
||||
surface.pending = false;
|
||||
data.state
|
||||
.common
|
||||
.shell
|
||||
.active_space_mut(&surface.output)
|
||||
.space
|
||||
.send_frames(
|
||||
data.state.common.start_time.elapsed().as_millis()
|
||||
as u32,
|
||||
);
|
||||
let rescheduled =
|
||||
if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) {
|
||||
if let Some(surface) = device.surfaces.get_mut(&crtc) {
|
||||
#[cfg(feature = "debug")]
|
||||
surface.fps.displayed();
|
||||
|
||||
match surface.surface.as_mut().map(|x| x.frame_submitted()) {
|
||||
Some(Ok(feedback)) => {
|
||||
if let Some(mut feedback) = feedback.flatten() {
|
||||
let submit_time =
|
||||
match metadata.take().map(|data| data.time) {
|
||||
Some(DrmEventTime::Monotonic(tp)) => Some(tp),
|
||||
_ => None,
|
||||
};
|
||||
let seq = metadata
|
||||
.as_ref()
|
||||
.map(|metadata| metadata.sequence)
|
||||
.unwrap_or(0);
|
||||
|
||||
let (clock, flags) = if let Some(tp) = submit_time {
|
||||
(
|
||||
tp.into(),
|
||||
wp_presentation_feedback::Kind::Vsync
|
||||
| wp_presentation_feedback::Kind::HwClock
|
||||
| wp_presentation_feedback::Kind::HwCompletion,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
data.state.common.clock.now(),
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
)
|
||||
};
|
||||
|
||||
feedback.presented(
|
||||
clock,
|
||||
surface
|
||||
.output
|
||||
.current_mode()
|
||||
.map(|mode| mode.refresh as u32)
|
||||
.unwrap_or_default(),
|
||||
seq as u64,
|
||||
flags,
|
||||
);
|
||||
}
|
||||
|
||||
surface.pending = false;
|
||||
surface.dirty.then(|| {
|
||||
(surface.output.clone(), surface.fps.avg_rendertime(5))
|
||||
})
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
slog_scope::warn!("Failed to submit frame: {}", err);
|
||||
None
|
||||
}
|
||||
_ => None, // got disabled
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
slog_scope::warn!("Failed to submit frame: {}", err)
|
||||
}
|
||||
None => {} // got disabled
|
||||
};
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((output, avg_rendertime)) = rescheduled {
|
||||
let mut scheduled_sessions =
|
||||
data.state.workspace_session_for_output(&output);
|
||||
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>()
|
||||
{
|
||||
scheduled_sessions
|
||||
.get_or_insert_with(Vec::new)
|
||||
.extend(sessions.borrow_mut().drain(..));
|
||||
}
|
||||
|
||||
let repaint_delay = std::cmp::max(avg_rendertime, MIN_RENDER_TIME);
|
||||
if let Err(err) = data.state.backend.kms().schedule_render(
|
||||
&data.state.common.event_loop_handle,
|
||||
&output,
|
||||
Some(repaint_delay),
|
||||
scheduled_sessions,
|
||||
) {
|
||||
slog_scope::warn!("Failed to schedule render: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -424,47 +499,44 @@ impl State {
|
|||
let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices
|
||||
let mut wl_outputs = Vec::new();
|
||||
let mut w = self.common.shell.global_space().size.w;
|
||||
for (crtc, conn) in outputs {
|
||||
match device.setup_surface(crtc, conn, (w, 0)) {
|
||||
Ok(output) => {
|
||||
w += output
|
||||
.user_data()
|
||||
.get::<RefCell<OutputConfig>>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.mode_size()
|
||||
.w;
|
||||
wl_outputs.push(output);
|
||||
}
|
||||
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
|
||||
};
|
||||
{
|
||||
let backend = self.backend.kms();
|
||||
for (crtc, conn) in outputs {
|
||||
let mut renderer = match backend.api.renderer(&render_node, &render_node) {
|
||||
Ok(renderer) => renderer,
|
||||
Err(err) => {
|
||||
slog_scope::warn!("Failed to initialize output: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match device.setup_surface(crtc, conn, (w, 0), &mut renderer) {
|
||||
Ok(output) => {
|
||||
w += output
|
||||
.user_data()
|
||||
.get::<RefCell<OutputConfig>>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.mode_size()
|
||||
.w;
|
||||
wl_outputs.push(output);
|
||||
}
|
||||
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
|
||||
};
|
||||
}
|
||||
backend.devices.insert(drm_node, device);
|
||||
}
|
||||
self.backend.kms().devices.insert(drm_node, device);
|
||||
|
||||
self.common
|
||||
.output_configuration_state
|
||||
.add_heads(wl_outputs.iter());
|
||||
self.common.output_configuration_state.update();
|
||||
for output in wl_outputs {
|
||||
if let Err(err) = self.backend.kms().apply_config_for_output(
|
||||
&output,
|
||||
&mut self.common.shell,
|
||||
false,
|
||||
&self.common.event_loop_handle,
|
||||
) {
|
||||
slog_scope::warn!("Failed to initialize output: {}", err);
|
||||
}
|
||||
}
|
||||
let seats = self.common.seats().cloned().collect::<Vec<_>>();
|
||||
self.common.config.read_outputs(
|
||||
self.common.output_configuration_state.outputs(),
|
||||
&mut self.common.output_configuration_state,
|
||||
&mut self.backend,
|
||||
&mut self.common.shell,
|
||||
seats.into_iter(),
|
||||
&self.common.event_loop_handle,
|
||||
);
|
||||
self.common.shell.refresh_outputs();
|
||||
self.common
|
||||
.config
|
||||
.write_outputs(self.common.output_configuration_state.outputs());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -477,32 +549,45 @@ impl State {
|
|||
let drm_node = DrmNode::from_dev_id(dev)?;
|
||||
let mut outputs_removed = Vec::new();
|
||||
let mut outputs_added = Vec::new();
|
||||
if let Some(device) = self.backend.kms().devices.get_mut(&drm_node) {
|
||||
let changes = device.enumerate_surfaces()?;
|
||||
let mut w = self.common.shell.global_space().size.w;
|
||||
for crtc in changes.removed {
|
||||
if let Some(surface) = device.surfaces.remove(&crtc) {
|
||||
if let Some(token) = surface.render_timer_token {
|
||||
self.common.event_loop_handle.remove(token);
|
||||
{
|
||||
let backend = self.backend.kms();
|
||||
if let Some(device) = backend.devices.get_mut(&drm_node) {
|
||||
let changes = device.enumerate_surfaces()?;
|
||||
let mut w = self.common.shell.global_space().size.w;
|
||||
for crtc in changes.removed {
|
||||
if let Some(surface) = device.surfaces.remove(&crtc) {
|
||||
if let Some(token) = surface.render_timer_token {
|
||||
self.common.event_loop_handle.remove(token);
|
||||
}
|
||||
w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0);
|
||||
outputs_removed.push(surface.output.clone());
|
||||
}
|
||||
w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0);
|
||||
outputs_removed.push(surface.output.clone());
|
||||
}
|
||||
}
|
||||
for (crtc, conn) in changes.added {
|
||||
match device.setup_surface(crtc, conn, (w, 0)) {
|
||||
Ok(output) => {
|
||||
w += output
|
||||
.user_data()
|
||||
.get::<RefCell<OutputConfig>>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.mode_size()
|
||||
.w;
|
||||
outputs_added.push(output);
|
||||
}
|
||||
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
|
||||
};
|
||||
for (crtc, conn) in changes.added {
|
||||
let mut renderer = match backend
|
||||
.api
|
||||
.renderer(&device.render_node, &device.render_node)
|
||||
{
|
||||
Ok(renderer) => renderer,
|
||||
Err(err) => {
|
||||
slog_scope::warn!("Failed to initialize output: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match device.setup_surface(crtc, conn, (w, 0), &mut renderer) {
|
||||
Ok(output) => {
|
||||
w += output
|
||||
.user_data()
|
||||
.get::<RefCell<OutputConfig>>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.mode_size()
|
||||
.w;
|
||||
outputs_added.push(output);
|
||||
}
|
||||
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -512,30 +597,19 @@ impl State {
|
|||
self.common
|
||||
.output_configuration_state
|
||||
.add_heads(outputs_added.iter());
|
||||
for output in outputs_added {
|
||||
if let Err(err) = self.backend.kms().apply_config_for_output(
|
||||
&output,
|
||||
&mut self.common.shell,
|
||||
false,
|
||||
&self.common.event_loop_handle,
|
||||
) {
|
||||
slog_scope::warn!("Failed to initialize output: {}", err);
|
||||
}
|
||||
}
|
||||
let seats = self.common.seats().cloned().collect::<Vec<_>>();
|
||||
for output in outputs_removed {
|
||||
self.common.shell.remove_output(&output);
|
||||
self.common
|
||||
.shell
|
||||
.remove_output(&output, seats.iter().cloned());
|
||||
}
|
||||
self.common.output_configuration_state.update();
|
||||
self.common.config.read_outputs(
|
||||
self.common.output_configuration_state.outputs(),
|
||||
&mut self.common.output_configuration_state,
|
||||
&mut self.backend,
|
||||
&mut self.common.shell,
|
||||
seats.into_iter(),
|
||||
&self.common.event_loop_handle,
|
||||
);
|
||||
self.common.shell.refresh_outputs();
|
||||
self.common
|
||||
.config
|
||||
.write_outputs(self.common.output_configuration_state.outputs());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -564,22 +638,23 @@ impl State {
|
|||
self.common
|
||||
.output_configuration_state
|
||||
.remove_heads(outputs_removed.iter());
|
||||
self.common.output_configuration_state.update();
|
||||
|
||||
let seats = self.common.seats().cloned().collect::<Vec<_>>();
|
||||
if self.backend.kms().session.is_active() {
|
||||
for output in outputs_removed {
|
||||
self.common.shell.remove_output(&output);
|
||||
self.common
|
||||
.shell
|
||||
.remove_output(&output, seats.iter().cloned());
|
||||
}
|
||||
self.common.config.read_outputs(
|
||||
self.common.output_configuration_state.outputs(),
|
||||
&mut self.common.output_configuration_state,
|
||||
&mut self.backend,
|
||||
&mut self.common.shell,
|
||||
seats.into_iter(),
|
||||
&self.common.event_loop_handle,
|
||||
);
|
||||
self.common.shell.refresh_outputs();
|
||||
self.common
|
||||
.config
|
||||
.write_outputs(self.common.output_configuration_state.outputs());
|
||||
} else {
|
||||
self.common.output_configuration_state.update();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -624,6 +699,7 @@ impl Device {
|
|||
crtc: crtc::Handle,
|
||||
conn: connector::Handle,
|
||||
position: (i32, i32),
|
||||
renderer: &mut GlMultiRenderer<'_>,
|
||||
) -> Result<Output> {
|
||||
let drm = &mut *self.drm.as_source_mut();
|
||||
let crtc_info = drm.get_crtc(crtc)?;
|
||||
|
|
@ -689,16 +765,15 @@ impl Device {
|
|||
|
||||
let data = Surface {
|
||||
output: output.clone(),
|
||||
damage_tracker: DamageTrackedRenderer::from_output(&output),
|
||||
surface: None,
|
||||
connector: conn,
|
||||
vrr,
|
||||
refresh_rate,
|
||||
last_submit: None,
|
||||
last_render: None,
|
||||
pending: false,
|
||||
dirty: false,
|
||||
render_timer_token: None,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: Fps::default(),
|
||||
fps: Fps::new(renderer.as_mut()),
|
||||
};
|
||||
self.surfaces.insert(crtc, data);
|
||||
|
||||
|
|
@ -717,8 +792,8 @@ fn render_node_for_output(
|
|||
let workspace = shell.active_space(output);
|
||||
let nodes = workspace
|
||||
.get_fullscreen(output)
|
||||
.map(|w| vec![w])
|
||||
.unwrap_or_else(|| workspace.space.windows().collect::<Vec<_>>())
|
||||
.map(|w| vec![w.clone()])
|
||||
.unwrap_or_else(|| workspace.windows().collect::<Vec<_>>())
|
||||
.into_iter()
|
||||
.flat_map(|w| {
|
||||
dh.get_client(w.toplevel().wl_surface().id())
|
||||
|
|
@ -750,44 +825,44 @@ impl Surface {
|
|||
pub fn render_output(
|
||||
&mut self,
|
||||
dh: &DisplayHandle,
|
||||
api: &mut GpuManager<EglGlesBackend>,
|
||||
api: &mut GpuManager<EglGlesBackend<GlowRenderer>>,
|
||||
target_node: &DrmNode,
|
||||
state: &mut Common,
|
||||
screencopy: Option<&[(ScreencopySession, BufferParams)]>,
|
||||
) -> Result<()> {
|
||||
if self.surface.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if render::needs_buffer_reset(&self.output, state) {
|
||||
self.surface.as_mut().unwrap().reset_buffers();
|
||||
}
|
||||
|
||||
let render_node = render_node_for_output(dh, &self.output, *target_node, &state.shell);
|
||||
let mut renderer = api.renderer(&render_node, &target_node).unwrap();
|
||||
let mut renderer: GlMultiRenderer = api.renderer(&render_node, &target_node).unwrap();
|
||||
|
||||
let surface = self.surface.as_mut().unwrap();
|
||||
let (buffer, age) = surface
|
||||
.next_buffer()
|
||||
.with_context(|| "Failed to allocate buffer")?;
|
||||
|
||||
renderer
|
||||
.bind(buffer.clone())
|
||||
.with_context(|| "Failed to bind buffer")?;
|
||||
|
||||
match render::render_output(
|
||||
match render::render_output::<GlMultiRenderer, _, Gles2Renderbuffer, _>(
|
||||
Some(&render_node),
|
||||
&mut renderer,
|
||||
age,
|
||||
buffer.clone(),
|
||||
&mut self.damage_tracker,
|
||||
age as usize,
|
||||
state,
|
||||
&self.output,
|
||||
false,
|
||||
#[cfg(feature = "debug")]
|
||||
CursorMode::All,
|
||||
screencopy.map(|sessions| (buffer, sessions)),
|
||||
Some(&mut self.fps),
|
||||
) {
|
||||
Ok(_) => {
|
||||
self.last_render = Some((buffer, Instant::now()));
|
||||
Ok((damage, states)) => {
|
||||
let feedback = if damage.is_some() {
|
||||
Some(state.take_presentation_feedback(&self.output, &states))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state.send_frames(&self.output, &states);
|
||||
surface
|
||||
.queue_buffer()
|
||||
.queue_buffer(feedback)
|
||||
.with_context(|| "Failed to submit buffer for display")?;
|
||||
}
|
||||
Err(err) => {
|
||||
|
|
@ -795,6 +870,7 @@ impl Surface {
|
|||
anyhow::bail!("Rendering failed: {}", err);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -807,6 +883,7 @@ impl KmsState {
|
|||
pub fn apply_config_for_output(
|
||||
&mut self,
|
||||
output: &Output,
|
||||
seats: impl Iterator<Item = Seat<State>>,
|
||||
shell: &mut Shell,
|
||||
test_only: bool,
|
||||
loop_handle: &LoopHandle<'_, Data>,
|
||||
|
|
@ -829,10 +906,11 @@ impl KmsState {
|
|||
|
||||
if !output_config.enabled {
|
||||
if !test_only {
|
||||
shell.remove_output(output, seats);
|
||||
if surface.surface.take().is_some() {
|
||||
// just drop it
|
||||
shell.remove_output(output);
|
||||
surface.pending = false;
|
||||
surface.dirty = false;
|
||||
}
|
||||
}
|
||||
false
|
||||
|
|
@ -856,7 +934,7 @@ impl KmsState {
|
|||
.ok_or(anyhow::anyhow!("Unknown mode"))?;
|
||||
|
||||
if !test_only {
|
||||
if let Some(gbm_surface) = surface.surface.as_mut() {
|
||||
let res = if let Some(gbm_surface) = surface.surface.as_mut() {
|
||||
if output_config.vrr != surface.vrr {
|
||||
surface.vrr = drm_helpers::set_vrr(
|
||||
drm,
|
||||
|
|
@ -888,9 +966,10 @@ impl KmsState {
|
|||
)
|
||||
})?;
|
||||
surface.surface = Some(target);
|
||||
shell.add_output(output);
|
||||
true
|
||||
}
|
||||
};
|
||||
shell.add_output(output);
|
||||
res
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -901,7 +980,17 @@ impl KmsState {
|
|||
|
||||
shell.refresh_outputs();
|
||||
if recreated {
|
||||
if let Err(err) = self.schedule_render(loop_handle, output) {
|
||||
let sessions = output.pending_buffers().collect::<Vec<_>>();
|
||||
if let Err(err) = self.schedule_render(
|
||||
loop_handle,
|
||||
output,
|
||||
None,
|
||||
if !sessions.is_empty() {
|
||||
Some(sessions)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
) {
|
||||
slog_scope::crit!(
|
||||
"Error scheduling event loop for output {}: {:?}",
|
||||
output.name(),
|
||||
|
|
@ -964,6 +1053,8 @@ impl KmsState {
|
|||
&mut self,
|
||||
loop_handle: &LoopHandle<'_, Data>,
|
||||
output: &Output,
|
||||
delay: Option<Duration>,
|
||||
mut screencopy_sessions: Option<Vec<(ScreencopySession, BufferParams)>>,
|
||||
) -> Result<(), InsertError<Timer>> {
|
||||
if let Some((device, crtc, surface)) = self
|
||||
.devices
|
||||
|
|
@ -975,20 +1066,8 @@ impl KmsState {
|
|||
return Ok(());
|
||||
}
|
||||
if !surface.pending {
|
||||
surface.dirty = false;
|
||||
surface.pending = true;
|
||||
/*
|
||||
let instant = surface
|
||||
.last_submit
|
||||
.as_ref()
|
||||
.and_then(|x| match x {
|
||||
DrmEventTime::Monotonic(instant) => Some(instant),
|
||||
DrmEventTime::Realtime(_) => None,
|
||||
})
|
||||
.map(|i| {
|
||||
*i + Duration::from_secs_f64(1.0 / surface.refresh_rate as f64)
|
||||
- Duration::from_millis(20) // render budget
|
||||
});
|
||||
*/
|
||||
|
||||
let device = *device;
|
||||
let crtc = *crtc;
|
||||
|
|
@ -996,10 +1075,11 @@ impl KmsState {
|
|||
loop_handle.remove(token);
|
||||
}
|
||||
surface.render_timer_token = Some(loop_handle.insert_source(
|
||||
//if surface.vrr || instant.is_none() {
|
||||
Timer::immediate(), /*} else {
|
||||
Timer::from_deadline(instant.unwrap())
|
||||
}*/
|
||||
if surface.vrr || delay.is_none() {
|
||||
Timer::immediate()
|
||||
} else {
|
||||
Timer::from_duration(delay.unwrap())
|
||||
},
|
||||
move |_time, _, data| {
|
||||
let backend = data.state.backend.kms();
|
||||
if let Some(device) = backend.devices.get_mut(&device) {
|
||||
|
|
@ -1009,11 +1089,17 @@ impl KmsState {
|
|||
&mut backend.api,
|
||||
&device.render_node,
|
||||
&mut data.state.common,
|
||||
screencopy_sessions.as_deref(),
|
||||
) {
|
||||
if let Some(sessions) = screencopy_sessions.as_mut() {
|
||||
for (session, params) in sessions.drain(..) {
|
||||
data.state.common.still_pending(session, params);
|
||||
}
|
||||
}
|
||||
if backend.session.is_active() {
|
||||
slog_scope::error!("Error rendering: {}", err);
|
||||
return TimeoutAction::ToDuration(Duration::from_secs_f64(
|
||||
1.0 / surface.refresh_rate as f64,
|
||||
(1000.0 / surface.refresh_rate as f64) - 0.003,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -1022,21 +1108,10 @@ impl KmsState {
|
|||
TimeoutAction::Drop
|
||||
},
|
||||
)?);
|
||||
} else {
|
||||
surface.dirty = true;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn capture_output(&self, output: &Output) -> Option<(DrmNode, Dmabuf, Instant)> {
|
||||
self.devices.values().find_map(|dev| {
|
||||
dev.surfaces
|
||||
.values()
|
||||
.find(|s| &s.output == output)
|
||||
.and_then(|s| {
|
||||
s.last_render
|
||||
.clone()
|
||||
.map(|(buf, time)| (dev.render_node.clone(), buf, time))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,18 +39,22 @@ pub fn init_backend_auto(
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
if res.is_ok() {
|
||||
for seat in &state.common.seats {
|
||||
let output = state
|
||||
.common
|
||||
.shell
|
||||
.outputs()
|
||||
.next()
|
||||
.with_context(|| "Backend initialized without output")?
|
||||
.clone();
|
||||
seat.user_data()
|
||||
.insert_if_missing(|| crate::input::ActiveOutput(std::cell::RefCell::new(output)));
|
||||
}
|
||||
let output = state
|
||||
.common
|
||||
.shell
|
||||
.outputs()
|
||||
.next()
|
||||
.with_context(|| "Backend initialized without output")?;
|
||||
let initial_seat = crate::input::add_seat(
|
||||
dh,
|
||||
&mut state.common.seat_state,
|
||||
output,
|
||||
&state.common.config,
|
||||
"seat-0".into(),
|
||||
);
|
||||
state.common.add_seat(initial_seat);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@
|
|||
|
||||
use crate::utils::prelude::*;
|
||||
use smithay::{
|
||||
backend::renderer::{Frame, ImportAll, ImportMem, Renderer, Texture},
|
||||
desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree},
|
||||
backend::renderer::{
|
||||
element::{
|
||||
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
|
||||
texture::{TextureBuffer, TextureRenderElement},
|
||||
},
|
||||
ImportAll, ImportMem, Renderer,
|
||||
},
|
||||
input::{
|
||||
pointer::{CursorImageAttributes, CursorImageStatus},
|
||||
Seat,
|
||||
},
|
||||
reexports::wayland_server::protocol::wl_surface,
|
||||
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform},
|
||||
render_elements,
|
||||
utils::{IsAlive, Logical, Monotonic, Point, Scale, Time, Transform},
|
||||
wayland::compositor::{get_role, with_states},
|
||||
};
|
||||
use std::{
|
||||
|
|
@ -18,6 +24,7 @@ use std::{
|
|||
collections::HashMap,
|
||||
io::Read,
|
||||
sync::Mutex,
|
||||
time::Duration,
|
||||
};
|
||||
use xcursor::{
|
||||
parser::{parse_xcursor, Image},
|
||||
|
|
@ -119,13 +126,21 @@ fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
|
|||
parse_xcursor(&cursor_data).ok_or(Error::Parse)
|
||||
}
|
||||
|
||||
pub fn draw_surface_cursor(
|
||||
surface: wl_surface::WlSurface,
|
||||
render_elements! {
|
||||
pub CursorRenderElement<R> where R: ImportAll;
|
||||
Static=TextureRenderElement<<R as Renderer>::TextureId>,
|
||||
Surface=WaylandSurfaceRenderElement,
|
||||
}
|
||||
|
||||
pub fn draw_surface_cursor<R: Renderer + ImportAll>(
|
||||
surface: &wl_surface::WlSurface,
|
||||
location: impl Into<Point<i32, Logical>>,
|
||||
) -> SurfaceTree
|
||||
scale: impl Into<Scale<f64>>,
|
||||
) -> Vec<CursorRenderElement<R>>
|
||||
where
|
||||
{
|
||||
let mut position = location.into();
|
||||
let scale = scale.into();
|
||||
let h = with_states(&surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
|
|
@ -136,129 +151,30 @@ where
|
|||
.hotspot
|
||||
});
|
||||
position -= h;
|
||||
SurfaceTree {
|
||||
surface,
|
||||
position,
|
||||
z_index: 100,
|
||||
}
|
||||
|
||||
render_elements_from_surface_tree(surface, position.to_physical_precise_round(scale), scale)
|
||||
}
|
||||
|
||||
pub fn draw_dnd_icon(
|
||||
surface: wl_surface::WlSurface,
|
||||
pub fn draw_dnd_icon<R: Renderer + ImportAll>(
|
||||
surface: &wl_surface::WlSurface,
|
||||
location: impl Into<Point<i32, Logical>>,
|
||||
) -> SurfaceTree {
|
||||
scale: impl Into<Scale<f64>>,
|
||||
) -> Vec<CursorRenderElement<R>> {
|
||||
if get_role(&surface) != Some("dnd_icon") {
|
||||
slog_scope::warn!(
|
||||
"Trying to display as a dnd icon a surface that does not have the DndIcon role."
|
||||
);
|
||||
}
|
||||
SurfaceTree {
|
||||
let scale = scale.into();
|
||||
render_elements_from_surface_tree(
|
||||
surface,
|
||||
position: location.into(),
|
||||
z_index: 100,
|
||||
}
|
||||
location.into().to_physical_precise_round(scale),
|
||||
scale,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct PointerElement<T: Texture> {
|
||||
seat_id: usize,
|
||||
texture: T,
|
||||
position: Point<f64, Logical>,
|
||||
size: Size<i32, Logical>,
|
||||
new_frame: bool,
|
||||
}
|
||||
|
||||
impl<T: Texture> PointerElement<T> {
|
||||
pub fn new(
|
||||
seat: &Seat<State>,
|
||||
texture: T,
|
||||
relative_pointer_pos: Point<f64, Logical>,
|
||||
new_frame: bool,
|
||||
) -> PointerElement<T> {
|
||||
let size = texture.size().to_logical(1, Transform::Normal);
|
||||
PointerElement {
|
||||
seat_id: seat.id(),
|
||||
texture,
|
||||
position: relative_pointer_pos,
|
||||
size,
|
||||
new_frame,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderElement<R> for PointerElement<<R as Renderer>::TextureId>
|
||||
where
|
||||
R: Renderer + ImportAll,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
{
|
||||
fn id(&self) -> usize {
|
||||
self.seat_id
|
||||
}
|
||||
|
||||
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
|
||||
self.position.to_physical(scale)
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
|
||||
Rectangle::from_loc_and_size(self.position, self.size.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round()
|
||||
}
|
||||
|
||||
fn accumulated_damage(
|
||||
&self,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
_: Option<SpaceOutputTuple<'_, '_>>,
|
||||
) -> Vec<Rectangle<i32, Physical>> {
|
||||
if self.new_frame {
|
||||
let scale = scale.into();
|
||||
vec![Rectangle::from_loc_and_size(
|
||||
self.position.to_physical(scale).to_i32_round(),
|
||||
self.size.to_physical_precise_round(scale),
|
||||
)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn opaque_regions(
|
||||
&self,
|
||||
_scale: impl Into<Scale<f64>>,
|
||||
) -> Option<Vec<Rectangle<i32, Physical>>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_renderer: &mut R,
|
||||
frame: &mut <R as Renderer>::Frame,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
position: Point<f64, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_log: &slog::Logger,
|
||||
) -> Result<(), <R as Renderer>::Error> {
|
||||
let scale = scale.into();
|
||||
frame.render_texture_at(
|
||||
&self.texture,
|
||||
position.to_i32_round(),
|
||||
1,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
&damage
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|mut rect| {
|
||||
rect.loc -= self.position.to_physical(scale).to_i32_round();
|
||||
rect
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
1.0,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct CursorState {
|
||||
cursor: Cursor,
|
||||
pub struct CursorState {
|
||||
pub cursor: Cursor,
|
||||
current_image: RefCell<Option<Image>>,
|
||||
image_cache: RefCell<HashMap<(TypeId, usize), Vec<(Image, Box<dyn Any + 'static>)>>>,
|
||||
}
|
||||
|
|
@ -273,79 +189,90 @@ impl Default for CursorState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn draw_cursor<R, I>(
|
||||
pub fn draw_cursor<R>(
|
||||
renderer: &mut R,
|
||||
seat: &Seat<State>,
|
||||
location: Point<f64, Logical>,
|
||||
start_time: &std::time::Instant,
|
||||
scale: Scale<f64>,
|
||||
time: Time<Monotonic>,
|
||||
draw_default: bool,
|
||||
) -> Option<I>
|
||||
) -> Vec<CursorRenderElement<R>>
|
||||
where
|
||||
I: From<SurfaceTree> + From<PointerElement<<R as Renderer>::TextureId>>,
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R: Renderer + ImportMem + ImportAll,
|
||||
<R as Renderer>::TextureId: Clone + 'static,
|
||||
{
|
||||
// draw the cursor as relevant
|
||||
{
|
||||
// reset the cursor if the surface is no longer alive
|
||||
let cursor_status = seat
|
||||
.user_data()
|
||||
.get::<RefCell<CursorImageStatus>>()
|
||||
.map(|cell| {
|
||||
let mut cursor_status = cell.borrow_mut();
|
||||
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
|
||||
if !surface.alive() {
|
||||
*cursor_status = CursorImageStatus::Default;
|
||||
}
|
||||
// reset the cursor if the surface is no longer alive
|
||||
let cursor_status = seat
|
||||
.user_data()
|
||||
.get::<RefCell<CursorImageStatus>>()
|
||||
.map(|cell| {
|
||||
let mut cursor_status = cell.borrow_mut();
|
||||
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
|
||||
if !surface.alive() {
|
||||
*cursor_status = CursorImageStatus::Default;
|
||||
}
|
||||
cursor_status.clone()
|
||||
})
|
||||
.unwrap_or(CursorImageStatus::Default);
|
||||
}
|
||||
cursor_status.clone()
|
||||
})
|
||||
.unwrap_or(CursorImageStatus::Default);
|
||||
|
||||
if let CursorImageStatus::Surface(wl_surface) = cursor_status {
|
||||
Some(draw_surface_cursor(wl_surface.clone(), location.to_i32_round()).into())
|
||||
} else if draw_default {
|
||||
let seat_userdata = seat.user_data();
|
||||
seat_userdata.insert_if_missing(CursorState::default);
|
||||
let state = seat_userdata.get::<CursorState>().unwrap();
|
||||
let frame = state
|
||||
.cursor
|
||||
.get_image(1, start_time.elapsed().as_millis() as u32);
|
||||
let new_frame = state.current_image.borrow().as_ref() != Some(&frame);
|
||||
if let CursorImageStatus::Surface(ref wl_surface) = cursor_status {
|
||||
return draw_surface_cursor(wl_surface, location.to_i32_round(), scale);
|
||||
} else if draw_default && CursorImageStatus::Default == cursor_status {
|
||||
let integer_scale = scale.x.max(scale.y).ceil() as u32;
|
||||
|
||||
let mut cache = state.image_cache.borrow_mut();
|
||||
let pointer_images = cache
|
||||
.entry((TypeId::of::<<R as Renderer>::TextureId>(), renderer.id()))
|
||||
.or_default();
|
||||
let pointer_image = pointer_images
|
||||
.iter()
|
||||
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None })
|
||||
.and_then(|texture| {
|
||||
texture
|
||||
.downcast_ref::<<R as Renderer>::TextureId>()
|
||||
.cloned()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let texture = renderer
|
||||
.import_memory(
|
||||
&frame.pixels_rgba,
|
||||
(frame.width as i32, frame.height as i32).into(),
|
||||
false,
|
||||
)
|
||||
.expect("Failed to import cursor bitmap");
|
||||
pointer_images.push((frame.clone(), Box::new(texture.clone())));
|
||||
texture
|
||||
});
|
||||
let hotspot =
|
||||
Point::<i32, Logical>::from((frame.xhot as i32, frame.yhot as i32)).to_f64();
|
||||
*state.current_image.borrow_mut() = Some(frame);
|
||||
let seat_userdata = seat.user_data();
|
||||
seat_userdata.insert_if_missing(CursorState::default);
|
||||
let state = seat_userdata.get::<CursorState>().unwrap();
|
||||
let frame = state.cursor.get_image(
|
||||
integer_scale,
|
||||
Into::<Duration>::into(time).as_millis() as u32,
|
||||
);
|
||||
|
||||
Some(
|
||||
PointerElement::new(seat, pointer_image.clone(), location - hotspot, new_frame)
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let mut cache = state.image_cache.borrow_mut();
|
||||
let pointer_images = cache
|
||||
.entry((TypeId::of::<TextureBuffer<R::TextureId>>(), renderer.id()))
|
||||
.or_default();
|
||||
|
||||
let maybe_image = pointer_images
|
||||
.iter()
|
||||
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None })
|
||||
.and_then(|texture| texture.downcast_ref::<TextureBuffer<R::TextureId>>());
|
||||
let pointer_image = match maybe_image {
|
||||
Some(image) => image,
|
||||
None => {
|
||||
let texture = TextureBuffer::from_memory(
|
||||
renderer,
|
||||
&frame.pixels_rgba,
|
||||
(frame.width as i32, frame.height as i32),
|
||||
false,
|
||||
integer_scale as i32,
|
||||
Transform::Normal,
|
||||
None,
|
||||
)
|
||||
.expect("Failed to import cursor bitmap");
|
||||
pointer_images.push((frame.clone(), Box::new(texture.clone())));
|
||||
pointer_images
|
||||
.last()
|
||||
.and_then(|(_, i)| i.downcast_ref::<TextureBuffer<R::TextureId>>())
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
let hotspot = Point::<i32, Logical>::from((frame.xhot as i32, frame.yhot as i32)).to_f64();
|
||||
*state.current_image.borrow_mut() = Some(frame);
|
||||
|
||||
return vec![CursorRenderElement::Static(
|
||||
TextureRenderElement::from_texture_buffer(
|
||||
(location - hotspot).to_physical(scale),
|
||||
pointer_image,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
)];
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
309
src/backend/render/element.rs
Normal file
309
src/backend/render/element.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,477 +1,314 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
use crate::{debug::fps_ui, utils::prelude::*};
|
||||
use crate::{
|
||||
debug::{debug_ui, fps_ui, log_ui, EguiFrame},
|
||||
state::Fps,
|
||||
utils::prelude::*,
|
||||
};
|
||||
use crate::{
|
||||
shell::grabs::{MoveGrabRenderElement, SeatMoveGrabState},
|
||||
state::Common,
|
||||
wayland::handlers::data_device::get_dnd_icon,
|
||||
};
|
||||
|
||||
use slog::Logger;
|
||||
use smithay::{
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
renderer::{
|
||||
gles2::{Gles2Renderbuffer, Gles2Renderer, Gles2Texture},
|
||||
multigpu::{egl::EglGlesBackend, Error as MultiError, MultiFrame, MultiRenderer},
|
||||
Frame, ImportAll, Renderer,
|
||||
shell::{layout::floating::SeatMoveGrabState, CosmicMappedRenderElement},
|
||||
state::{Common, Fps},
|
||||
wayland::{
|
||||
handlers::{data_device::get_dnd_icon, screencopy::render_session},
|
||||
protocols::{
|
||||
screencopy::{
|
||||
BufferParams, CursorMode as ScreencopyCursorMode, Session as ScreencopySession,
|
||||
},
|
||||
workspace::WorkspaceHandle,
|
||||
},
|
||||
},
|
||||
desktop::{
|
||||
draw_layer_popups, draw_layer_surface, draw_window, draw_window_popups,
|
||||
layer_map_for_output,
|
||||
space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree},
|
||||
utils::damage_from_surface_tree,
|
||||
Window,
|
||||
};
|
||||
|
||||
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason;
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::dmabuf::Dmabuf,
|
||||
drm::DrmNode,
|
||||
renderer::{
|
||||
buffer_dimensions,
|
||||
damage::{
|
||||
DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode,
|
||||
},
|
||||
element::{RenderElement, RenderElementStates},
|
||||
gles2::{Gles2Error, Gles2Renderbuffer},
|
||||
glow::GlowRenderer,
|
||||
multigpu::{egl::EglGlesBackend, MultiFrame, MultiRenderer},
|
||||
Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, TextureFilter,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
utils::{Physical, Point, Rectangle, Scale, Transform},
|
||||
wayland::shell::wlr_layer::Layer as WlrLayer,
|
||||
utils::{Physical, Rectangle},
|
||||
wayland::dmabuf::get_dmabuf,
|
||||
};
|
||||
|
||||
pub mod cursor;
|
||||
use self::cursor::PointerElement;
|
||||
use self::cursor::CursorRenderElement;
|
||||
pub mod element;
|
||||
use self::element::{AsGles2Frame, AsGlowRenderer, CosmicElement};
|
||||
|
||||
pub type GlMultiRenderer<'a> =
|
||||
MultiRenderer<'a, 'a, EglGlesBackend, EglGlesBackend, Gles2Renderbuffer>;
|
||||
pub type GlMultiFrame = MultiFrame<EglGlesBackend, EglGlesBackend>;
|
||||
pub type GlMultiRenderer<'a> = MultiRenderer<
|
||||
'a,
|
||||
'a,
|
||||
EglGlesBackend<GlowRenderer>,
|
||||
EglGlesBackend<GlowRenderer>,
|
||||
Gles2Renderbuffer,
|
||||
>;
|
||||
pub type GlMultiFrame = MultiFrame<EglGlesBackend<GlowRenderer>, EglGlesBackend<GlowRenderer>>;
|
||||
|
||||
static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0];
|
||||
pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0];
|
||||
|
||||
smithay::custom_elements! {
|
||||
pub CustomElem<=Gles2Renderer>;
|
||||
SurfaceTree=SurfaceTree,
|
||||
PointerElement=PointerElement::<Gles2Texture>,
|
||||
MoveGrabRenderElement=MoveGrabRenderElement,
|
||||
#[cfg(feature = "debug")]
|
||||
EguiFrame=EguiFrame,
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CursorMode {
|
||||
None,
|
||||
NotDefault,
|
||||
All,
|
||||
}
|
||||
|
||||
// TODO: due to the lifetime of MultiRenderer, we cannot be generic over CustomElem's renderer
|
||||
// util after GATs land. So we generate with the macro for Gles2 and then
|
||||
// do a manual impl for MultiRenderer.
|
||||
impl RenderElement<GlMultiRenderer<'_>> for CustomElem {
|
||||
fn id(&self) -> usize {
|
||||
RenderElement::<Gles2Renderer>::id(self)
|
||||
}
|
||||
|
||||
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
|
||||
RenderElement::<Gles2Renderer>::location(self, scale)
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
|
||||
RenderElement::<Gles2Renderer>::geometry(self, scale)
|
||||
}
|
||||
|
||||
fn accumulated_damage(
|
||||
&self,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
for_values: Option<SpaceOutputTuple<'_, '_>>,
|
||||
) -> Vec<Rectangle<i32, Physical>> {
|
||||
RenderElement::<Gles2Renderer>::accumulated_damage(self, scale, for_values)
|
||||
}
|
||||
|
||||
fn opaque_regions(
|
||||
&self,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
) -> Option<Vec<Rectangle<i32, Physical>>> {
|
||||
RenderElement::<Gles2Renderer>::opaque_regions(self, scale)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut GlMultiRenderer<'_>,
|
||||
frame: &mut GlMultiFrame,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
location: Point<f64, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
log: &Logger,
|
||||
) -> Result<(), MultiError<EglGlesBackend, EglGlesBackend>> {
|
||||
RenderElement::<Gles2Renderer>::draw(
|
||||
self,
|
||||
renderer.as_mut(),
|
||||
frame.as_mut(),
|
||||
scale,
|
||||
location,
|
||||
damage,
|
||||
log,
|
||||
)
|
||||
.map_err(MultiError::Render)
|
||||
}
|
||||
|
||||
fn z_index(&self) -> u8 {
|
||||
RenderElement::<Gles2Renderer>::z_index(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AsGles2Renderer {
|
||||
fn as_gles2(&mut self) -> &mut Gles2Renderer;
|
||||
}
|
||||
impl AsGles2Renderer for Gles2Renderer {
|
||||
fn as_gles2(&mut self) -> &mut Gles2Renderer {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl AsGles2Renderer for GlMultiRenderer<'_> {
|
||||
fn as_gles2(&mut self) -> &mut Gles2Renderer {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn needs_buffer_reset(output: &Output, state: &Common) -> bool {
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
struct DidCustomRendering(AtomicBool);
|
||||
|
||||
let will_render_custom = {
|
||||
let workspace = state.shell.active_space(output);
|
||||
workspace.get_fullscreen(output).is_some()
|
||||
};
|
||||
|
||||
let userdata = output.user_data();
|
||||
userdata.insert_if_missing(|| DidCustomRendering(AtomicBool::new(false)));
|
||||
userdata
|
||||
.get::<DidCustomRendering>()
|
||||
.unwrap()
|
||||
.0
|
||||
.swap(will_render_custom, Ordering::AcqRel)
|
||||
!= will_render_custom
|
||||
}
|
||||
|
||||
pub fn cursor_custom_elements<R>(
|
||||
pub fn cursor_elements<E, R>(
|
||||
renderer: &mut R,
|
||||
state: &Common,
|
||||
output: &Output,
|
||||
hardware_cursor: bool,
|
||||
) -> Vec<CustomElem>
|
||||
mode: CursorMode,
|
||||
) -> Vec<E>
|
||||
where
|
||||
R: AsGles2Renderer,
|
||||
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
<R as Renderer>::TextureId: Clone + 'static,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
E: From<CursorRenderElement<R>> + From<CosmicMappedRenderElement<R>>,
|
||||
{
|
||||
let mut custom_elements = Vec::new();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
let mut elements = Vec::new();
|
||||
|
||||
for seat in &state.seats {
|
||||
for seat in state.seats() {
|
||||
let pointer = match seat.get_pointer() {
|
||||
Some(ptr) => ptr,
|
||||
None => continue,
|
||||
};
|
||||
let location = state
|
||||
.shell
|
||||
.space_relative_output_geometry(pointer.current_location().to_i32_round(), output);
|
||||
let location = pointer.current_location() - output.current_location().to_f64();
|
||||
|
||||
if let Some(grab) = seat
|
||||
if mode != CursorMode::None {
|
||||
elements.extend(
|
||||
cursor::draw_cursor(
|
||||
renderer,
|
||||
seat,
|
||||
location,
|
||||
scale.into(),
|
||||
state.clock.now(),
|
||||
mode != CursorMode::NotDefault,
|
||||
)
|
||||
.into_iter()
|
||||
.map(E::from),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(wl_surface) = get_dnd_icon(seat) {
|
||||
elements.extend(
|
||||
cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale)
|
||||
.into_iter()
|
||||
.map(E::from),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(grab_elements) = seat
|
||||
.user_data()
|
||||
.get::<SeatMoveGrabState>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|state| state.render(seat, output))
|
||||
.map(|state| state.render::<E, R>(seat, output))
|
||||
{
|
||||
custom_elements.push(grab);
|
||||
}
|
||||
|
||||
if let Some(wl_surface) = get_dnd_icon(seat) {
|
||||
custom_elements.push(cursor::draw_dnd_icon(wl_surface, location.to_i32_round()).into());
|
||||
}
|
||||
|
||||
if let Some(cursor) = cursor::draw_cursor(
|
||||
renderer.as_gles2(),
|
||||
seat,
|
||||
location,
|
||||
&state.start_time,
|
||||
!hardware_cursor,
|
||||
) {
|
||||
custom_elements.push(cursor)
|
||||
elements.extend(grab_elements);
|
||||
}
|
||||
}
|
||||
|
||||
custom_elements
|
||||
elements
|
||||
}
|
||||
|
||||
pub fn render_output<R>(
|
||||
pub fn render_output<R, Target, OffTarget, Source>(
|
||||
gpu: Option<&DrmNode>,
|
||||
renderer: &mut R,
|
||||
age: u8,
|
||||
target: Target,
|
||||
damage_tracker: &mut DamageTrackedRenderer,
|
||||
age: usize,
|
||||
state: &mut Common,
|
||||
output: &Output,
|
||||
hardware_cursor: bool,
|
||||
#[cfg(feature = "debug")] mut fps: Option<&mut Fps>,
|
||||
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
|
||||
cursor_mode: CursorMode,
|
||||
screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
|
||||
fps: Option<&mut Fps>,
|
||||
) -> Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), RenderError<R>>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGles2Renderer,
|
||||
R: Renderer
|
||||
+ ImportAll
|
||||
+ ImportMem
|
||||
+ ExportMem
|
||||
+ Bind<Dmabuf>
|
||||
+ Bind<Target>
|
||||
+ Offscreen<OffTarget>
|
||||
+ Blit<Source>
|
||||
+ AsGlowRenderer,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
<R as Renderer>::TextureId: Clone + 'static,
|
||||
CustomElem: RenderElement<R>,
|
||||
<R as Renderer>::Error: From<Gles2Error>,
|
||||
CosmicElement<R>: RenderElement<R>,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
Source: Clone,
|
||||
{
|
||||
let workspace = state.shell.active_space(output).idx;
|
||||
let handle = state.shell.workspaces.active(output).handle;
|
||||
render_workspace(
|
||||
gpu,
|
||||
renderer,
|
||||
target,
|
||||
damage_tracker,
|
||||
age,
|
||||
state,
|
||||
workspace,
|
||||
output,
|
||||
hardware_cursor,
|
||||
&handle,
|
||||
cursor_mode,
|
||||
screencopy,
|
||||
fps,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_workspace<R>(
|
||||
pub fn render_workspace<R, Target, OffTarget, Source>(
|
||||
gpu: Option<&DrmNode>,
|
||||
renderer: &mut R,
|
||||
age: u8,
|
||||
target: Target,
|
||||
damage_tracker: &mut DamageTrackedRenderer,
|
||||
age: usize,
|
||||
state: &mut Common,
|
||||
space_idx: u8,
|
||||
output: &Output,
|
||||
hardware_cursor: bool,
|
||||
#[cfg(feature = "debug")] mut fps: Option<&mut Fps>,
|
||||
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
|
||||
handle: &WorkspaceHandle,
|
||||
mut cursor_mode: CursorMode,
|
||||
screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
|
||||
mut fps: Option<&mut Fps>,
|
||||
) -> Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), RenderError<R>>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGles2Renderer,
|
||||
R: Renderer
|
||||
+ ImportAll
|
||||
+ ImportMem
|
||||
+ ExportMem
|
||||
+ Bind<Dmabuf>
|
||||
+ Bind<Target>
|
||||
+ Offscreen<OffTarget>
|
||||
+ Blit<Source>
|
||||
+ AsGlowRenderer,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
<R as Renderer>::TextureId: Clone + 'static,
|
||||
CustomElem: RenderElement<R>,
|
||||
<R as Renderer>::Error: From<Gles2Error>,
|
||||
CosmicElement<R>: RenderElement<R>,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
Source: Clone,
|
||||
{
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(ref mut fps) = fps {
|
||||
fps.start();
|
||||
}
|
||||
|
||||
let space_idx = space_idx as usize;
|
||||
let workspace = &mut state.shell.spaces[space_idx];
|
||||
let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned();
|
||||
let workspace = state.shell.space_for_handle(&handle).ok_or(OutputNoMode)?;
|
||||
|
||||
let res = if let Some(window) = maybe_fullscreen_window {
|
||||
#[cfg(not(feature = "debug"))]
|
||||
{
|
||||
render_fullscreen(gpu, renderer, window, state, output, hardware_cursor)
|
||||
}
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
render_fullscreen(
|
||||
gpu,
|
||||
renderer,
|
||||
window,
|
||||
state,
|
||||
output,
|
||||
hardware_cursor,
|
||||
fps.as_deref_mut(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(feature = "debug"))]
|
||||
{
|
||||
render_desktop(
|
||||
gpu,
|
||||
renderer,
|
||||
age,
|
||||
state,
|
||||
space_idx,
|
||||
output,
|
||||
hardware_cursor,
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
render_desktop(
|
||||
gpu,
|
||||
renderer,
|
||||
age,
|
||||
state,
|
||||
space_idx,
|
||||
output,
|
||||
hardware_cursor,
|
||||
fps.as_deref_mut(),
|
||||
)
|
||||
}
|
||||
let screencopy_contains_embedded = screencopy.as_ref().map_or(false, |(_, sessions)| {
|
||||
sessions
|
||||
.iter()
|
||||
.any(|(s, _)| s.cursor_mode() == ScreencopyCursorMode::Embedded)
|
||||
});
|
||||
// cursor handling without a cursor_plane in this case is horrible.
|
||||
// because what if some session disagree and/or the backend wants to render with a different mode?
|
||||
// It seems we would need to render to an offscreen buffer in those cases (and do multiple renders, which messes with damage tracking).
|
||||
// So for now, we just pick the worst mode (embedded), if any requires it.
|
||||
//
|
||||
// Once we move to a cursor_plane, the default framebuffer will never contain a cursor and we can just composite the cursor for each session separately on top (or not).
|
||||
if screencopy_contains_embedded {
|
||||
cursor_mode = CursorMode::All;
|
||||
};
|
||||
|
||||
let mut elements: Vec<CosmicElement<R>> = cursor_elements(renderer, state, output, cursor_mode);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(ref mut fps) = fps {
|
||||
fps.end();
|
||||
{
|
||||
let output_geo = output.geometry();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
|
||||
if let Some(fps) = fps.as_mut() {
|
||||
let fps_overlay = fps_ui(
|
||||
gpu,
|
||||
state,
|
||||
renderer.glow_renderer_mut(),
|
||||
fps,
|
||||
Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
(output_geo.size.w.min(400), output_geo.size.h.min(800)),
|
||||
),
|
||||
scale,
|
||||
)
|
||||
.map_err(<R as Renderer>::Error::from)
|
||||
.map_err(RenderError::Rendering)?;
|
||||
elements.push(fps_overlay.into());
|
||||
}
|
||||
}
|
||||
|
||||
elements.extend(
|
||||
workspace
|
||||
.render_output::<R>(output)
|
||||
.map_err(|_| OutputNoMode)?
|
||||
.into_iter()
|
||||
.map(Into::into),
|
||||
);
|
||||
|
||||
if let Some(fps) = fps.as_mut() {
|
||||
fps.elements();
|
||||
}
|
||||
|
||||
renderer.bind(target).map_err(RenderError::Rendering)?;
|
||||
let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None);
|
||||
|
||||
if let Some(fps) = fps.as_mut() {
|
||||
fps.render();
|
||||
}
|
||||
|
||||
if let Some((source, buffers)) = screencopy {
|
||||
if res.is_ok() {
|
||||
for (session, params) in buffers {
|
||||
match render_session(
|
||||
gpu.cloned(),
|
||||
renderer,
|
||||
&session,
|
||||
params,
|
||||
output.current_transform(),
|
||||
|_node, buffer, renderer, dtr, age| {
|
||||
let res = dtr.damage_output(age, &elements, slog_scope::logger())?;
|
||||
|
||||
if let (Some(ref damage), _) = &res {
|
||||
if let Ok(dmabuf) = get_dmabuf(buffer) {
|
||||
renderer.bind(dmabuf).map_err(RenderError::Rendering)?;
|
||||
} else {
|
||||
let size = buffer_dimensions(buffer).unwrap();
|
||||
let render_buffer = renderer
|
||||
.create_buffer(size)
|
||||
.map_err(RenderError::Rendering)?;
|
||||
renderer
|
||||
.bind(render_buffer)
|
||||
.map_err(RenderError::Rendering)?;
|
||||
}
|
||||
for rect in damage {
|
||||
renderer
|
||||
.blit_from(source.clone(), *rect, *rect, TextureFilter::Nearest)
|
||||
.map_err(RenderError::Rendering)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
},
|
||||
) {
|
||||
Ok(true) => {} // success
|
||||
Ok(false) => state.still_pending(session.clone(), params.clone()),
|
||||
Err(err) => {
|
||||
slog_scope::warn!("Error rendering to screencopy session: {}", err);
|
||||
session.failed(FailureReason::Unspec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(fps) = fps.as_mut() {
|
||||
fps.screencopy();
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn render_desktop<R>(
|
||||
_gpu: Option<&DrmNode>,
|
||||
renderer: &mut R,
|
||||
age: u8,
|
||||
state: &mut Common,
|
||||
space_idx: usize,
|
||||
output: &Output,
|
||||
hardware_cursor: bool,
|
||||
#[cfg(feature = "debug")] fps: Option<&mut Fps>,
|
||||
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGles2Renderer,
|
||||
<R as Renderer>::TextureId: Clone + 'static,
|
||||
CustomElem: RenderElement<R>,
|
||||
{
|
||||
let mut custom_elements = Vec::<CustomElem>::new();
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
let workspace = &state.shell.spaces[space_idx];
|
||||
let output_geo = workspace
|
||||
.space
|
||||
.output_geometry(output)
|
||||
.unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0)));
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
|
||||
if let Some(fps) = fps {
|
||||
let fps_overlay = fps_ui(
|
||||
_gpu,
|
||||
state,
|
||||
fps,
|
||||
output_geo.to_f64().to_physical(scale),
|
||||
scale,
|
||||
);
|
||||
custom_elements.push(fps_overlay.into());
|
||||
}
|
||||
|
||||
let area = Rectangle::<f64, smithay::utils::Logical>::from_loc_and_size(
|
||||
state
|
||||
.shell
|
||||
.space_relative_output_geometry((0.0f64, 0.0f64), output),
|
||||
state.shell.global_space().to_f64().size,
|
||||
)
|
||||
.to_physical(scale);
|
||||
if let Some(log_ui) = log_ui(state, area, scale, output_geo.size.w as f32 * 0.6) {
|
||||
custom_elements.push(log_ui.into());
|
||||
}
|
||||
if let Some(debug_overlay) = debug_ui(state, area, scale) {
|
||||
custom_elements.push(debug_overlay.into());
|
||||
}
|
||||
}
|
||||
|
||||
custom_elements.extend(cursor_custom_elements(
|
||||
renderer,
|
||||
state,
|
||||
output,
|
||||
hardware_cursor,
|
||||
));
|
||||
|
||||
state.shell.spaces[space_idx].space.render_output(
|
||||
renderer,
|
||||
&output,
|
||||
age as usize,
|
||||
CLEAR_COLOR,
|
||||
&*custom_elements,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_fullscreen<R>(
|
||||
_gpu: Option<&DrmNode>,
|
||||
renderer: &mut R,
|
||||
window: Window,
|
||||
state: &mut Common,
|
||||
output: &Output,
|
||||
hardware_cursor: bool,
|
||||
#[cfg(feature = "debug")] fps: Option<&mut Fps>,
|
||||
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGles2Renderer,
|
||||
<R as Renderer>::TextureId: Clone + 'static,
|
||||
CustomElem: RenderElement<R>,
|
||||
{
|
||||
let transform = Transform::from(output.current_transform());
|
||||
let mode = output.current_mode().unwrap();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
|
||||
let mut custom_elements = Vec::<CustomElem>::new();
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(fps) = fps {
|
||||
let output_geo = output.geometry();
|
||||
let fps_overlay = fps_ui(
|
||||
_gpu,
|
||||
state,
|
||||
fps,
|
||||
Rectangle::from_loc_and_size((0, 0), output_geo.size)
|
||||
.to_f64()
|
||||
.to_physical(scale),
|
||||
scale,
|
||||
);
|
||||
custom_elements.push(fps_overlay.into());
|
||||
}
|
||||
|
||||
custom_elements.extend(cursor_custom_elements(
|
||||
renderer,
|
||||
state,
|
||||
output,
|
||||
hardware_cursor,
|
||||
));
|
||||
|
||||
renderer
|
||||
.render(mode.size, transform, |renderer, frame| {
|
||||
let mut damage = window.accumulated_damage((0.0, 0.0), scale, None);
|
||||
frame.clear(
|
||||
CLEAR_COLOR,
|
||||
&[Rectangle::from_loc_and_size((0, 0), mode.size)],
|
||||
)?;
|
||||
draw_window(
|
||||
renderer,
|
||||
frame,
|
||||
&window,
|
||||
scale,
|
||||
(0.0, 0.0),
|
||||
&[Rectangle::from_loc_and_size((0, 0), mode.size)],
|
||||
&slog_scope::logger(),
|
||||
)?;
|
||||
draw_window_popups(
|
||||
renderer,
|
||||
frame,
|
||||
&window,
|
||||
scale,
|
||||
(0.0, 0.0),
|
||||
&[Rectangle::from_loc_and_size((0, 0), mode.size)],
|
||||
&slog_scope::logger(),
|
||||
)?;
|
||||
let layer_map = layer_map_for_output(output);
|
||||
for layer_surface in layer_map.layers_on(WlrLayer::Overlay) {
|
||||
let geo = layer_map.layer_geometry(&layer_surface).unwrap();
|
||||
draw_layer_surface(
|
||||
renderer,
|
||||
frame,
|
||||
layer_surface,
|
||||
scale,
|
||||
geo.loc.to_f64().to_physical(scale),
|
||||
&[Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
geo.size.to_physical_precise_round(scale),
|
||||
)],
|
||||
&slog_scope::logger(),
|
||||
)?;
|
||||
draw_layer_popups(
|
||||
renderer,
|
||||
frame,
|
||||
layer_surface,
|
||||
scale,
|
||||
geo.loc.to_f64().to_physical(scale),
|
||||
&[Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
geo.size.to_physical_precise_round(scale),
|
||||
)],
|
||||
&slog_scope::logger(),
|
||||
)?;
|
||||
damage.extend(damage_from_surface_tree(
|
||||
layer_surface.wl_surface(),
|
||||
geo.loc.to_f64().to_physical(scale),
|
||||
scale,
|
||||
None,
|
||||
));
|
||||
}
|
||||
for elem in custom_elements {
|
||||
let loc = elem.location(scale);
|
||||
let geo = elem.geometry(scale);
|
||||
let elem_damage = elem.accumulated_damage(scale, None);
|
||||
elem.draw(renderer, frame, scale, loc, &[geo], &slog_scope::logger())?;
|
||||
damage.extend(elem_damage)
|
||||
}
|
||||
Ok(Some(damage))
|
||||
})
|
||||
.and_then(std::convert::identity)
|
||||
.map_err(RenderError::<R>::Rendering)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,22 @@ use crate::{
|
|||
input::Devices,
|
||||
state::{BackendData, Common, Data},
|
||||
utils::prelude::*,
|
||||
wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession},
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use smithay::{
|
||||
backend::{
|
||||
renderer::{ImportDma, ImportEgl},
|
||||
renderer::{
|
||||
damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, ImportDma,
|
||||
ImportEgl,
|
||||
},
|
||||
winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice},
|
||||
},
|
||||
desktop::layer_map_for_output,
|
||||
output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
|
||||
reexports::{
|
||||
calloop::{ping, EventLoop},
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::DisplayHandle,
|
||||
},
|
||||
utils::Transform,
|
||||
|
|
@ -26,52 +31,71 @@ use std::cell::RefCell;
|
|||
#[cfg(feature = "debug")]
|
||||
use crate::state::Fps;
|
||||
|
||||
use super::render::CursorMode;
|
||||
|
||||
pub struct WinitState {
|
||||
// The winit backend currently has no notion of multiple windows
|
||||
pub backend: WinitGraphicsBackend,
|
||||
pub backend: WinitGraphicsBackend<GlowRenderer>,
|
||||
output: Output,
|
||||
age_reset: u8,
|
||||
damage_tracker: DamageTrackedRenderer,
|
||||
screencopy: Vec<(ScreencopySession, BufferParams)>,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: Fps,
|
||||
}
|
||||
|
||||
impl WinitState {
|
||||
pub fn render_output(&mut self, state: &mut Common) -> Result<()> {
|
||||
if render::needs_buffer_reset(&self.output, state) {
|
||||
self.reset_buffers();
|
||||
}
|
||||
|
||||
self.backend
|
||||
.bind()
|
||||
.with_context(|| "Failed to bind buffer")?;
|
||||
let age = if self.age_reset > 0 {
|
||||
self.age_reset -= 1;
|
||||
0
|
||||
} else {
|
||||
self.backend.buffer_age().unwrap_or(0)
|
||||
};
|
||||
let age = self.backend.buffer_age().unwrap_or(0);
|
||||
|
||||
match render::render_output(
|
||||
let surface = self.backend.egl_surface();
|
||||
match render::render_output::<_, _, Gles2Renderbuffer, _>(
|
||||
None,
|
||||
self.backend.renderer(),
|
||||
age as u8,
|
||||
surface.clone(),
|
||||
&mut self.damage_tracker,
|
||||
age,
|
||||
state,
|
||||
&self.output,
|
||||
true,
|
||||
CursorMode::NotDefault,
|
||||
if !self.screencopy.is_empty() {
|
||||
Some((surface, &self.screencopy))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
#[cfg(not(feature = "debug"))]
|
||||
None,
|
||||
#[cfg(feature = "debug")]
|
||||
Some(&mut self.fps),
|
||||
) {
|
||||
Ok(damage) => {
|
||||
state
|
||||
.shell
|
||||
.active_space_mut(&self.output)
|
||||
.space
|
||||
.send_frames(state.start_time.elapsed().as_millis() as u32);
|
||||
Ok((damage, states)) => {
|
||||
self.screencopy.clear();
|
||||
self.backend
|
||||
.submit(damage.as_ref().map(|x| &**x))
|
||||
.submit(damage.as_deref())
|
||||
.with_context(|| "Failed to submit buffer for display")?;
|
||||
#[cfg(feature = "debug")]
|
||||
self.fps.displayed();
|
||||
state.send_frames(&self.output, &states);
|
||||
if damage.is_some() {
|
||||
let mut output_presentation_feedback =
|
||||
state.take_presentation_feedback(&self.output, &states);
|
||||
output_presentation_feedback.presented(
|
||||
state.clock.now(),
|
||||
self.output
|
||||
.current_mode()
|
||||
.map(|mode| mode.refresh as u32)
|
||||
.unwrap_or_default(),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
)
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
for (session, params) in self.screencopy.drain(..) {
|
||||
state.still_pending(session, params)
|
||||
}
|
||||
anyhow::bail!("Rendering failed: {}", err);
|
||||
}
|
||||
};
|
||||
|
|
@ -105,8 +129,10 @@ impl WinitState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn reset_buffers(&mut self) {
|
||||
self.age_reset = 3;
|
||||
pub fn pending_screencopy(&mut self, new: Option<Vec<(ScreencopySession, BufferParams)>>) {
|
||||
if let Some(sessions) = new {
|
||||
self.screencopy.extend(sessions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +159,6 @@ pub fn init_backend(
|
|||
refresh: 60_000,
|
||||
};
|
||||
let output = Output::new(name, props, None);
|
||||
let _global = output.create_global::<State>(dh);
|
||||
output.add_mode(mode);
|
||||
output.set_preferred(mode);
|
||||
output.change_current_state(
|
||||
|
|
@ -180,8 +205,7 @@ pub fn init_backend(
|
|||
.handle()
|
||||
.insert_source(event_source, move |_, _, data| {
|
||||
match input.dispatch_new_events(|event| {
|
||||
data.state
|
||||
.process_winit_event(&data.display.handle(), event, &render_ping_handle)
|
||||
data.state.process_winit_event(event, &render_ping_handle)
|
||||
}) {
|
||||
Ok(_) => {
|
||||
event_ping_handle.ping();
|
||||
|
|
@ -189,7 +213,11 @@ pub fn init_backend(
|
|||
}
|
||||
Err(winit::WinitError::WindowClosed) => {
|
||||
let output = data.state.backend.winit().output.clone();
|
||||
data.state.common.shell.remove_output(&output);
|
||||
let seats = data.state.common.seats().cloned().collect::<Vec<_>>();
|
||||
data.state
|
||||
.common
|
||||
.shell
|
||||
.remove_output(&output, seats.into_iter());
|
||||
if let Some(token) = token.take() {
|
||||
event_loop_handle.remove(token);
|
||||
}
|
||||
|
|
@ -199,27 +227,30 @@ pub fn init_backend(
|
|||
.map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?;
|
||||
event_ping.ping();
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
let fps = Fps::new(backend.renderer());
|
||||
|
||||
state.backend = BackendData::Winit(WinitState {
|
||||
backend,
|
||||
output: output.clone(),
|
||||
damage_tracker: DamageTrackedRenderer::from_output(&output),
|
||||
screencopy: Vec::new(),
|
||||
#[cfg(feature = "debug")]
|
||||
fps: Fps::default(),
|
||||
age_reset: 0,
|
||||
fps,
|
||||
});
|
||||
state
|
||||
.common
|
||||
.output_configuration_state
|
||||
.add_heads(std::iter::once(&output));
|
||||
state.common.output_configuration_state.update();
|
||||
state.common.shell.add_output(&output);
|
||||
let seats = state.common.seats().cloned().collect::<Vec<_>>();
|
||||
state.common.config.read_outputs(
|
||||
std::iter::once(&output),
|
||||
&mut state.common.output_configuration_state,
|
||||
&mut state.backend,
|
||||
&mut state.common.shell,
|
||||
seats.iter().cloned(),
|
||||
&state.common.event_loop_handle,
|
||||
);
|
||||
state.common.shell.refresh_outputs();
|
||||
state.common.config.write_outputs(std::iter::once(&output));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -227,7 +258,7 @@ pub fn init_backend(
|
|||
fn init_egl_client_side(
|
||||
dh: &DisplayHandle,
|
||||
state: &mut State,
|
||||
renderer: &mut WinitGraphicsBackend,
|
||||
renderer: &mut WinitGraphicsBackend<GlowRenderer>,
|
||||
) -> Result<()> {
|
||||
let bind_result = renderer.renderer().bind_wl_display(dh);
|
||||
match bind_result {
|
||||
|
|
@ -250,19 +281,14 @@ fn init_egl_client_side(
|
|||
}
|
||||
|
||||
impl State {
|
||||
pub fn process_winit_event(
|
||||
&mut self,
|
||||
dh: &DisplayHandle,
|
||||
event: WinitEvent,
|
||||
render_ping: &ping::Ping,
|
||||
) {
|
||||
pub fn process_winit_event(&mut self, event: WinitEvent, render_ping: &ping::Ping) {
|
||||
// here we can handle special cases for winit inputs
|
||||
match event {
|
||||
WinitEvent::Focus(true) => {
|
||||
for seat in self.common.seats.clone().iter() {
|
||||
for seat in self.common.seats().cloned().collect::<Vec<_>>().iter() {
|
||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||
if devices.has_device(&WinitVirtualDevice) {
|
||||
set_active_output(seat, &self.backend.winit().output);
|
||||
seat.set_active_output(&self.backend.winit().output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -286,7 +312,7 @@ impl State {
|
|||
output.delete_mode(output.current_mode().unwrap());
|
||||
output.set_preferred(mode);
|
||||
output.change_current_state(Some(mode), None, None, None);
|
||||
layer_map_for_output(output).arrange(dh);
|
||||
layer_map_for_output(output).arrange();
|
||||
self.common.output_configuration_state.update();
|
||||
self.common.shell.refresh_outputs();
|
||||
render_ping.ping();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
input::Devices,
|
||||
state::{BackendData, Common, Data},
|
||||
utils::prelude::*,
|
||||
wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession},
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use smithay::{
|
||||
|
|
@ -13,7 +14,10 @@ use smithay::{
|
|||
allocator::dmabuf::Dmabuf,
|
||||
egl::{EGLContext, EGLDisplay},
|
||||
input::{Event, InputEvent},
|
||||
renderer::{gles2::Gles2Renderer, Bind, ImportDma, ImportEgl},
|
||||
renderer::{
|
||||
damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, Bind,
|
||||
ImportDma, ImportEgl,
|
||||
},
|
||||
x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface},
|
||||
},
|
||||
desktop::layer_map_for_output,
|
||||
|
|
@ -21,6 +25,7 @@ use smithay::{
|
|||
reexports::{
|
||||
calloop::{ping, EventLoop, LoopHandle},
|
||||
gbm::{Device as GbmDevice, FdWrapper},
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::DisplayHandle,
|
||||
},
|
||||
utils::Transform,
|
||||
|
|
@ -36,7 +41,7 @@ use crate::state::Fps;
|
|||
pub struct X11State {
|
||||
allocator: Arc<Mutex<GbmDevice<FdWrapper>>>,
|
||||
_egl: EGLDisplay,
|
||||
pub renderer: Gles2Renderer,
|
||||
pub renderer: GlowRenderer,
|
||||
surfaces: Vec<Surface>,
|
||||
handle: X11Handle,
|
||||
}
|
||||
|
|
@ -114,12 +119,14 @@ impl X11State {
|
|||
self.surfaces.push(Surface {
|
||||
window,
|
||||
surface,
|
||||
damage_tracker: DamageTrackedRenderer::from_output(&output),
|
||||
output: output.clone(),
|
||||
render: ping.clone(),
|
||||
dirty: false,
|
||||
pending: true,
|
||||
screencopy: Vec::new(),
|
||||
#[cfg(feature = "debug")]
|
||||
fps: Fps::default(),
|
||||
fps: Fps::new(&mut self.renderer),
|
||||
});
|
||||
|
||||
// schedule first render
|
||||
|
|
@ -127,9 +134,16 @@ impl X11State {
|
|||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn schedule_render(&mut self, output: &Output) {
|
||||
pub fn schedule_render(
|
||||
&mut self,
|
||||
output: &Output,
|
||||
screencopy: Option<Vec<(ScreencopySession, BufferParams)>>,
|
||||
) {
|
||||
if let Some(surface) = self.surfaces.iter_mut().find(|s| s.output == *output) {
|
||||
surface.dirty = true;
|
||||
if let Some(sessions) = screencopy {
|
||||
surface.screencopy.extend(sessions);
|
||||
}
|
||||
if !surface.pending {
|
||||
surface.render.ping();
|
||||
}
|
||||
|
|
@ -168,6 +182,8 @@ impl X11State {
|
|||
|
||||
pub struct Surface {
|
||||
window: Window,
|
||||
damage_tracker: DamageTrackedRenderer,
|
||||
screencopy: Vec<(ScreencopySession, BufferParams)>,
|
||||
surface: X11Surface,
|
||||
output: Output,
|
||||
render: ping::Ping,
|
||||
|
|
@ -178,44 +194,56 @@ pub struct Surface {
|
|||
}
|
||||
|
||||
impl Surface {
|
||||
pub fn render_output(
|
||||
&mut self,
|
||||
renderer: &mut Gles2Renderer,
|
||||
state: &mut Common,
|
||||
) -> Result<()> {
|
||||
if render::needs_buffer_reset(&self.output, state) {
|
||||
self.surface.reset_buffers();
|
||||
}
|
||||
|
||||
pub fn render_output(&mut self, renderer: &mut GlowRenderer, state: &mut Common) -> Result<()> {
|
||||
let (buffer, age) = self
|
||||
.surface
|
||||
.buffer()
|
||||
.with_context(|| "Failed to allocate buffer")?;
|
||||
renderer
|
||||
.bind(buffer)
|
||||
.with_context(|| "Failed to bind buffer")?;
|
||||
|
||||
match render::render_output(
|
||||
match render::render_output::<_, _, Gles2Renderbuffer, _>(
|
||||
None,
|
||||
renderer,
|
||||
age as u8,
|
||||
buffer.clone(),
|
||||
&mut self.damage_tracker,
|
||||
age as usize,
|
||||
state,
|
||||
&self.output,
|
||||
true,
|
||||
render::CursorMode::NotDefault,
|
||||
if !self.screencopy.is_empty() {
|
||||
Some((buffer, &self.screencopy))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
#[cfg(not(feature = "debug"))]
|
||||
None,
|
||||
#[cfg(feature = "debug")]
|
||||
Some(&mut self.fps),
|
||||
) {
|
||||
Ok(_) => {
|
||||
state
|
||||
.shell
|
||||
.active_space_mut(&self.output)
|
||||
.space
|
||||
.send_frames(state.start_time.elapsed().as_millis() as u32);
|
||||
Ok((damage, states)) => {
|
||||
self.screencopy.clear();
|
||||
self.surface
|
||||
.submit()
|
||||
.with_context(|| "Failed to submit buffer for display")?;
|
||||
#[cfg(feature = "debug")]
|
||||
self.fps.displayed();
|
||||
state.send_frames(&self.output, &states);
|
||||
if damage.is_some() {
|
||||
let mut output_presentation_feedback =
|
||||
state.take_presentation_feedback(&self.output, &states);
|
||||
output_presentation_feedback.presented(
|
||||
state.clock.now(),
|
||||
self.output
|
||||
.current_mode()
|
||||
.map(|mode| mode.refresh as u32)
|
||||
.unwrap_or_default(),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
)
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
for (session, params) in self.screencopy.drain(..) {
|
||||
state.still_pending(session, params)
|
||||
}
|
||||
self.surface.reset_buffers();
|
||||
anyhow::bail!("Rendering failed: {}", err);
|
||||
}
|
||||
|
|
@ -247,7 +275,7 @@ pub fn init_backend(
|
|||
// Create the OpenGL context
|
||||
let context = EGLContext::new(&egl, None).with_context(|| "Failed to create EGL context")?;
|
||||
// Create a renderer
|
||||
let mut renderer = unsafe { Gles2Renderer::new(context, None) }
|
||||
let mut renderer = unsafe { GlowRenderer::new(context, None) }
|
||||
.with_context(|| "Failed to initialize renderer")?;
|
||||
|
||||
init_egl_client_side(dh, state, &mut renderer)?;
|
||||
|
|
@ -269,20 +297,19 @@ pub fn init_backend(
|
|||
.common
|
||||
.output_configuration_state
|
||||
.add_heads(std::iter::once(&output));
|
||||
state.common.output_configuration_state.update();
|
||||
state.common.shell.add_output(&output);
|
||||
let seats = state.common.seats().cloned().collect::<Vec<_>>();
|
||||
state.common.config.read_outputs(
|
||||
std::iter::once(&output),
|
||||
&mut state.common.output_configuration_state,
|
||||
&mut state.backend,
|
||||
&mut state.common.shell,
|
||||
seats.iter().cloned(),
|
||||
&state.common.event_loop_handle,
|
||||
);
|
||||
state.common.shell.refresh_outputs();
|
||||
state.common.config.write_outputs(std::iter::once(&output));
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(backend, |event, _, data| match event {
|
||||
.insert_source(backend, move |event, _, data| match event {
|
||||
X11Event::CloseRequested { window_id } => {
|
||||
// TODO: drain_filter
|
||||
let mut outputs_removed = Vec::new();
|
||||
|
|
@ -303,7 +330,10 @@ pub fn init_backend(
|
|||
.surfaces
|
||||
.retain(|s| s.window.id() != window_id);
|
||||
for output in outputs_removed.into_iter() {
|
||||
data.state.common.shell.remove_output(&output);
|
||||
data.state
|
||||
.common
|
||||
.shell
|
||||
.remove_output(&output, seats.iter().cloned());
|
||||
}
|
||||
}
|
||||
X11Event::Resized {
|
||||
|
|
@ -336,7 +366,7 @@ pub fn init_backend(
|
|||
output.delete_mode(output.current_mode().unwrap());
|
||||
output.change_current_state(Some(mode), None, None, None);
|
||||
output.set_preferred(mode);
|
||||
layer_map_for_output(output).arrange(&data.display.handle());
|
||||
layer_map_for_output(output).arrange();
|
||||
data.state.common.output_configuration_state.update();
|
||||
data.state.common.shell.refresh_outputs();
|
||||
surface.dirty = true;
|
||||
|
|
@ -368,11 +398,10 @@ pub fn init_backend(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn init_egl_client_side(
|
||||
dh: &DisplayHandle,
|
||||
state: &mut State,
|
||||
renderer: &mut Gles2Renderer,
|
||||
) -> Result<()> {
|
||||
fn init_egl_client_side<R>(dh: &DisplayHandle, state: &mut State, renderer: &mut R) -> Result<()>
|
||||
where
|
||||
R: ImportEgl + ImportDma,
|
||||
{
|
||||
let bind_result = renderer.bind_wl_display(dh);
|
||||
match bind_result {
|
||||
Ok(_) => {
|
||||
|
|
@ -405,10 +434,10 @@ impl State {
|
|||
.unwrap();
|
||||
|
||||
let device = event.device();
|
||||
for seat in self.common.seats.clone().iter() {
|
||||
for seat in self.common.seats().cloned().collect::<Vec<_>>().iter() {
|
||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||
if devices.has_device(&device) {
|
||||
set_active_output(seat, &output);
|
||||
seat.set_active_output(&output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -420,8 +449,7 @@ impl State {
|
|||
self.process_input_event(event);
|
||||
// TODO actually figure out the output
|
||||
for output in self.common.shell.outputs() {
|
||||
self.backend
|
||||
.schedule_render(&self.common.event_loop_handle, output);
|
||||
self.backend.x11().schedule_render(output, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
shell::{focus::FocusDirection, Shell},
|
||||
state::{BackendData, Data},
|
||||
shell::{focus::FocusDirection, layout::tiling::Direction, Shell, WorkspaceAmount},
|
||||
state::{BackendData, Data, State},
|
||||
wayland::protocols::output_configuration::OutputConfigurationState,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smithay::input::Seat;
|
||||
pub use smithay::{
|
||||
backend::input::KeyState,
|
||||
input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState},
|
||||
|
|
@ -32,6 +34,7 @@ pub struct Config {
|
|||
pub struct StaticConfig {
|
||||
pub key_bindings: HashMap<KeyPattern, Action>,
|
||||
pub workspace_mode: WorkspaceMode,
|
||||
pub workspace_amount: WorkspaceAmount,
|
||||
pub floating_default: bool,
|
||||
}
|
||||
|
||||
|
|
@ -215,6 +218,7 @@ impl Config {
|
|||
StaticConfig {
|
||||
key_bindings: HashMap::new(),
|
||||
workspace_mode: WorkspaceMode::Global,
|
||||
workspace_amount: WorkspaceAmount::Dynamic,
|
||||
floating_default: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -276,12 +280,14 @@ impl Config {
|
|||
|
||||
pub fn read_outputs(
|
||||
&mut self,
|
||||
outputs: impl Iterator<Item = impl std::borrow::Borrow<Output>>,
|
||||
output_state: &mut OutputConfigurationState<State>,
|
||||
backend: &mut BackendData,
|
||||
shell: &mut Shell,
|
||||
seats: impl Iterator<Item = Seat<State>>,
|
||||
loop_handle: &LoopHandle<'_, Data>,
|
||||
) {
|
||||
let outputs = outputs.map(|x| x.borrow().clone()).collect::<Vec<_>>();
|
||||
let seats = seats.collect::<Vec<_>>();
|
||||
let outputs = output_state.outputs().collect::<Vec<_>>();
|
||||
let mut infos = outputs
|
||||
.iter()
|
||||
.cloned()
|
||||
|
|
@ -305,14 +311,19 @@ impl Config {
|
|||
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
|
||||
{
|
||||
let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone();
|
||||
let enabled = output_config.enabled;
|
||||
*output
|
||||
.user_data()
|
||||
.get::<RefCell<OutputConfig>>()
|
||||
.unwrap()
|
||||
.borrow_mut() = output_config;
|
||||
if let Err(err) =
|
||||
backend.apply_config_for_output(&output, false, shell, loop_handle)
|
||||
{
|
||||
if let Err(err) = backend.apply_config_for_output(
|
||||
&output,
|
||||
false,
|
||||
shell,
|
||||
seats.iter().cloned(),
|
||||
loop_handle,
|
||||
) {
|
||||
slog_scope::warn!(
|
||||
"Failed to set new config for output {}: {}",
|
||||
output.name(),
|
||||
|
|
@ -320,6 +331,12 @@ impl Config {
|
|||
);
|
||||
reset = true;
|
||||
break;
|
||||
} else {
|
||||
if enabled {
|
||||
output_state.enable_head(&output);
|
||||
} else {
|
||||
output_state.disable_head(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,22 +346,36 @@ impl Config {
|
|||
.into_iter()
|
||||
.zip(known_good_configs.into_iter())
|
||||
{
|
||||
let enabled = output_config.enabled;
|
||||
*output
|
||||
.user_data()
|
||||
.get::<RefCell<OutputConfig>>()
|
||||
.unwrap()
|
||||
.borrow_mut() = output_config;
|
||||
if let Err(err) =
|
||||
backend.apply_config_for_output(&output, false, shell, loop_handle)
|
||||
{
|
||||
if let Err(err) = backend.apply_config_for_output(
|
||||
&output,
|
||||
false,
|
||||
shell,
|
||||
seats.iter().cloned(),
|
||||
loop_handle,
|
||||
) {
|
||||
slog_scope::error!(
|
||||
"Failed to reset config for output {}: {}",
|
||||
output.name(),
|
||||
err
|
||||
);
|
||||
} else {
|
||||
if enabled {
|
||||
output_state.enable_head(&output);
|
||||
} else {
|
||||
output_state.disable_head(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output_state.update();
|
||||
self.write_outputs(output_state.outputs());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -689,7 +720,7 @@ pub enum KeyModifier {
|
|||
Ctrl,
|
||||
Alt,
|
||||
Shift,
|
||||
Logo,
|
||||
Super,
|
||||
CapsLock,
|
||||
NumLock,
|
||||
}
|
||||
|
|
@ -721,7 +752,7 @@ impl std::ops::AddAssign<KeyModifier> for KeyModifiers {
|
|||
KeyModifier::Ctrl => self.ctrl = true,
|
||||
KeyModifier::Alt => self.alt = true,
|
||||
KeyModifier::Shift => self.shift = true,
|
||||
KeyModifier::Logo => self.logo = true,
|
||||
KeyModifier::Super => self.logo = true,
|
||||
KeyModifier::CapsLock => self.caps_lock = true,
|
||||
KeyModifier::NumLock => self.num_lock = true,
|
||||
};
|
||||
|
|
@ -780,13 +811,30 @@ pub enum Action {
|
|||
Terminate,
|
||||
Debug,
|
||||
Close,
|
||||
|
||||
Workspace(u8),
|
||||
NextWorkspace,
|
||||
PreviousWorkspace,
|
||||
LastWorkspace,
|
||||
MoveToWorkspace(u8),
|
||||
MoveToNextWorkspace,
|
||||
MoveToPreviousWorkspace,
|
||||
MoveToLastWorkspace,
|
||||
|
||||
NextOutput,
|
||||
PreviousOutput,
|
||||
MoveToNextOutput,
|
||||
MoveToPreviousOutput,
|
||||
|
||||
Focus(FocusDirection),
|
||||
Move(Direction),
|
||||
|
||||
ToggleOrientation,
|
||||
Orientation(crate::shell::layout::Orientation),
|
||||
|
||||
ToggleTiling,
|
||||
ToggleWindowFloating,
|
||||
Fullscreen,
|
||||
Screenshot,
|
||||
|
||||
Maximize,
|
||||
Spawn(String),
|
||||
}
|
||||
|
|
|
|||
499
src/debug.rs
499
src/debug.rs
|
|
@ -1,22 +1,35 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::state::{Common, Fps};
|
||||
use egui::{Color32, Vec2};
|
||||
use smithay::{
|
||||
backend::drm::DrmNode,
|
||||
desktop::layer_map_for_output,
|
||||
reexports::wayland_server::Resource,
|
||||
utils::{IsAlive, Physical, Rectangle},
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
renderer::{
|
||||
element::texture::TextureRenderElement,
|
||||
gles2::{Gles2Error, Gles2Texture},
|
||||
glow::GlowRenderer,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Rectangle},
|
||||
};
|
||||
pub use smithay_egui::EguiFrame;
|
||||
|
||||
pub const ELEMENTS_COLOR: Color32 = Color32::from_rgb(70, 198, 115);
|
||||
pub const RENDER_COLOR: Color32 = Color32::from_rgb(29, 114, 58);
|
||||
pub const SCREENCOPY_COLOR: Color32 = Color32::from_rgb(253, 178, 39);
|
||||
pub const DISPLAY_COLOR: Color32 = Color32::from_rgb(41, 184, 209);
|
||||
|
||||
pub fn fps_ui(
|
||||
gpu: Option<&DrmNode>,
|
||||
state: &Common,
|
||||
renderer: &mut GlowRenderer,
|
||||
fps: &mut Fps,
|
||||
area: Rectangle<f64, Physical>,
|
||||
area: Rectangle<i32, Logical>,
|
||||
scale: f64,
|
||||
) -> EguiFrame {
|
||||
use egui::widgets::plot::{Bar, BarChart, HLine, Legend, Plot};
|
||||
) -> Result<TextureRenderElement<Gles2Texture>, Gles2Error> {
|
||||
use egui::widgets::plot::{Bar, BarChart, Legend, Plot};
|
||||
|
||||
let (max, min, avg, avg_fps) = (
|
||||
fps.max_frametime().as_secs_f64(),
|
||||
|
|
@ -24,404 +37,154 @@ pub fn fps_ui(
|
|||
fps.avg_frametime().as_secs_f64(),
|
||||
fps.avg_fps(),
|
||||
);
|
||||
let bars = fps
|
||||
let (max_disp, min_disp) = (
|
||||
fps.max_time_to_display().as_secs_f64(),
|
||||
fps.min_time_to_display().as_secs_f64(),
|
||||
);
|
||||
|
||||
let amount = avg_fps.round() as usize * 2;
|
||||
let ((bars_elements, bars_render), (bars_screencopy, bars_displayed)): (
|
||||
(Vec<Bar>, Vec<Bar>),
|
||||
(Vec<Bar>, Vec<Bar>),
|
||||
) = fps
|
||||
.frames
|
||||
.iter()
|
||||
.rev()
|
||||
.take(30)
|
||||
.take(amount)
|
||||
.rev()
|
||||
.enumerate()
|
||||
.map(|(i, (_, d))| {
|
||||
let value = d.as_secs_f64();
|
||||
let transformed = ((value - min) / (max - min) * 255.0).round() as u8;
|
||||
Bar::new(i as f64, transformed as f64).fill(egui::Color32::from_rgb(
|
||||
transformed,
|
||||
255 - transformed,
|
||||
0,
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
.map(|(i, frame)| {
|
||||
let elements_val = frame.duration_elements.as_secs_f64();
|
||||
let render_val = frame.duration_render.as_secs_f64();
|
||||
let screencopy_val = frame
|
||||
.duration_screencopy
|
||||
.as_ref()
|
||||
.map(|val| val.as_secs_f64())
|
||||
.unwrap_or(0.0);
|
||||
let displayed_val = frame.duration_displayed.as_secs_f64();
|
||||
|
||||
fps.state.run(
|
||||
let transformed_elements =
|
||||
((elements_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
|
||||
let transformed_render =
|
||||
((render_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
|
||||
let transformed_screencopy =
|
||||
((screencopy_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
|
||||
let transformed_displayed =
|
||||
((displayed_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8;
|
||||
(
|
||||
(
|
||||
Bar::new(i as f64, transformed_elements as f64).fill(ELEMENTS_COLOR),
|
||||
Bar::new(i as f64, transformed_render as f64).fill(RENDER_COLOR),
|
||||
),
|
||||
(
|
||||
Bar::new(i as f64, transformed_screencopy as f64).fill(SCREENCOPY_COLOR),
|
||||
Bar::new(i as f64, transformed_displayed as f64).fill(DISPLAY_COLOR),
|
||||
),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let vendors = HashMap::from([
|
||||
(
|
||||
"0x10de",
|
||||
fps.state
|
||||
.with_image(renderer, "nvidia", |image, ctx| {
|
||||
(image.texture_id(ctx), image.size_vec2())
|
||||
})
|
||||
.expect("Logo images not loaded?"),
|
||||
),
|
||||
(
|
||||
"0x1002",
|
||||
fps.state
|
||||
.with_image(renderer, "amd", |image, ctx| {
|
||||
(image.texture_id(ctx), image.size_vec2())
|
||||
})
|
||||
.expect("Logo images not loaded?"),
|
||||
),
|
||||
(
|
||||
"0x8086",
|
||||
fps.state
|
||||
.with_image(renderer, "intel", |image, ctx| {
|
||||
(image.texture_id(ctx), image.size_vec2())
|
||||
})
|
||||
.expect("Logo images not loaded?"),
|
||||
),
|
||||
]);
|
||||
|
||||
fps.state.render(
|
||||
|ctx| {
|
||||
egui::Area::new("main")
|
||||
.anchor(egui::Align2::LEFT_TOP, (10.0, 10.0))
|
||||
.show(ctx, |ui| {
|
||||
let label_res = ui.label(format!(
|
||||
ui.label(format!(
|
||||
"cosmic-comp version {}",
|
||||
std::env!("CARGO_PKG_VERSION")
|
||||
));
|
||||
if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..8)) {
|
||||
ui.label(hash);
|
||||
if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) {
|
||||
ui.label(format!(": {hash}"));
|
||||
}
|
||||
|
||||
if !state.egui.active {
|
||||
ui.label("Press Mod+Escape for debug menu");
|
||||
ui.label("Press Super+Escape for debug overlay");
|
||||
} else {
|
||||
ui.set_max_width(label_res.rect.min.x + label_res.rect.width());
|
||||
ui.set_max_width(300.0);
|
||||
ui.separator();
|
||||
|
||||
if let Some(gpu) = gpu {
|
||||
ui.label(egui::RichText::new(format!("renderD{}", gpu.minor())).code());
|
||||
ui.horizontal(|ui| {
|
||||
let resp = ui.label(
|
||||
egui::RichText::new(format!("renderD{}", gpu.minor())).code(),
|
||||
);
|
||||
if let Ok(vendor) = std::fs::read_to_string(format!(
|
||||
"/sys/class/drm/renderD{}/device/vendor",
|
||||
gpu.minor()
|
||||
)) {
|
||||
if let Some((texture_id, mut size)) = vendors.get(vendor.trim())
|
||||
{
|
||||
let factor = resp.rect.height() / size.y;
|
||||
size = Vec2::from([size.x * factor, resp.rect.height()]);
|
||||
ui.image(*texture_id, size);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.label(egui::RichText::new(format!("FPS: {:>7.3}", avg_fps)).heading());
|
||||
ui.label("Frame Times:");
|
||||
ui.label(egui::RichText::new(format!("avg: {:>7.6}", avg)).code());
|
||||
ui.label(egui::RichText::new(format!("min: {:>7.6}", min)).code());
|
||||
ui.label(egui::RichText::new(format!("max: {:>7.6}", max)).code());
|
||||
let fps_chart = BarChart::new(bars).vertical();
|
||||
let elements_chart = BarChart::new(bars_elements).vertical();
|
||||
let render_chart = BarChart::new(bars_render)
|
||||
.stack_on(&[&elements_chart])
|
||||
.vertical();
|
||||
let screencopy_chart = BarChart::new(bars_screencopy)
|
||||
.stack_on(&[&elements_chart, &render_chart])
|
||||
.vertical();
|
||||
let display_chart = BarChart::new(bars_displayed)
|
||||
.stack_on(&[&elements_chart, &render_chart, &screencopy_chart])
|
||||
.vertical();
|
||||
|
||||
Plot::new("FPS")
|
||||
.legend(Legend::default())
|
||||
.view_aspect(33.0)
|
||||
.view_aspect(50.0)
|
||||
.include_x(0.0)
|
||||
.include_x(30.0)
|
||||
.include_x(amount as f64)
|
||||
.include_y(0.0)
|
||||
.include_y(300.0)
|
||||
.include_y(300)
|
||||
.show_x(false)
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.bar_chart(fps_chart);
|
||||
plot_ui.hline(
|
||||
HLine::new(avg)
|
||||
.highlight(true)
|
||||
.color(egui::Color32::LIGHT_BLUE),
|
||||
);
|
||||
plot_ui.bar_chart(elements_chart);
|
||||
plot_ui.bar_chart(render_chart);
|
||||
plot_ui.bar_chart(screencopy_chart);
|
||||
plot_ui.bar_chart(display_chart);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
renderer,
|
||||
area,
|
||||
scale,
|
||||
1.0,
|
||||
&state.start_time,
|
||||
fps.modifiers.clone(),
|
||||
0.8,
|
||||
state.clock.now().into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn debug_ui(
|
||||
state: &mut Common,
|
||||
area: Rectangle<f64, Physical>,
|
||||
scale: f64,
|
||||
) -> Option<EguiFrame> {
|
||||
if !state.egui.active {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(state.egui.debug_state.run(
|
||||
|ctx| {
|
||||
use crate::utils::prelude::*;
|
||||
|
||||
egui::Window::new("Workspaces")
|
||||
.default_pos([0.0, 300.0])
|
||||
.vscroll(true)
|
||||
.collapsible(true)
|
||||
.show(ctx, |ui| {
|
||||
use crate::{
|
||||
config::WorkspaceMode as ConfigMode,
|
||||
shell::{OutputBoundState, WorkspaceMode, MAX_WORKSPACES},
|
||||
};
|
||||
|
||||
ui.set_min_width(250.0);
|
||||
|
||||
// Mode
|
||||
|
||||
ui.label(egui::RichText::new("Mode").heading());
|
||||
let mut mode = match &state.shell.workspace_mode {
|
||||
WorkspaceMode::Global { .. } => ConfigMode::Global,
|
||||
WorkspaceMode::OutputBound => ConfigMode::OutputBound,
|
||||
};
|
||||
ui.radio_value(&mut mode, ConfigMode::OutputBound, "Output bound");
|
||||
ui.radio_value(&mut mode, ConfigMode::Global, "Global");
|
||||
state.shell.set_mode(mode);
|
||||
|
||||
let mode = match &state.shell.workspace_mode {
|
||||
WorkspaceMode::OutputBound => (ConfigMode::OutputBound, None),
|
||||
WorkspaceMode::Global { ref active, .. } => {
|
||||
(ConfigMode::Global, Some(*active))
|
||||
}
|
||||
};
|
||||
match mode {
|
||||
(ConfigMode::OutputBound, _) => {
|
||||
ui.label("Workspaces:");
|
||||
for output in state.shell.outputs().cloned().collect::<Vec<_>>() {
|
||||
ui.horizontal(|ui| {
|
||||
let active = output
|
||||
.user_data()
|
||||
.get::<OutputBoundState>()
|
||||
.unwrap()
|
||||
.active
|
||||
.get();
|
||||
let mut active_val = active as f64;
|
||||
ui.label(output.name());
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut active_val)
|
||||
.clamp_range(0..=(MAX_WORKSPACES - 1))
|
||||
.speed(1.0),
|
||||
);
|
||||
if active != active_val as usize {
|
||||
state.shell.activate(
|
||||
&state.seats[0],
|
||||
&output,
|
||||
active_val as usize,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
(ConfigMode::Global, Some(active)) => {
|
||||
ui.horizontal(|ui| {
|
||||
let mut active_val = active as f64;
|
||||
ui.label("Workspace:");
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut active_val)
|
||||
.clamp_range(0..=(MAX_WORKSPACES - 1))
|
||||
.speed(1.0),
|
||||
);
|
||||
if active != active_val as usize {
|
||||
let output = state.shell.outputs().next().cloned().unwrap();
|
||||
state.shell.activate(
|
||||
&state.seats[0],
|
||||
&output,
|
||||
active_val as usize,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Spaces
|
||||
for (i, workspace) in state.shell.spaces.iter().enumerate() {
|
||||
ui.collapsing(format!("Space: {}", i), |ui| {
|
||||
ui.collapsing(format!("Windows"), |ui| {
|
||||
for window in workspace.space.windows() {
|
||||
ui.collapsing(format!("{:?}", window.toplevel()), |ui| {
|
||||
ui.label(format!("Rect: {:?}", {
|
||||
let mut geo = window.geometry();
|
||||
geo.loc += workspace
|
||||
.space
|
||||
.window_location(window)
|
||||
.unwrap_or((0, 0).into());
|
||||
geo
|
||||
}));
|
||||
ui.label(format!(
|
||||
"Bounding box: {:?}",
|
||||
workspace.space.window_bbox(window)
|
||||
));
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
egui::Window::new("Outputs")
|
||||
.collapsible(true)
|
||||
.hscroll(true)
|
||||
.default_pos([300.0, 300.0])
|
||||
.show(ctx, |ui| {
|
||||
ui.label(format!("Global Space: {:?}", state.shell.global_space()));
|
||||
for output in state
|
||||
.shell
|
||||
.outputs()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
{
|
||||
ui.separator();
|
||||
ui.collapsing(output.name(), |ui| {
|
||||
ui.label(format!("Mode: {:#?}", output.current_mode()));
|
||||
ui.label(format!("Scale: {:#?}", output.current_scale()));
|
||||
ui.label(format!("Transform: {:#?}", output.current_transform()));
|
||||
ui.label(format!("Geometry: {:?}", output.geometry()));
|
||||
ui.label(format!(
|
||||
"Local Geometry: {:?}",
|
||||
state
|
||||
.shell
|
||||
.active_space(&output)
|
||||
.space
|
||||
.output_geometry(&output)
|
||||
));
|
||||
ui.label(format!(
|
||||
"Relative Geometry: {:?}",
|
||||
state
|
||||
.shell
|
||||
.space_relative_output_geometry((0i32, 0i32), &output)
|
||||
));
|
||||
ui.separator();
|
||||
ui.collapsing("Layers:", |ui| {
|
||||
let map = layer_map_for_output(&output);
|
||||
for layer in map.layers() {
|
||||
ui.collapsing(
|
||||
format!(
|
||||
"{}/{:?}",
|
||||
layer.wl_surface().id(),
|
||||
layer.wl_surface().client_id()
|
||||
),
|
||||
|ui| {
|
||||
ui.label(format!(
|
||||
"Alive: {:?} {:?} {:?}",
|
||||
layer.alive(),
|
||||
layer.layer_surface().alive(),
|
||||
layer.wl_surface().alive()
|
||||
));
|
||||
ui.label(format!("Layer: {:?}", layer.layer()));
|
||||
ui.label(format!("Namespace: {:?}", layer.namespace()));
|
||||
ui.label(format!("Geometry: {:?}", layer.bbox()));
|
||||
ui.label(format!(
|
||||
"Anchor: {:?}",
|
||||
layer.cached_state().anchor
|
||||
));
|
||||
ui.label(format!(
|
||||
"Margin: {:?}",
|
||||
layer.cached_state().margin
|
||||
));
|
||||
ui.label(format!(
|
||||
"Exclusive: {:?}",
|
||||
layer.cached_state().exclusive_zone
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
ui.label(format!("{:?}", map));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
area,
|
||||
scale,
|
||||
state.egui.alpha,
|
||||
&state.start_time,
|
||||
state.egui.modifiers.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn log_ui(
|
||||
state: &mut Common,
|
||||
area: Rectangle<f64, Physical>,
|
||||
scale: f64,
|
||||
default_width: f32,
|
||||
) -> Option<EguiFrame> {
|
||||
if !state.egui.active {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(state.egui.log_state.run(
|
||||
|ctx| {
|
||||
egui::SidePanel::right("Log")
|
||||
.frame(egui::Frame {
|
||||
inner_margin: egui::Vec2::new(10.0, 10.0).into(),
|
||||
outer_margin: egui::Vec2::new(0.0, 0.0).into(),
|
||||
rounding: 5.0.into(),
|
||||
shadow: egui::epaint::Shadow {
|
||||
extrusion: 0.0,
|
||||
color: egui::Color32::TRANSPARENT,
|
||||
},
|
||||
fill: egui::Color32::from_black_alpha(100),
|
||||
stroke: egui::Stroke::none(),
|
||||
})
|
||||
.default_width(default_width)
|
||||
.show(ctx, |ui| {
|
||||
egui::ScrollArea::vertical()
|
||||
.always_show_scroll(true)
|
||||
.stick_to_bottom()
|
||||
.show(ui, |ui| {
|
||||
for (_i, record) in state
|
||||
.log
|
||||
.debug_buffer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
{
|
||||
let mut message = egui::text::LayoutJob::single_section(
|
||||
record.level.as_short_str().to_string(),
|
||||
egui::TextFormat::simple(
|
||||
egui::FontId::monospace(16.0),
|
||||
match record.level {
|
||||
slog::Level::Critical => egui::Color32::RED,
|
||||
slog::Level::Error => egui::Color32::LIGHT_RED,
|
||||
slog::Level::Warning => egui::Color32::LIGHT_YELLOW,
|
||||
slog::Level::Info => egui::Color32::LIGHT_BLUE,
|
||||
slog::Level::Debug => egui::Color32::LIGHT_GREEN,
|
||||
slog::Level::Trace => egui::Color32::GRAY,
|
||||
},
|
||||
),
|
||||
);
|
||||
message.append(
|
||||
&record.message,
|
||||
6.0,
|
||||
egui::TextFormat::simple(
|
||||
egui::FontId::default(),
|
||||
egui::Color32::WHITE,
|
||||
),
|
||||
);
|
||||
ui.vertical(|ui| {
|
||||
ui.add(egui::Label::new(message));
|
||||
ui.add_space(4.0);
|
||||
for (k, v) in &record.kv {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(
|
||||
egui::Label::new(egui::RichText::new(k).code())
|
||||
.sense(egui::Sense::click()),
|
||||
)
|
||||
.on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||
render_value(ui, v);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
area,
|
||||
scale,
|
||||
state.egui.alpha,
|
||||
&state.start_time,
|
||||
state.egui.modifiers.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn render_value(ui: &mut egui::Ui, value: &serde_json::Value) {
|
||||
use serde_json::Value::*;
|
||||
|
||||
match value {
|
||||
Null => {
|
||||
ui.label(egui::RichText::new("null").code());
|
||||
}
|
||||
Bool(val) => {
|
||||
ui.label(egui::RichText::new(format!("{}", val)).code());
|
||||
}
|
||||
Number(val) => {
|
||||
ui.label(egui::RichText::new(format!("{}", val)).code());
|
||||
}
|
||||
String(val) => {
|
||||
ui.label(val);
|
||||
}
|
||||
Array(list) => {
|
||||
ui.vertical(|ui| {
|
||||
ui.label("[");
|
||||
for val in list {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(4.0);
|
||||
render_value(ui, val);
|
||||
});
|
||||
}
|
||||
ui.label("]");
|
||||
});
|
||||
}
|
||||
Object(map) => {
|
||||
ui.vertical(|ui| {
|
||||
for (k, val) in map {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(4.0);
|
||||
ui.add(egui::Label::new(egui::RichText::new(k).code()));
|
||||
render_value(ui, val);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
930
src/input/mod.rs
930
src/input/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,104 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use slog::Drain;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
mod serializer;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
const MAX_RECORDS: usize = 1000;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub type LogBuffer = Arc<Mutex<VecDeque<OwnedRecord>>>;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[derive(Clone)]
|
||||
struct DebugDrain {
|
||||
buffer: LogBuffer,
|
||||
dirty_flag: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
pub struct LogState {
|
||||
_guard: slog_scope::GlobalLoggerGuard,
|
||||
#[cfg(feature = "debug")]
|
||||
pub dirty_flag: Arc<AtomicBool>,
|
||||
#[cfg(feature = "debug")]
|
||||
pub debug_buffer: LogBuffer,
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub struct OwnedRecord {
|
||||
pub message: String,
|
||||
pub level: slog::Level,
|
||||
pub kv: serde_json::map::Map<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl DebugDrain {
|
||||
fn new() -> (DebugDrain, LogBuffer, Arc<AtomicBool>) {
|
||||
let dirty_flag = Arc::new(AtomicBool::new(false));
|
||||
let buffer = Arc::new(Mutex::new(VecDeque::new()));
|
||||
(
|
||||
DebugDrain {
|
||||
buffer: buffer.clone(),
|
||||
dirty_flag: dirty_flag.clone(),
|
||||
},
|
||||
buffer,
|
||||
dirty_flag,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl Drain for DebugDrain {
|
||||
type Ok = ();
|
||||
type Err = slog::Error;
|
||||
|
||||
fn log(
|
||||
&self,
|
||||
record: &slog::Record<'_>,
|
||||
values: &slog::OwnedKVList,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
use serde_json::value::{Serializer as ValueSerializer, Value};
|
||||
use serializer::SerdeSerializer;
|
||||
use slog::KV;
|
||||
|
||||
let mut serializer = SerdeSerializer::start(ValueSerializer, None)?;
|
||||
values.serialize(record, &mut serializer)?;
|
||||
record.kv().serialize(record, &mut serializer)?;
|
||||
let value = match serializer.end().map_err(|_| slog::Error::Other)? {
|
||||
Value::Object(map) => map,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut buffer = self.buffer.lock().unwrap();
|
||||
buffer.push_front(OwnedRecord {
|
||||
message: format!("{}", record.msg()),
|
||||
level: record.level(),
|
||||
kv: value,
|
||||
});
|
||||
buffer.truncate(MAX_RECORDS);
|
||||
self.dirty_flag.store(true, Ordering::SeqCst);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_logger() -> Result<LogState> {
|
||||
let decorator = slog_term::TermDecorator::new().stderr().build();
|
||||
// usually we would not want to use a Mutex here, but this is usefull for a prototype,
|
||||
// to make sure we do not miss any in-flight messages, when we crash.
|
||||
#[cfg(not(feature = "debug"))]
|
||||
let logger = slog::Logger::root(
|
||||
std::sync::Mutex::new(
|
||||
slog_term::CompactFormat::new(decorator)
|
||||
|
|
@ -108,21 +20,6 @@ pub fn init_logger() -> Result<LogState> {
|
|||
.fuse(),
|
||||
slog::o!(),
|
||||
);
|
||||
#[cfg(feature = "debug")]
|
||||
let (debug_drain, debug_buffer, dirty_flag) = DebugDrain::new();
|
||||
#[cfg(feature = "debug")]
|
||||
let logger = slog::Logger::root(
|
||||
slog::Duplicate::new(
|
||||
std::sync::Mutex::new(
|
||||
slog_term::CompactFormat::new(decorator)
|
||||
.build()
|
||||
.ignore_res(),
|
||||
),
|
||||
debug_drain,
|
||||
)
|
||||
.fuse(),
|
||||
slog::o!(),
|
||||
);
|
||||
|
||||
let _guard = slog_scope::set_global_logger(logger);
|
||||
slog_stdlog::init().unwrap();
|
||||
|
|
@ -135,11 +32,5 @@ pub fn init_logger() -> Result<LogState> {
|
|||
);
|
||||
}
|
||||
|
||||
Ok(LogState {
|
||||
_guard,
|
||||
#[cfg(feature = "debug")]
|
||||
debug_buffer,
|
||||
#[cfg(feature = "debug")]
|
||||
dirty_flag,
|
||||
})
|
||||
Ok(LogState { _guard })
|
||||
}
|
||||
|
|
|
|||
17
src/main.rs
17
src/main.rs
|
|
@ -9,11 +9,7 @@ use smithay::{
|
|||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
os::unix::prelude::AsRawFd,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
};
|
||||
use std::{ffi::OsString, os::unix::prelude::AsRawFd, sync::Arc};
|
||||
|
||||
pub mod backend;
|
||||
pub mod config;
|
||||
|
|
@ -68,18 +64,9 @@ fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
// trigger routines
|
||||
data.state.common.shell.refresh(&data.display.handle());
|
||||
data.state.common.shell.refresh();
|
||||
state::Common::refresh_focus(&mut data.state);
|
||||
|
||||
// do we need to trigger another render
|
||||
if data.state.common.dirty_flag.swap(false, Ordering::SeqCst) {
|
||||
for output in data.state.common.shell.outputs() {
|
||||
data.state
|
||||
.backend
|
||||
.schedule_render(&data.state.common.event_loop_handle, output)
|
||||
}
|
||||
}
|
||||
|
||||
// send out events
|
||||
let _ = data.display.flush_clients();
|
||||
})?;
|
||||
|
|
|
|||
1101
src/shell/element/mod.rs
Normal file
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
400
src/shell/element/stack.rs
Normal 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
262
src/shell/element/window.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,18 @@
|
|||
use crate::{
|
||||
shell::{OutputBoundState, Shell, Workspace, WorkspaceMode},
|
||||
shell::{element::CosmicMapped, Shell, Workspace},
|
||||
state::Common,
|
||||
utils::prelude::*,
|
||||
wayland::handlers::xdg_shell::PopupGrabData,
|
||||
};
|
||||
use indexmap::IndexSet;
|
||||
use smithay::{
|
||||
desktop::{layer_map_for_output, PopupUngrabStrategy, Window, WindowSurfaceType},
|
||||
desktop::{layer_map_for_output, PopupUngrabStrategy},
|
||||
input::Seat,
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
utils::{IsAlive, Serial, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
compositor::get_role,
|
||||
shell::{wlr_layer::LAYER_SURFACE_ROLE, xdg::XDG_TOPLEVEL_ROLE},
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use self::target::{KeyboardFocusTarget, WindowGroup};
|
||||
|
||||
pub mod target;
|
||||
|
||||
|
|
@ -32,80 +26,59 @@ pub enum FocusDirection {
|
|||
Out,
|
||||
}
|
||||
|
||||
pub struct FocusStack<'a>(Ref<'a, IndexSet<Window>>);
|
||||
pub struct FocusStackMut<'a>(RefMut<'a, IndexSet<Window>>);
|
||||
pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet<CosmicMapped>>);
|
||||
pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet<CosmicMapped>);
|
||||
|
||||
impl<'a> FocusStack<'a> {
|
||||
pub fn last(&self) -> Option<Window> {
|
||||
self.0.iter().rev().find(|w| w.toplevel().alive()).cloned()
|
||||
pub fn last(&self) -> Option<&CosmicMapped> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.and_then(|set| set.iter().rev().find(|w| w.alive()))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ Window> {
|
||||
self.0.iter().rev().filter(|w| w.toplevel().alive())
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
|
||||
self.0
|
||||
.iter()
|
||||
.flat_map(|set| set.iter().rev().filter(|w| w.alive()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FocusStackMut<'a> {
|
||||
pub fn append(&mut self, window: &Window) {
|
||||
self.0.retain(|w| w.toplevel().alive());
|
||||
pub fn append(&mut self, window: &CosmicMapped) {
|
||||
self.0.retain(|w| w.alive());
|
||||
self.0.shift_remove(window);
|
||||
self.0.insert(window.clone());
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<Window> {
|
||||
self.0.iter().rev().find(|w| w.toplevel().alive()).cloned()
|
||||
pub fn last(&self) -> Option<&CosmicMapped> {
|
||||
self.0.iter().rev().find(|w| w.alive())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ Window> {
|
||||
self.0.iter().rev().filter(|w| w.toplevel().alive())
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
|
||||
self.0.iter().rev().filter(|w| w.alive())
|
||||
}
|
||||
}
|
||||
|
||||
type FocusStackData = RefCell<(HashMap<u8, IndexSet<Window>>, IndexSet<Window>)>;
|
||||
impl Workspace {}
|
||||
|
||||
impl Workspace {
|
||||
pub fn focus_stack<'a, 'b>(&'b self, seat: &'a Seat<State>) -> FocusStack<'a> {
|
||||
seat.user_data()
|
||||
.insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new())));
|
||||
let idx = self.idx;
|
||||
FocusStack(Ref::map(
|
||||
seat.user_data().get::<FocusStackData>().unwrap().borrow(),
|
||||
|map| map.0.get(&idx).unwrap_or(&map.1), //TODO: workaround until Ref::filter_map goes stable
|
||||
))
|
||||
}
|
||||
|
||||
pub fn focus_stack_mut<'a, 'b>(&'b self, seat: &'a Seat<State>) -> FocusStackMut<'a> {
|
||||
seat.user_data()
|
||||
.insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new())));
|
||||
let idx = self.idx;
|
||||
FocusStackMut(RefMut::map(
|
||||
seat.user_data()
|
||||
.get::<FocusStackData>()
|
||||
.unwrap()
|
||||
.borrow_mut(),
|
||||
|map| map.0.entry(idx).or_insert_with(|| IndexSet::new()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActiveFocus(RefCell<Option<WlSurface>>);
|
||||
pub struct ActiveFocus(RefCell<Option<KeyboardFocusTarget>>);
|
||||
|
||||
impl ActiveFocus {
|
||||
fn set(seat: &Seat<State>, surface: Option<WlSurface>) {
|
||||
fn set(seat: &Seat<State>, target: Option<KeyboardFocusTarget>) {
|
||||
if !seat
|
||||
.user_data()
|
||||
.insert_if_missing(|| ActiveFocus(RefCell::new(surface.clone())))
|
||||
.insert_if_missing(|| ActiveFocus(RefCell::new(target.clone())))
|
||||
{
|
||||
*seat
|
||||
.user_data()
|
||||
.get::<ActiveFocus>()
|
||||
.unwrap()
|
||||
.0
|
||||
.borrow_mut() = surface;
|
||||
.borrow_mut() = target;
|
||||
}
|
||||
}
|
||||
|
||||
fn get(seat: &Seat<State>) -> Option<WlSurface> {
|
||||
fn get(seat: &Seat<State>) -> Option<KeyboardFocusTarget> {
|
||||
seat.user_data()
|
||||
.get::<ActiveFocus>()
|
||||
.and_then(|a| a.0.borrow().clone())
|
||||
|
|
@ -115,30 +88,25 @@ impl ActiveFocus {
|
|||
impl Shell {
|
||||
pub fn set_focus<'a>(
|
||||
state: &mut State,
|
||||
surface: Option<&WlSurface>,
|
||||
target: Option<&KeyboardFocusTarget>,
|
||||
active_seat: &Seat<State>,
|
||||
serial: Option<Serial>,
|
||||
) {
|
||||
// update FocusStack and notify layouts about new focus (if any window)
|
||||
if let Some(surface) = surface {
|
||||
if let Some(workspace) = state.common.shell.space_for_window_mut(surface) {
|
||||
if let Some(window) = workspace
|
||||
.space
|
||||
.window_for_surface(surface, WindowSurfaceType::ALL)
|
||||
{
|
||||
let mut focus_stack = workspace.focus_stack_mut(active_seat);
|
||||
if Some(window) != focus_stack.last().as_ref() {
|
||||
slog_scope::debug!("Focusing window: {:?}", window);
|
||||
focus_stack.append(window);
|
||||
// also remove popup grabs, if we are switching focus
|
||||
if let Some(mut popup_grab) = active_seat
|
||||
.user_data()
|
||||
.get::<PopupGrabData>()
|
||||
.and_then(|x| x.take())
|
||||
{
|
||||
if !popup_grab.has_ended() {
|
||||
popup_grab.ungrab(PopupUngrabStrategy::All);
|
||||
}
|
||||
if let Some(KeyboardFocusTarget::Element(mapped)) = target {
|
||||
if let Some(workspace) = state.common.shell.space_for_mut(mapped) {
|
||||
let mut focus_stack = workspace.focus_stack.get_mut(active_seat);
|
||||
if Some(mapped) != focus_stack.last() {
|
||||
slog_scope::debug!("Focusing window: {:?}", mapped);
|
||||
focus_stack.append(mapped);
|
||||
// also remove popup grabs, if we are switching focus
|
||||
if let Some(mut popup_grab) = active_seat
|
||||
.user_data()
|
||||
.get::<PopupGrabData>()
|
||||
.and_then(|x| x.take())
|
||||
{
|
||||
if !popup_grab.has_ended() {
|
||||
popup_grab.ungrab(PopupUngrabStrategy::All);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -147,10 +115,10 @@ impl Shell {
|
|||
|
||||
// update keyboard focus
|
||||
if let Some(keyboard) = active_seat.get_keyboard() {
|
||||
ActiveFocus::set(active_seat, surface.cloned());
|
||||
ActiveFocus::set(active_seat, target.cloned());
|
||||
keyboard.set_focus(
|
||||
state,
|
||||
surface.cloned(),
|
||||
target.cloned(),
|
||||
serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()),
|
||||
);
|
||||
}
|
||||
|
|
@ -160,30 +128,23 @@ impl Shell {
|
|||
// update activate status
|
||||
let focused_windows = seats
|
||||
.flat_map(|seat| {
|
||||
self.outputs
|
||||
.iter()
|
||||
.flat_map(|o| self.active_space(o).focus_stack(seat).last().clone())
|
||||
self.outputs.iter().flat_map(|o| {
|
||||
let space = self.active_space(o);
|
||||
let stack = space.focus_stack.get(seat);
|
||||
stack.last().cloned()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for output in self.outputs.iter() {
|
||||
let workspace = match &self.workspace_mode {
|
||||
WorkspaceMode::OutputBound => {
|
||||
let active = output
|
||||
.user_data()
|
||||
.get::<OutputBoundState>()
|
||||
.unwrap()
|
||||
.active
|
||||
.get();
|
||||
&mut self.spaces[active]
|
||||
}
|
||||
WorkspaceMode::Global { active, .. } => &mut self.spaces[*active],
|
||||
};
|
||||
let workspace = self.workspaces.active_mut(output);
|
||||
for focused in focused_windows.iter() {
|
||||
workspace.space.raise_window(focused, true);
|
||||
if workspace.floating_layer.mapped().any(|m| m == focused) {
|
||||
workspace.floating_layer.space.raise_element(focused, true);
|
||||
}
|
||||
}
|
||||
for window in workspace.space.windows() {
|
||||
window.set_activated(focused_windows.contains(window));
|
||||
for window in workspace.mapped() {
|
||||
window.set_activated(focused_windows.contains(&window));
|
||||
window.configure();
|
||||
}
|
||||
}
|
||||
|
|
@ -193,55 +154,54 @@ impl Shell {
|
|||
impl Common {
|
||||
pub fn set_focus(
|
||||
state: &mut State,
|
||||
surface: Option<&WlSurface>,
|
||||
target: Option<&KeyboardFocusTarget>,
|
||||
active_seat: &Seat<State>,
|
||||
serial: Option<Serial>,
|
||||
) {
|
||||
Shell::set_focus(state, surface, active_seat, serial);
|
||||
state.common.shell.update_active(state.common.seats.iter());
|
||||
Shell::set_focus(state, target, active_seat, serial);
|
||||
let seats = state.common.seats().cloned().collect::<Vec<_>>();
|
||||
state.common.shell.update_active(seats.iter());
|
||||
}
|
||||
|
||||
pub fn refresh_focus(state: &mut State) {
|
||||
let seats = state.common.seats.clone();
|
||||
let seats = state.common.seats().cloned().collect::<Vec<_>>();
|
||||
for seat in seats {
|
||||
let output = active_output(&seat, &state.common);
|
||||
let output = seat.active_output();
|
||||
if !state.common.shell.outputs.contains(&output) {
|
||||
seat.set_active_output(&state.common.shell.outputs[0]);
|
||||
continue;
|
||||
}
|
||||
let last_known_focus = ActiveFocus::get(&seat);
|
||||
|
||||
if let Some(surface) = last_known_focus {
|
||||
if surface.alive() {
|
||||
let is_toplevel = matches!(get_role(&surface), Some(XDG_TOPLEVEL_ROLE));
|
||||
let is_layer = matches!(get_role(&surface), Some(LAYER_SURFACE_ROLE));
|
||||
|
||||
if let Some(popup) = state.common.shell.popups.find_popup(&surface) {
|
||||
if popup.alive() {
|
||||
continue;
|
||||
}
|
||||
} else if is_layer {
|
||||
if layer_map_for_output(&output)
|
||||
.layer_for_surface(&surface, WindowSurfaceType::ALL)
|
||||
.is_some()
|
||||
{
|
||||
continue; // Focus is valid
|
||||
}
|
||||
} else if is_toplevel {
|
||||
let workspace = state.common.shell.active_space(&output);
|
||||
if let Some(window) = workspace
|
||||
.space
|
||||
.window_for_surface(&surface, WindowSurfaceType::ALL)
|
||||
{
|
||||
let focus_stack = workspace.focus_stack(&seat);
|
||||
if !focus_stack.last().map(|w| &w != window).unwrap_or(true) {
|
||||
if let Some(target) = last_known_focus {
|
||||
if target.alive() {
|
||||
match target {
|
||||
KeyboardFocusTarget::Element(mapped) => {
|
||||
let workspace = state.common.shell.active_space(&output);
|
||||
let focus_stack = workspace.focus_stack.get(&seat);
|
||||
if focus_stack.last().map(|m| m == &mapped).unwrap_or(false) {
|
||||
continue; // Focus is valid
|
||||
} else {
|
||||
slog_scope::debug!("Wrong Window, focus fixup");
|
||||
}
|
||||
} else {
|
||||
slog_scope::debug!("Different workspaces Window, focus fixup");
|
||||
}
|
||||
} else {
|
||||
// unknown surface type, fixup
|
||||
slog_scope::debug!("Surface unmapped, focus fixup");
|
||||
}
|
||||
KeyboardFocusTarget::LayerSurface(layer) => {
|
||||
if layer_map_for_output(&output).layers().any(|l| l == &layer) {
|
||||
continue; // Focus is valid
|
||||
}
|
||||
}
|
||||
KeyboardFocusTarget::Group(WindowGroup {
|
||||
output: weak_output,
|
||||
..
|
||||
}) => {
|
||||
if weak_output == output {
|
||||
continue; // Focus is valid,
|
||||
}
|
||||
}
|
||||
KeyboardFocusTarget::Popup(_) | KeyboardFocusTarget::Fullscreen(_) => {
|
||||
continue;
|
||||
} // Focus is valid
|
||||
};
|
||||
} else {
|
||||
slog_scope::debug!("Surface dead, focus fixup");
|
||||
}
|
||||
|
|
@ -263,21 +223,24 @@ impl Common {
|
|||
}
|
||||
|
||||
// update keyboard focus
|
||||
let surface = state
|
||||
let target = state
|
||||
.common
|
||||
.shell
|
||||
.active_space(&output)
|
||||
.focus_stack(&seat)
|
||||
.focus_stack
|
||||
.get(&seat)
|
||||
.last()
|
||||
.map(|w| w.toplevel().wl_surface().clone());
|
||||
.cloned()
|
||||
.map(KeyboardFocusTarget::from);
|
||||
if let Some(keyboard) = seat.get_keyboard() {
|
||||
slog_scope::info!("restoring focus to: {:?}", surface.as_ref());
|
||||
keyboard.set_focus(state, surface.clone(), SERIAL_COUNTER.next_serial());
|
||||
ActiveFocus::set(&seat, surface);
|
||||
slog_scope::info!("restoring focus to: {:?}", target.as_ref());
|
||||
keyboard.set_focus(state, target.clone(), SERIAL_COUNTER.next_serial());
|
||||
ActiveFocus::set(&seat, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.common.shell.update_active(state.common.seats.iter())
|
||||
let seats = state.common.seats().cloned().collect::<Vec<_>>();
|
||||
state.common.shell.update_active(seats.iter())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use crate::utils::prelude::*;
|
||||
pub use smithay::{
|
||||
use std::sync::Weak;
|
||||
|
||||
use crate::{shell::element::CosmicMapped, utils::prelude::*};
|
||||
use id_tree::NodeId;
|
||||
use smithay::{
|
||||
backend::input::KeyState,
|
||||
desktop::{LayerSurface, PopupKind, Window},
|
||||
input::{
|
||||
|
|
@ -7,83 +10,131 @@ pub use smithay::{
|
|||
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget},
|
||||
Seat,
|
||||
},
|
||||
output::WeakOutput,
|
||||
reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource},
|
||||
utils::{IsAlive, Serial},
|
||||
wayland::seat::WaylandFocus,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FocusTarget {
|
||||
Window(Window),
|
||||
pub enum PointerFocusTarget {
|
||||
Element(CosmicMapped),
|
||||
Fullscreen(Window),
|
||||
LayerSurface(LayerSurface),
|
||||
Popup(PopupKind),
|
||||
}
|
||||
|
||||
impl IsAlive for FocusTarget {
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
FocusTarget::Window(w) => w.alive(),
|
||||
FocusTarget::LayerSurface(l) => l.alive(),
|
||||
FocusTarget::Popup(p) => p.alive(),
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum KeyboardFocusTarget {
|
||||
Element(CosmicMapped),
|
||||
Fullscreen(Window),
|
||||
Group(WindowGroup),
|
||||
LayerSurface(LayerSurface),
|
||||
Popup(PopupKind),
|
||||
}
|
||||
|
||||
impl From<KeyboardFocusTarget> for PointerFocusTarget {
|
||||
fn from(target: KeyboardFocusTarget) -> Self {
|
||||
match target {
|
||||
KeyboardFocusTarget::Element(elem) => PointerFocusTarget::Element(elem),
|
||||
KeyboardFocusTarget::Fullscreen(elem) => PointerFocusTarget::Fullscreen(elem),
|
||||
KeyboardFocusTarget::LayerSurface(layer) => PointerFocusTarget::LayerSurface(layer),
|
||||
KeyboardFocusTarget::Popup(popup) => PointerFocusTarget::Popup(popup),
|
||||
_ => unreachable!("A window grab cannot start a popup grab"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerTarget<State> for FocusTarget {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowGroup {
|
||||
pub(in crate::shell) node: NodeId,
|
||||
pub(in crate::shell) output: WeakOutput,
|
||||
pub(in crate::shell) alive: Weak<()>,
|
||||
}
|
||||
|
||||
impl PartialEq for WindowGroup {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node == other.node
|
||||
&& self.output == other.output
|
||||
&& Weak::ptr_eq(&self.alive, &other.alive)
|
||||
}
|
||||
}
|
||||
|
||||
impl IsAlive for PointerFocusTarget {
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
PointerFocusTarget::Element(e) => e.alive(),
|
||||
PointerFocusTarget::Fullscreen(f) => f.alive(),
|
||||
PointerFocusTarget::LayerSurface(l) => l.alive(),
|
||||
PointerFocusTarget::Popup(p) => p.alive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IsAlive for KeyboardFocusTarget {
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
KeyboardFocusTarget::Element(e) => e.alive(),
|
||||
KeyboardFocusTarget::Fullscreen(f) => f.alive(),
|
||||
KeyboardFocusTarget::Group(g) => g.alive.upgrade().is_some(),
|
||||
KeyboardFocusTarget::LayerSurface(l) => l.alive(),
|
||||
KeyboardFocusTarget::Popup(p) => p.alive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerTarget<State> for PointerFocusTarget {
|
||||
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
PointerTarget::enter(w.toplevel().wl_surface(), seat, data, event)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => PointerTarget::enter(l.wl_surface(), seat, data, event),
|
||||
FocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event),
|
||||
PointerFocusTarget::Element(w) => PointerTarget::enter(w, seat, data, event),
|
||||
PointerFocusTarget::Fullscreen(w) => PointerTarget::enter(w, seat, data, event),
|
||||
PointerFocusTarget::LayerSurface(l) => PointerTarget::enter(l, seat, data, event),
|
||||
PointerFocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event),
|
||||
}
|
||||
}
|
||||
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
PointerTarget::motion(w.toplevel().wl_surface(), seat, data, event)
|
||||
PointerFocusTarget::Element(w) => PointerTarget::motion(w, seat, data, event),
|
||||
PointerFocusTarget::Fullscreen(w) => PointerTarget::motion(w, seat, data, event),
|
||||
PointerFocusTarget::LayerSurface(l) => PointerTarget::motion(l, seat, data, event),
|
||||
PointerFocusTarget::Popup(p) => {
|
||||
PointerTarget::motion(p.wl_surface(), seat, data, event)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => {
|
||||
PointerTarget::motion(l.wl_surface(), seat, data, event)
|
||||
}
|
||||
FocusTarget::Popup(p) => PointerTarget::motion(p.wl_surface(), seat, data, event),
|
||||
}
|
||||
}
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
PointerTarget::button(w.toplevel().wl_surface(), seat, data, event)
|
||||
PointerFocusTarget::Element(w) => PointerTarget::button(w, seat, data, event),
|
||||
PointerFocusTarget::Fullscreen(w) => PointerTarget::button(w, seat, data, event),
|
||||
PointerFocusTarget::LayerSurface(l) => PointerTarget::button(l, seat, data, event),
|
||||
PointerFocusTarget::Popup(p) => {
|
||||
PointerTarget::button(p.wl_surface(), seat, data, event)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => {
|
||||
PointerTarget::button(l.wl_surface(), seat, data, event)
|
||||
}
|
||||
FocusTarget::Popup(p) => PointerTarget::button(p.wl_surface(), seat, data, event),
|
||||
}
|
||||
}
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
PointerTarget::axis(w.toplevel().wl_surface(), seat, data, frame)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => PointerTarget::axis(l.wl_surface(), seat, data, frame),
|
||||
FocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame),
|
||||
PointerFocusTarget::Element(w) => PointerTarget::axis(w, seat, data, frame),
|
||||
PointerFocusTarget::Fullscreen(w) => PointerTarget::axis(w, seat, data, frame),
|
||||
PointerFocusTarget::LayerSurface(l) => PointerTarget::axis(l, seat, data, frame),
|
||||
PointerFocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame),
|
||||
}
|
||||
}
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
PointerTarget::leave(w.toplevel().wl_surface(), seat, data, serial, time)
|
||||
PointerFocusTarget::Element(w) => PointerTarget::leave(w, seat, data, serial, time),
|
||||
PointerFocusTarget::Fullscreen(w) => PointerTarget::leave(w, seat, data, serial, time),
|
||||
PointerFocusTarget::LayerSurface(l) => {
|
||||
PointerTarget::leave(l, seat, data, serial, time)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => {
|
||||
PointerTarget::leave(l.wl_surface(), seat, data, serial, time)
|
||||
PointerFocusTarget::Popup(p) => {
|
||||
PointerTarget::leave(p.wl_surface(), seat, data, serial, time)
|
||||
}
|
||||
FocusTarget::Popup(p) => PointerTarget::leave(p.wl_surface(), seat, data, serial, time),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardTarget<State> for FocusTarget {
|
||||
impl KeyboardTarget<State> for KeyboardFocusTarget {
|
||||
fn enter(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
|
|
@ -92,26 +143,28 @@ impl KeyboardTarget<State> for FocusTarget {
|
|||
serial: Serial,
|
||||
) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
KeyboardTarget::enter(w.toplevel().wl_surface(), seat, data, keys, serial)
|
||||
KeyboardFocusTarget::Element(w) => KeyboardTarget::enter(w, seat, data, keys, serial),
|
||||
KeyboardFocusTarget::Fullscreen(w) => {
|
||||
KeyboardTarget::enter(w, seat, data, keys, serial)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => {
|
||||
KeyboardTarget::enter(l.wl_surface(), seat, data, keys, serial)
|
||||
KeyboardFocusTarget::Group(_) => {}
|
||||
KeyboardFocusTarget::LayerSurface(l) => {
|
||||
KeyboardTarget::enter(l, seat, data, keys, serial)
|
||||
}
|
||||
FocusTarget::Popup(p) => {
|
||||
KeyboardFocusTarget::Popup(p) => {
|
||||
KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
KeyboardTarget::leave(w.toplevel().wl_surface(), seat, data, serial)
|
||||
KeyboardFocusTarget::Element(w) => KeyboardTarget::leave(w, seat, data, serial),
|
||||
KeyboardFocusTarget::Fullscreen(w) => KeyboardTarget::leave(w, seat, data, serial),
|
||||
KeyboardFocusTarget::Group(_) => {}
|
||||
KeyboardFocusTarget::LayerSurface(l) => KeyboardTarget::leave(l, seat, data, serial),
|
||||
KeyboardFocusTarget::Popup(p) => {
|
||||
KeyboardTarget::leave(p.wl_surface(), seat, data, serial)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => {
|
||||
KeyboardTarget::leave(l.wl_surface(), seat, data, serial)
|
||||
}
|
||||
FocusTarget::Popup(p) => KeyboardTarget::leave(p.wl_surface(), seat, data, serial),
|
||||
}
|
||||
}
|
||||
fn key(
|
||||
|
|
@ -124,19 +177,17 @@ impl KeyboardTarget<State> for FocusTarget {
|
|||
time: u32,
|
||||
) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => KeyboardTarget::key(
|
||||
w.toplevel().wl_surface(),
|
||||
seat,
|
||||
data,
|
||||
key,
|
||||
state,
|
||||
serial,
|
||||
time,
|
||||
),
|
||||
FocusTarget::LayerSurface(l) => {
|
||||
KeyboardTarget::key(l.wl_surface(), seat, data, key, state, serial, time)
|
||||
KeyboardFocusTarget::Element(w) => {
|
||||
KeyboardTarget::key(w, seat, data, key, state, serial, time)
|
||||
}
|
||||
FocusTarget::Popup(p) => {
|
||||
KeyboardFocusTarget::Fullscreen(w) => {
|
||||
KeyboardTarget::key(w, seat, data, key, state, serial, time)
|
||||
}
|
||||
KeyboardFocusTarget::Group(_) => {}
|
||||
KeyboardFocusTarget::LayerSurface(l) => {
|
||||
KeyboardTarget::key(l, seat, data, key, state, serial, time)
|
||||
}
|
||||
KeyboardFocusTarget::Popup(p) => {
|
||||
KeyboardTarget::key(p.wl_surface(), seat, data, key, state, serial, time)
|
||||
}
|
||||
}
|
||||
|
|
@ -149,50 +200,113 @@ impl KeyboardTarget<State> for FocusTarget {
|
|||
serial: Serial,
|
||||
) {
|
||||
match self {
|
||||
FocusTarget::Window(w) => {
|
||||
KeyboardTarget::modifiers(w.toplevel().wl_surface(), seat, data, modifiers, serial)
|
||||
KeyboardFocusTarget::Element(w) => {
|
||||
KeyboardTarget::modifiers(w, seat, data, modifiers, serial)
|
||||
}
|
||||
FocusTarget::LayerSurface(l) => {
|
||||
KeyboardTarget::modifiers(l.wl_surface(), seat, data, modifiers, serial)
|
||||
KeyboardFocusTarget::Fullscreen(w) => {
|
||||
KeyboardTarget::modifiers(w, seat, data, modifiers, serial)
|
||||
}
|
||||
FocusTarget::Popup(p) => {
|
||||
KeyboardFocusTarget::Group(_) => {}
|
||||
KeyboardFocusTarget::LayerSurface(l) => {
|
||||
KeyboardTarget::modifiers(l, seat, data, modifiers, serial)
|
||||
}
|
||||
KeyboardFocusTarget::Popup(p) => {
|
||||
KeyboardTarget::modifiers(p.wl_surface(), seat, data, modifiers, serial)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for FocusTarget {
|
||||
fn wl_surface(&self) -> Option<&WlSurface> {
|
||||
Some(match self {
|
||||
FocusTarget::Window(w) => w.toplevel().wl_surface(),
|
||||
FocusTarget::LayerSurface(l) => l.wl_surface(),
|
||||
FocusTarget::Popup(p) => p.wl_surface(),
|
||||
})
|
||||
impl WaylandFocus for KeyboardFocusTarget {
|
||||
fn wl_surface(&self) -> Option<WlSurface> {
|
||||
match self {
|
||||
KeyboardFocusTarget::Element(w) => WaylandFocus::wl_surface(w),
|
||||
KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w),
|
||||
KeyboardFocusTarget::Group(_) => None,
|
||||
KeyboardFocusTarget::LayerSurface(l) => Some(l.wl_surface().clone()),
|
||||
KeyboardFocusTarget::Popup(p) => Some(p.wl_surface().clone()),
|
||||
}
|
||||
}
|
||||
fn same_client_as(&self, object_id: &ObjectId) -> bool {
|
||||
match self {
|
||||
FocusTarget::Window(w) => w.toplevel().wl_surface().id().same_client_as(object_id),
|
||||
FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
|
||||
FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
|
||||
KeyboardFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id),
|
||||
KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id),
|
||||
KeyboardFocusTarget::Group(_) => false,
|
||||
KeyboardFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
|
||||
KeyboardFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Window> for FocusTarget {
|
||||
impl WaylandFocus for PointerFocusTarget {
|
||||
fn wl_surface(&self) -> Option<WlSurface> {
|
||||
Some(match self {
|
||||
PointerFocusTarget::Element(w) => WaylandFocus::wl_surface(w)?,
|
||||
PointerFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w)?,
|
||||
PointerFocusTarget::LayerSurface(l) => l.wl_surface().clone(),
|
||||
PointerFocusTarget::Popup(p) => p.wl_surface().clone(),
|
||||
})
|
||||
}
|
||||
fn same_client_as(&self, object_id: &ObjectId) -> bool {
|
||||
match self {
|
||||
PointerFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id),
|
||||
PointerFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id),
|
||||
PointerFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
|
||||
PointerFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CosmicMapped> for PointerFocusTarget {
|
||||
fn from(w: CosmicMapped) -> Self {
|
||||
PointerFocusTarget::Element(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Window> for PointerFocusTarget {
|
||||
fn from(w: Window) -> Self {
|
||||
FocusTarget::Window(w)
|
||||
PointerFocusTarget::Fullscreen(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayerSurface> for FocusTarget {
|
||||
impl From<LayerSurface> for PointerFocusTarget {
|
||||
fn from(l: LayerSurface) -> Self {
|
||||
FocusTarget::LayerSurface(l)
|
||||
PointerFocusTarget::LayerSurface(l)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupKind> for FocusTarget {
|
||||
impl From<PopupKind> for PointerFocusTarget {
|
||||
fn from(p: PopupKind) -> Self {
|
||||
FocusTarget::Popup(p)
|
||||
PointerFocusTarget::Popup(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CosmicMapped> for KeyboardFocusTarget {
|
||||
fn from(w: CosmicMapped) -> Self {
|
||||
KeyboardFocusTarget::Element(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Window> for KeyboardFocusTarget {
|
||||
fn from(w: Window) -> Self {
|
||||
KeyboardFocusTarget::Fullscreen(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowGroup> for KeyboardFocusTarget {
|
||||
fn from(w: WindowGroup) -> Self {
|
||||
KeyboardFocusTarget::Group(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayerSurface> for KeyboardFocusTarget {
|
||||
fn from(l: LayerSurface) -> Self {
|
||||
KeyboardFocusTarget::LayerSurface(l)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupKind> for KeyboardFocusTarget {
|
||||
fn from(p: PopupKind) -> Self {
|
||||
KeyboardFocusTarget::Popup(p)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,367 +1,107 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::Shell;
|
||||
use crate::utils::prelude::*;
|
||||
|
||||
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState;
|
||||
use smithay::{
|
||||
backend::renderer::{ImportAll, Renderer},
|
||||
desktop::{
|
||||
draw_window,
|
||||
space::{RenderElement, SpaceOutputTuple},
|
||||
Kind, Window,
|
||||
input::pointer::{
|
||||
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
||||
PointerInnerHandle,
|
||||
},
|
||||
input::{
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent,
|
||||
PointerGrab, PointerInnerHandle,
|
||||
},
|
||||
Seat,
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
|
||||
wayland_server::protocol::wl_surface::WlSurface,
|
||||
},
|
||||
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial},
|
||||
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||
utils::{Logical, Point},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
impl Shell {
|
||||
pub fn move_request(
|
||||
state: &mut State,
|
||||
window: &Window,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
start_data: PointerGrabStartData<State>,
|
||||
) {
|
||||
// TODO touch grab
|
||||
if let Some(pointer) = seat.get_pointer() {
|
||||
let workspace = state
|
||||
.common
|
||||
.shell
|
||||
.space_for_window_mut(window.toplevel().wl_surface())
|
||||
.unwrap();
|
||||
if workspace.fullscreen.values().any(|w| w == window) {
|
||||
return;
|
||||
}
|
||||
use crate::state::State;
|
||||
|
||||
let pos = pointer.current_location();
|
||||
let output = match workspace
|
||||
.space
|
||||
.outputs_for_window(&window)
|
||||
.into_iter()
|
||||
.find(|o| o.geometry().contains(pos.to_i32_round())) {
|
||||
Some(o) => o,
|
||||
None => return,
|
||||
};
|
||||
let mut initial_window_location = workspace.space.window_location(&window).unwrap();
|
||||
use super::{
|
||||
focus::target::PointerFocusTarget,
|
||||
layout::{floating::ResizeSurfaceGrab, tiling::ResizeForkGrab},
|
||||
};
|
||||
|
||||
match &window.toplevel() {
|
||||
Kind::Xdg(surface) => {
|
||||
// If surface is maximized then unmaximize it
|
||||
let current_state = surface.current_state();
|
||||
if current_state.states.contains(XdgState::Maximized) {
|
||||
workspace
|
||||
.floating_layer
|
||||
.unmaximize_request(&mut workspace.space, window);
|
||||
let new_size = surface.with_pending_state(|state| state.size);
|
||||
let ratio = pos.x / output.geometry().size.w as f64;
|
||||
bitflags::bitflags! {
|
||||
pub struct ResizeEdge: u32 {
|
||||
const TOP = 0b0001;
|
||||
const BOTTOM = 0b0010;
|
||||
const LEFT = 0b0100;
|
||||
const RIGHT = 0b1000;
|
||||
|
||||
initial_window_location = new_size
|
||||
.map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into())
|
||||
.unwrap_or_else(|| pos)
|
||||
.to_i32_round();
|
||||
}
|
||||
}
|
||||
};
|
||||
const TOP_LEFT = Self::TOP.bits | Self::LEFT.bits;
|
||||
const BOTTOM_LEFT = Self::BOTTOM.bits | Self::LEFT.bits;
|
||||
|
||||
let was_tiled = if workspace.tiling_layer.windows.contains(&window) {
|
||||
workspace
|
||||
.tiling_layer
|
||||
.unmap_window(&mut workspace.space, &window);
|
||||
true
|
||||
} else {
|
||||
workspace
|
||||
.floating_layer
|
||||
.unmap_window(&mut workspace.space, &window);
|
||||
false
|
||||
};
|
||||
|
||||
let workspace_handle = workspace.handle;
|
||||
let workspace_is_empty = workspace.space.windows().next().is_none();
|
||||
|
||||
if workspace_is_empty {
|
||||
state
|
||||
.common
|
||||
.shell
|
||||
.workspace_state
|
||||
.update()
|
||||
.add_workspace_state(&workspace_handle, WState::Hidden);
|
||||
}
|
||||
state
|
||||
.common
|
||||
.shell
|
||||
.toplevel_info_state
|
||||
.toplevel_leave_workspace(&window, &workspace_handle);
|
||||
state
|
||||
.common
|
||||
.shell
|
||||
.toplevel_info_state
|
||||
.toplevel_leave_output(&window, &output);
|
||||
|
||||
let grab_state = MoveGrabState {
|
||||
window: window.clone(),
|
||||
was_tiled,
|
||||
initial_cursor_location: pointer.current_location(),
|
||||
initial_window_location,
|
||||
initial_output_location: output.geometry().loc,
|
||||
};
|
||||
let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat);
|
||||
|
||||
*seat
|
||||
.user_data()
|
||||
.get::<SeatMoveGrabState>()
|
||||
.unwrap()
|
||||
.borrow_mut() = Some(grab_state);
|
||||
pointer.set_grab(state, grab, serial, Focus::Clear);
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_move(state: &mut State, seat: &Seat<State>, output: &Output) {
|
||||
if let Some(move_state) = seat
|
||||
.user_data()
|
||||
.get::<SeatMoveGrabState>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.take()
|
||||
{
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
let window = move_state.window;
|
||||
|
||||
if window.alive() {
|
||||
let delta = pointer.current_location() - move_state.initial_cursor_location;
|
||||
let window_location = (move_state.initial_window_location.to_f64()
|
||||
+ move_state.initial_output_location.to_f64()
|
||||
- output.geometry().loc.to_f64()
|
||||
+ delta)
|
||||
.to_i32_round();
|
||||
let surface = window.toplevel().wl_surface().clone();
|
||||
|
||||
let workspace_handle = state.common.shell.active_space(output).handle;
|
||||
state
|
||||
.common
|
||||
.shell
|
||||
.workspace_state
|
||||
.update()
|
||||
.remove_workspace_state(&workspace_handle, WState::Hidden);
|
||||
state
|
||||
.common
|
||||
.shell
|
||||
.toplevel_info_state
|
||||
.toplevel_enter_workspace(&window, &workspace_handle);
|
||||
state
|
||||
.common
|
||||
.shell
|
||||
.toplevel_info_state
|
||||
.toplevel_enter_output(&window, &output);
|
||||
|
||||
let workspace = state.common.shell.active_space_mut(output);
|
||||
if move_state.was_tiled {
|
||||
let focus_stack = workspace.focus_stack(&seat);
|
||||
workspace.tiling_layer.map_window(
|
||||
&mut workspace.space,
|
||||
window,
|
||||
&seat,
|
||||
focus_stack.iter(),
|
||||
);
|
||||
} else {
|
||||
workspace.floating_layer.map_window(
|
||||
&mut workspace.space,
|
||||
window,
|
||||
&seat,
|
||||
window_location,
|
||||
);
|
||||
}
|
||||
|
||||
Shell::set_focus(state, Some(&surface), &seat, None);
|
||||
|
||||
for window in state.common.shell.active_space(output).space.windows() {
|
||||
state.common.shell.update_reactive_popups(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
const TOP_RIGHT = Self::TOP.bits | Self::RIGHT.bits;
|
||||
const BOTTOM_RIGHT = Self::BOTTOM.bits | Self::RIGHT.bits;
|
||||
}
|
||||
}
|
||||
|
||||
pub type SeatMoveGrabState = RefCell<Option<MoveGrabState>>;
|
||||
|
||||
pub struct MoveGrabState {
|
||||
window: Window,
|
||||
was_tiled: bool,
|
||||
initial_cursor_location: Point<f64, Logical>,
|
||||
initial_window_location: Point<i32, Logical>,
|
||||
initial_output_location: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
pub struct MoveGrabRenderElement {
|
||||
seat_id: usize,
|
||||
window: Window,
|
||||
window_location: Point<f64, Logical>,
|
||||
}
|
||||
|
||||
impl<R> RenderElement<R> for MoveGrabRenderElement
|
||||
where
|
||||
R: Renderer + ImportAll,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
{
|
||||
fn id(&self) -> usize {
|
||||
self.seat_id
|
||||
}
|
||||
|
||||
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
|
||||
(self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale)
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
|
||||
let scale = scale.into();
|
||||
self.window
|
||||
.physical_bbox_with_popups(RenderElement::<R>::location(self, scale), scale)
|
||||
}
|
||||
|
||||
fn accumulated_damage(
|
||||
&self,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
for_values: Option<SpaceOutputTuple<'_, '_>>,
|
||||
) -> Vec<Rectangle<i32, Physical>> {
|
||||
let scale = scale.into();
|
||||
self.window.accumulated_damage(
|
||||
RenderElement::<R>::location(self, scale),
|
||||
scale,
|
||||
for_values.map(|t| (t.0, t.1)),
|
||||
)
|
||||
}
|
||||
|
||||
fn opaque_regions(
|
||||
&self,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
) -> Option<Vec<Rectangle<i32, Physical>>> {
|
||||
let scale = scale.into();
|
||||
self.window
|
||||
.opaque_regions(RenderElement::<R>::location(self, scale), scale)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
frame: &mut <R as Renderer>::Frame,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
position: Point<f64, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), <R as Renderer>::Error> {
|
||||
draw_window(renderer, frame, &self.window, scale, position, damage, log)
|
||||
impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
|
||||
#[inline]
|
||||
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
|
||||
Self::from_bits(x.into()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveGrabState {
|
||||
pub fn render<I>(&self, seat: &Seat<State>, output: &Output) -> Option<I>
|
||||
where
|
||||
I: From<MoveGrabRenderElement>,
|
||||
{
|
||||
let cursor_at = seat.get_pointer().unwrap().current_location();
|
||||
let delta = cursor_at - self.initial_cursor_location;
|
||||
let location =
|
||||
self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta;
|
||||
|
||||
let mut window_geo = self.window.bbox();
|
||||
window_geo.loc += location.to_i32_round();
|
||||
if !output.geometry().intersection(window_geo).is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(I::from(MoveGrabRenderElement {
|
||||
seat_id: seat.id(),
|
||||
window: self.window.clone(),
|
||||
window_location: location - output.geometry().loc.to_f64(),
|
||||
}))
|
||||
impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
|
||||
#[inline]
|
||||
fn from(x: ResizeEdge) -> Self {
|
||||
Self::try_from(x.bits()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MoveSurfaceGrab {
|
||||
window: Window,
|
||||
start_data: PointerGrabStartData<State>,
|
||||
seat: Seat<State>,
|
||||
pub enum ResizeGrab {
|
||||
Floating(ResizeSurfaceGrab),
|
||||
Tiling(ResizeForkGrab),
|
||||
}
|
||||
|
||||
impl PointerGrab<State> for MoveSurfaceGrab {
|
||||
impl From<ResizeSurfaceGrab> for ResizeGrab {
|
||||
fn from(grab: ResizeSurfaceGrab) -> Self {
|
||||
ResizeGrab::Floating(grab)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResizeForkGrab> for ResizeGrab {
|
||||
fn from(grab: ResizeForkGrab) -> Self {
|
||||
ResizeGrab::Tiling(grab)
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerGrab<State> for ResizeGrab {
|
||||
fn motion(
|
||||
&mut self,
|
||||
state: &mut State,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(WlSurface, Point<i32, Logical>)>,
|
||||
focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(state, None, event);
|
||||
if !self.window.alive() {
|
||||
self.ungrab(state, handle, event.serial, event.time);
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.motion(data, handle, focus, event),
|
||||
ResizeGrab::Tiling(grab) => grab.motion(data, handle, focus, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
state: &mut State,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(state, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
self.ungrab(state, handle, event.serial, event.time);
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.button(data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => grab.button(data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
state: &mut State,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(state, details);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<State> {
|
||||
&self.start_data
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveSurfaceGrab {
|
||||
pub fn new(
|
||||
start_data: PointerGrabStartData<State>,
|
||||
window: Window,
|
||||
seat: &Seat<State>,
|
||||
) -> MoveSurfaceGrab {
|
||||
MoveSurfaceGrab {
|
||||
window,
|
||||
start_data,
|
||||
seat: seat.clone(),
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.axis(data, handle, details),
|
||||
ResizeGrab::Tiling(grab) => grab.axis(data, handle, details),
|
||||
}
|
||||
}
|
||||
|
||||
fn ungrab(
|
||||
&mut self,
|
||||
state: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
) {
|
||||
// No more buttons are pressed, release the grab.
|
||||
let output = active_output(&self.seat, &state.common);
|
||||
let seat = self.seat.clone();
|
||||
|
||||
state.common.event_loop_handle.insert_idle(move |data| {
|
||||
Shell::drop_move(&mut data.state, &seat, &output);
|
||||
});
|
||||
handle.unset_grab(state, serial, time);
|
||||
fn start_data(&self) -> &PointerGrabStartData<State> {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.start_data(),
|
||||
ResizeGrab::Tiling(grab) => grab.start_data(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
5
src/shell/layout/floating/grabs/mod.rs
Normal file
5
src/shell/layout/floating/grabs/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod moving;
|
||||
mod resize;
|
||||
|
||||
pub use self::moving::*;
|
||||
pub use self::resize::*;
|
||||
205
src/shell/layout/floating/grabs/moving.rs
Normal file
205
src/shell/layout/floating/grabs/moving.rs
Normal 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
234
src/shell/layout/floating/grabs/resize.rs
Normal file
234
src/shell/layout/floating/grabs/resize.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +1,87 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use smithay::{
|
||||
desktop::{layer_map_for_output, space::RenderZindex, Kind, Space, Window},
|
||||
input::{
|
||||
pointer::{Focus, GrabStartData as PointerGrabStartData},
|
||||
Seat,
|
||||
},
|
||||
backend::renderer::{element::RenderElement, ImportAll, Renderer},
|
||||
desktop::{layer_map_for_output, space::SpaceElement, Space, Window},
|
||||
input::{pointer::GrabStartData as PointerGrabStartData, Seat},
|
||||
output::Output,
|
||||
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{
|
||||
ResizeEdge, State as XdgState,
|
||||
},
|
||||
utils::{IsAlive, Logical, Point, Rectangle, Serial},
|
||||
wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes},
|
||||
utils::{Logical, Point, Rectangle, Serial},
|
||||
};
|
||||
use std::{collections::HashSet, sync::Mutex};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::state::State;
|
||||
use crate::{
|
||||
backend::render::element::{AsGles2Frame, AsGlowRenderer},
|
||||
shell::{
|
||||
element::{CosmicMapped, CosmicMappedRenderElement},
|
||||
grabs::ResizeEdge,
|
||||
OutputNotMapped,
|
||||
},
|
||||
state::State,
|
||||
utils::prelude::*,
|
||||
};
|
||||
|
||||
mod grabs;
|
||||
pub use self::grabs::*;
|
||||
|
||||
pub const FLOATING_INDEX: u8 = RenderZindex::Shell as u8 + 1;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct FloatingLayout {
|
||||
pending_windows: Vec<Window>,
|
||||
pub windows: HashSet<Window>,
|
||||
pub(in crate::shell) space: Space<CosmicMapped>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WindowUserDataInner {
|
||||
last_geometry: Rectangle<i32, Logical>,
|
||||
impl Default for FloatingLayout {
|
||||
fn default() -> Self {
|
||||
FloatingLayout {
|
||||
space: Space::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type WindowUserData = Mutex<WindowUserDataInner>;
|
||||
|
||||
impl FloatingLayout {
|
||||
pub fn new() -> FloatingLayout {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn map_window(
|
||||
pub fn map_output(&mut self, output: &Output, location: Point<i32, Logical>) {
|
||||
self.space.map_output(output, location)
|
||||
}
|
||||
|
||||
pub fn unmap_output(&mut self, output: &Output) {
|
||||
self.space.unmap_output(output);
|
||||
self.refresh();
|
||||
}
|
||||
|
||||
pub fn map(
|
||||
&mut self,
|
||||
space: &mut Space,
|
||||
window: Window,
|
||||
mapped: impl Into<CosmicMapped>,
|
||||
seat: &Seat<State>,
|
||||
position: impl Into<Option<Point<i32, Logical>>>,
|
||||
) {
|
||||
if let Some(output) = super::output_from_seat(Some(seat), space) {
|
||||
self.map_window_internal(space, window, &output, position.into());
|
||||
} else {
|
||||
self.pending_windows.push(window);
|
||||
}
|
||||
let mapped = mapped.into();
|
||||
let output = seat.active_output();
|
||||
let position = position.into();
|
||||
|
||||
self.map_internal(mapped, &output, position)
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self, space: &mut Space) {
|
||||
self.pending_windows.retain(|w| w.toplevel().alive());
|
||||
if let Some(output) = super::output_from_seat(None, space) {
|
||||
for window in std::mem::take(&mut self.pending_windows).into_iter() {
|
||||
self.map_window_internal(space, window, &output, None);
|
||||
}
|
||||
}
|
||||
// TODO make sure all windows are still visible on any output or move them
|
||||
}
|
||||
|
||||
fn map_window_internal(
|
||||
pub(in crate::shell) fn map_internal(
|
||||
&mut self,
|
||||
space: &mut Space,
|
||||
window: Window,
|
||||
mapped: CosmicMapped,
|
||||
output: &Output,
|
||||
position: Option<Point<i32, Logical>>,
|
||||
) {
|
||||
let last_geometry = window
|
||||
.user_data()
|
||||
.get::<WindowUserData>()
|
||||
.map(|u| u.lock().unwrap().last_geometry);
|
||||
let mut win_geo = window.geometry();
|
||||
let mut win_geo = mapped.geometry();
|
||||
|
||||
let layers = layer_map_for_output(&output);
|
||||
let geometry = layers.non_exclusive_zone();
|
||||
let last_geometry = mapped.last_geometry.lock().unwrap().clone();
|
||||
|
||||
let mut geo_updated = false;
|
||||
if let Some(size) = last_geometry.clone().map(|g| g.size) {
|
||||
geo_updated = win_geo.size == size;
|
||||
if let Some(size) = last_geometry.map(|g| g.size) {
|
||||
geo_updated = win_geo.size != size;
|
||||
win_geo.size = size;
|
||||
}
|
||||
{
|
||||
let (min_size, max_size) = with_states(window.toplevel().wl_surface(), |states| {
|
||||
let attrs = states
|
||||
.data_map
|
||||
.get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
(attrs.min_size, attrs.max_size)
|
||||
});
|
||||
let (min_size, max_size) = (mapped.min_size(), mapped.max_size());
|
||||
if win_geo.size.w > geometry.size.w / 3 * 2 {
|
||||
// try a more reasonable size
|
||||
let mut width = geometry.size.w / 3 * 2;
|
||||
|
|
@ -136,124 +124,188 @@ impl FloatingLayout {
|
|||
.into()
|
||||
});
|
||||
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Kind::Xdg(xdg) = &window.toplevel() {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.unset(XdgState::TiledLeft);
|
||||
state.states.unset(XdgState::TiledRight);
|
||||
state.states.unset(XdgState::TiledTop);
|
||||
state.states.unset(XdgState::TiledBottom);
|
||||
if geo_updated {
|
||||
state.size = Some(win_geo.size);
|
||||
}
|
||||
});
|
||||
xdg.send_configure();
|
||||
mapped.set_tiled(false);
|
||||
if geo_updated {
|
||||
mapped.set_size(win_geo.size);
|
||||
}
|
||||
mapped.configure();
|
||||
|
||||
space.map_window(&window, position, FLOATING_INDEX, false);
|
||||
self.windows.insert(window);
|
||||
self.space.map_element(mapped, position, false);
|
||||
}
|
||||
|
||||
pub fn unmap_window(&mut self, space: &mut Space, window: &Window) {
|
||||
pub fn unmap(&mut self, window: &CosmicMapped) -> bool {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
let is_maximized = match &window.toplevel() {
|
||||
Kind::Xdg(surface) => {
|
||||
surface.with_pending_state(|state| state.states.contains(XdgState::Maximized))
|
||||
}
|
||||
};
|
||||
let is_maximized = window.is_maximized();
|
||||
|
||||
if !is_maximized {
|
||||
if let Some(location) = space.window_location(window) {
|
||||
let user_data = window.user_data();
|
||||
user_data.insert_if_missing(|| WindowUserData::default());
|
||||
user_data
|
||||
.get::<WindowUserData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size);
|
||||
if let Some(location) = self.space.element_location(window) {
|
||||
*window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
|
||||
location,
|
||||
window.geometry().size,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
space.unmap_window(window);
|
||||
self.pending_windows.retain(|w| w != window);
|
||||
self.windows.remove(window);
|
||||
let was_unmaped = self.space.elements().any(|e| e == window);
|
||||
self.space.unmap_elem(&window);
|
||||
was_unmaped
|
||||
}
|
||||
|
||||
pub fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) {
|
||||
let layers = layer_map_for_output(&output);
|
||||
let geometry = layers.non_exclusive_zone();
|
||||
|
||||
if let Some(location) = space.window_location(window) {
|
||||
let user_data = window.user_data();
|
||||
user_data.insert_if_missing(|| WindowUserData::default());
|
||||
user_data
|
||||
.get::<WindowUserData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size);
|
||||
}
|
||||
|
||||
space.map_window(
|
||||
&window,
|
||||
(geometry.loc.x, geometry.loc.y),
|
||||
FLOATING_INDEX,
|
||||
true,
|
||||
);
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Kind::Xdg(surface) = &window.toplevel() {
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(XdgState::Maximized);
|
||||
state.size = Some(geometry.size);
|
||||
});
|
||||
window.configure();
|
||||
}
|
||||
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Logical>> {
|
||||
self.space.element_geometry(elem)
|
||||
}
|
||||
|
||||
pub fn unmaximize_request(&mut self, space: &mut Space, window: &Window) {
|
||||
let last_geometry = window
|
||||
.user_data()
|
||||
.get::<WindowUserData>()
|
||||
.map(|u| u.lock().unwrap().last_geometry);
|
||||
match window.toplevel() {
|
||||
Kind::Xdg(toplevel) => {
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.states.unset(XdgState::Maximized);
|
||||
state.size = last_geometry.map(|g| g.size);
|
||||
});
|
||||
toplevel.send_configure();
|
||||
pub fn maximize_request(&mut self, window: &Window) {
|
||||
if let Some(mapped) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|m| m.windows().any(|(w, _)| &w == window))
|
||||
{
|
||||
if let Some(location) = self.space.element_location(mapped) {
|
||||
*mapped.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
|
||||
location,
|
||||
mapped.geometry().size,
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(last_location) = last_geometry.map(|g| g.loc) {
|
||||
space.map_window(&window, last_location, FLOATING_INDEX, true);
|
||||
}
|
||||
|
||||
pub fn unmaximize_request(&mut self, window: &Window) {
|
||||
let maybe_mapped = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|m| m.windows().any(|(w, _)| &w == window))
|
||||
.cloned();
|
||||
|
||||
if let Some(mapped) = maybe_mapped {
|
||||
let last_geometry = mapped.last_geometry.lock().unwrap().clone();
|
||||
mapped.set_size(last_geometry.map(|g| g.size).expect("No previous size?"));
|
||||
let last_location = last_geometry.map(|g| g.loc).expect("No previous location?");
|
||||
self.space.map_element(mapped, last_location, true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize_request(
|
||||
state: &mut State,
|
||||
window: &Window,
|
||||
&mut self,
|
||||
mapped: &CosmicMapped,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
_serial: Serial,
|
||||
start_data: PointerGrabStartData<State>,
|
||||
edges: ResizeEdge,
|
||||
) {
|
||||
// it is so stupid, that we have to do this here. TODO: Refactor grabs
|
||||
let workspace = state
|
||||
.common
|
||||
.shell
|
||||
.space_for_window_mut(window.toplevel().wl_surface())
|
||||
.unwrap();
|
||||
let space = &mut workspace.space;
|
||||
) -> Option<ResizeSurfaceGrab> {
|
||||
if seat.get_pointer().is_some() {
|
||||
let location = self.space.element_location(&mapped).unwrap();
|
||||
let size = mapped.geometry().size;
|
||||
|
||||
if let Some(pointer) = seat.get_pointer() {
|
||||
let location = space.window_location(&window).unwrap();
|
||||
let size = window.geometry().size;
|
||||
|
||||
let grab =
|
||||
grabs::ResizeSurfaceGrab::new(start_data, window.clone(), edges, location, size);
|
||||
|
||||
pointer.set_grab(state, grab, serial, Focus::Clear);
|
||||
Some(grabs::ResizeSurfaceGrab::new(
|
||||
start_data,
|
||||
mapped.clone(),
|
||||
edges,
|
||||
location,
|
||||
size,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mapped(&self) -> impl Iterator<Item = &CosmicMapped> {
|
||||
self.space.elements()
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> impl Iterator<Item = Window> + '_ {
|
||||
self.mapped().flat_map(|e| e.windows().map(|(w, _)| w))
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
self.space.refresh();
|
||||
for element in self
|
||||
.space
|
||||
.elements()
|
||||
.filter(|e| self.space.outputs_for_element(e).is_empty())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
{
|
||||
// TODO what about windows leaving to the top with no headerbar to drag? can that happen? (Probably if the user is moving outputs down)
|
||||
*element.last_geometry.lock().unwrap() = None;
|
||||
let output = self.space.outputs().next().unwrap().clone();
|
||||
self.map_internal(element, &output, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn most_overlapped_output_for_element(&self, elem: &CosmicMapped) -> Option<Output> {
|
||||
let elem_geo = self.space.element_geometry(elem)?;
|
||||
|
||||
if self.space.outputs().nth(1).is_none() {
|
||||
return self.space.outputs().next().cloned();
|
||||
}
|
||||
|
||||
Some(
|
||||
self.space
|
||||
.outputs_for_element(elem)
|
||||
.into_iter()
|
||||
.max_by_key(|o| {
|
||||
let output_geo = self.space.output_geometry(o).unwrap();
|
||||
if let Some(intersection) = output_geo.intersection(elem_geo) {
|
||||
intersection.size.w * intersection.size.h
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
.unwrap_or(self.space.outputs().next().unwrap().clone()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: FloatingLayout) {
|
||||
let mut output_pos_map = HashMap::new();
|
||||
for output in self.space.outputs() {
|
||||
output_pos_map.insert(
|
||||
output.clone(),
|
||||
self.space.output_geometry(output).unwrap().loc
|
||||
- other
|
||||
.space
|
||||
.output_geometry(output)
|
||||
.map(|geo| geo.loc)
|
||||
.unwrap_or_else(|| (0, 0).into()),
|
||||
);
|
||||
}
|
||||
for element in other.space.elements() {
|
||||
let mut elem_geo = other.space.element_geometry(element).unwrap();
|
||||
let output = other
|
||||
.space
|
||||
.outputs_for_element(element)
|
||||
.into_iter()
|
||||
.filter(|o| self.space.outputs().any(|o2| o == o2))
|
||||
.max_by_key(|o| {
|
||||
let output_geo = other.space.output_geometry(o).unwrap();
|
||||
let intersection = output_geo.intersection(elem_geo).unwrap();
|
||||
intersection.size.w * intersection.size.h
|
||||
})
|
||||
.unwrap_or(self.space.outputs().next().unwrap().clone());
|
||||
elem_geo.loc += output_pos_map
|
||||
.get(&output)
|
||||
.copied()
|
||||
.unwrap_or_else(|| (0, 0).into());
|
||||
self.space.map_element(element.clone(), elem_geo.loc, false);
|
||||
}
|
||||
self.refresh(); //fixup any out of bounds elements
|
||||
}
|
||||
|
||||
pub fn render_output<R>(
|
||||
&self,
|
||||
output: &Output,
|
||||
) -> Result<Vec<CosmicMappedRenderElement<R>>, OutputNotMapped>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGlowRenderer,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
{
|
||||
let output_scale = output.current_scale().fractional_scale();
|
||||
let output_geo = self.space.output_geometry(output).ok_or(OutputNotMapped)?;
|
||||
Ok(self
|
||||
.space
|
||||
.render_elements_for_region::<R, _>(&output_geo, output_scale))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{input::ActiveOutput, state::State};
|
||||
use regex::RegexSet;
|
||||
use smithay::{
|
||||
desktop::{Space, Window},
|
||||
input::Seat,
|
||||
output::Output,
|
||||
desktop::Window,
|
||||
wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes},
|
||||
};
|
||||
use std::sync::Mutex;
|
||||
|
|
@ -19,6 +16,16 @@ pub enum Orientation {
|
|||
Vertical,
|
||||
}
|
||||
|
||||
impl std::ops::Not for Orientation {
|
||||
type Output = Self;
|
||||
fn not(self) -> Self::Output {
|
||||
match self {
|
||||
Orientation::Horizontal => Orientation::Vertical,
|
||||
Orientation::Vertical => Orientation::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref EXCEPTIONS_APPID: RegexSet = RegexSet::new(&[
|
||||
r"Authy Desktop",
|
||||
|
|
@ -118,17 +125,3 @@ pub fn should_be_floating(window: &Window) -> bool {
|
|||
false
|
||||
})
|
||||
}
|
||||
|
||||
fn output_from_seat(seat: Option<&Seat<State>>, space: &Space) -> Option<Output> {
|
||||
seat.and_then(|seat| {
|
||||
seat.user_data()
|
||||
.get::<ActiveOutput>()
|
||||
.map(|active| active.0.borrow().clone())
|
||||
.or_else(|| {
|
||||
seat.get_pointer()
|
||||
.map(|ptr| space.output_under(ptr.current_location()).next().unwrap())
|
||||
.cloned()
|
||||
})
|
||||
})
|
||||
.or_else(|| space.outputs().next().cloned())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,52 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{shell::layout::Orientation, utils::prelude::*};
|
||||
use atomic_float::AtomicF64;
|
||||
use crate::{
|
||||
shell::{focus::target::PointerFocusTarget, layout::Orientation},
|
||||
utils::prelude::*,
|
||||
};
|
||||
use id_tree::NodeId;
|
||||
use smithay::{
|
||||
input::pointer::{
|
||||
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
||||
PointerInnerHandle,
|
||||
},
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
utils::{Logical, Point, Size},
|
||||
output::{Output, WeakOutput},
|
||||
utils::{Logical, Point},
|
||||
};
|
||||
use std::sync::{atomic::Ordering, Arc};
|
||||
|
||||
use super::Data;
|
||||
|
||||
pub struct ResizeForkGrab {
|
||||
pub start_data: PointerGrabStartData<State>,
|
||||
pub orientation: Orientation,
|
||||
pub initial_size: Size<i32, Logical>,
|
||||
pub initial_ratio: f64,
|
||||
pub ratio: Arc<AtomicF64>,
|
||||
start_data: PointerGrabStartData<State>,
|
||||
idx: usize,
|
||||
initial_size_upleft: i32,
|
||||
initial_size_downright: i32,
|
||||
node: NodeId,
|
||||
output: WeakOutput,
|
||||
}
|
||||
|
||||
impl ResizeForkGrab {
|
||||
pub fn new(
|
||||
start_data: PointerGrabStartData<State>,
|
||||
node: NodeId,
|
||||
output: &Output,
|
||||
data: &Data,
|
||||
idx: usize,
|
||||
) -> ResizeForkGrab {
|
||||
let sizes = match data {
|
||||
Data::Group { ref sizes, .. } => sizes,
|
||||
_ => panic!("Resizing without a group?!?"),
|
||||
};
|
||||
|
||||
ResizeForkGrab {
|
||||
start_data,
|
||||
idx,
|
||||
initial_size_upleft: sizes.iter().take(idx + 1).sum(),
|
||||
initial_size_downright: sizes.iter().skip(idx + 1).sum(),
|
||||
node,
|
||||
output: output.downgrade(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerGrab<State> for ResizeForkGrab {
|
||||
|
|
@ -25,21 +54,139 @@ impl PointerGrab<State> for ResizeForkGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(WlSurface, Point<i32, Logical>)>,
|
||||
_focus: Option<(PointerFocusTarget, Point<i32, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
||||
let delta = event.location - self.start_data.location;
|
||||
let delta = match self.orientation {
|
||||
Orientation::Vertical => delta.x / self.initial_size.w as f64,
|
||||
Orientation::Horizontal => delta.y / self.initial_size.h as f64,
|
||||
};
|
||||
self.ratio.store(
|
||||
0.9f64.min(0.1f64.max(self.initial_ratio + delta)),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
if let Some(output) = self.output.upgrade() {
|
||||
let tiling_layer = &mut data.common.shell.active_space_mut(&output).tiling_layer;
|
||||
if let Some(tree) = tiling_layer.trees.get_mut(&output) {
|
||||
if tree.get(&self.node).is_ok() {
|
||||
let orientation = tree.get(&self.node).unwrap().data().orientation();
|
||||
let delta = match orientation {
|
||||
Orientation::Vertical => delta.x,
|
||||
Orientation::Horizontal => delta.y,
|
||||
}
|
||||
.round() as i32;
|
||||
|
||||
let upleft_node_id =
|
||||
match tree.children_ids(&self.node).unwrap().skip(self.idx).next() {
|
||||
Some(elem) => elem,
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let downright_node_id = match tree
|
||||
.children_ids(&self.node)
|
||||
.unwrap()
|
||||
.skip(self.idx + 1)
|
||||
.next()
|
||||
{
|
||||
Some(elem) => elem,
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let next_mapped = |mut node| loop {
|
||||
if let Some(node_id) = node {
|
||||
match tree.get(&node_id).unwrap().data() {
|
||||
Data::Group { orientation: o, .. } if o == &orientation => {
|
||||
node = tree.children_ids(&node_id).unwrap().last().cloned();
|
||||
}
|
||||
_ => {
|
||||
break node_id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let upleft_mapped_id = next_mapped(Some(upleft_node_id.clone()));
|
||||
let downright_mapped_id = next_mapped(Some(downright_node_id.clone()));
|
||||
|
||||
let new_upleft_size = self.initial_size_upleft + delta;
|
||||
let new_downright_size = self.initial_size_downright - delta;
|
||||
let new_upleft_mapped_size = match orientation {
|
||||
Orientation::Horizontal => {
|
||||
tree.get(&upleft_mapped_id)
|
||||
.unwrap()
|
||||
.data()
|
||||
.geometry()
|
||||
.size
|
||||
.h
|
||||
}
|
||||
Orientation::Vertical => {
|
||||
tree.get(&upleft_mapped_id)
|
||||
.unwrap()
|
||||
.data()
|
||||
.geometry()
|
||||
.size
|
||||
.w
|
||||
}
|
||||
} + delta;
|
||||
let new_downright_mapped_size = match orientation {
|
||||
Orientation::Horizontal => {
|
||||
tree.get(&downright_mapped_id)
|
||||
.unwrap()
|
||||
.data()
|
||||
.geometry()
|
||||
.size
|
||||
.h
|
||||
}
|
||||
Orientation::Vertical => {
|
||||
tree.get(&downright_mapped_id)
|
||||
.unwrap()
|
||||
.data()
|
||||
.geometry()
|
||||
.size
|
||||
.w
|
||||
}
|
||||
} - delta;
|
||||
|
||||
if new_upleft_mapped_size > 100 && new_downright_mapped_size > 100 {
|
||||
// lets update
|
||||
{
|
||||
let node = tree.get_mut(&self.node).unwrap();
|
||||
let data = node.data_mut();
|
||||
match data {
|
||||
Data::Group { sizes, .. } => {
|
||||
sizes[self.idx] = new_upleft_size;
|
||||
sizes[self.idx + 1] = new_downright_size;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
for (mapped_id, mapped_size) in &[
|
||||
(upleft_mapped_id, new_upleft_mapped_size),
|
||||
(downright_mapped_id, new_downright_mapped_size),
|
||||
] {
|
||||
let parent = tree.get(mapped_id).unwrap().parent().cloned().unwrap();
|
||||
if parent != self.node {
|
||||
let idx = tree
|
||||
.children_ids(&parent)
|
||||
.unwrap()
|
||||
.position(|id| id == mapped_id)
|
||||
.unwrap();
|
||||
let node = tree.get_mut(&parent).unwrap();
|
||||
let data = node.data_mut();
|
||||
match data {
|
||||
Data::Group { sizes, .. } => {
|
||||
sizes[idx] = *mapped_size;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
return tiling_layer.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1426
src/shell/mod.rs
1426
src/shell/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,109 +1,221 @@
|
|||
use crate::{
|
||||
shell::layout::{floating::FloatingLayout, tiling::TilingLayout},
|
||||
backend::render::element::{AsGles2Frame, AsGlowRenderer},
|
||||
shell::layout::{
|
||||
floating::{FloatingLayout, MoveSurfaceGrab},
|
||||
tiling::TilingLayout,
|
||||
},
|
||||
state::State,
|
||||
wayland::protocols::workspace::WorkspaceHandle,
|
||||
utils::prelude::*,
|
||||
wayland::{
|
||||
handlers::screencopy::DropableSession,
|
||||
protocols::{
|
||||
screencopy::{BufferParams, Session as ScreencopySession},
|
||||
workspace::WorkspaceHandle,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use smithay::{
|
||||
desktop::{Kind, Space, Window, WindowSurfaceType},
|
||||
backend::renderer::{
|
||||
element::{surface::WaylandSurfaceRenderElement, AsRenderElements, Element, RenderElement},
|
||||
ImportAll, Renderer,
|
||||
},
|
||||
desktop::{layer_map_for_output, space::SpaceElement, Kind, LayerSurface, Window},
|
||||
input::{pointer::GrabStartData as PointerGrabStartData, Seat},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge},
|
||||
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle},
|
||||
wayland_server::protocol::wl_surface::WlSurface,
|
||||
},
|
||||
utils::{IsAlive, Serial},
|
||||
utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial},
|
||||
wayland::shell::wlr_layer::Layer,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{
|
||||
element::CosmicMapped,
|
||||
focus::{FocusStack, FocusStackMut},
|
||||
grabs::ResizeGrab,
|
||||
CosmicMappedRenderElement,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Workspace {
|
||||
pub idx: u8,
|
||||
pub space: Space,
|
||||
pub tiling_layer: TilingLayout,
|
||||
pub floating_layer: FloatingLayout,
|
||||
tiling_enabled: bool,
|
||||
pub fullscreen: HashMap<String, Window>,
|
||||
pub tiling_enabled: bool,
|
||||
pub fullscreen: HashMap<Output, Window>,
|
||||
pub handle: WorkspaceHandle,
|
||||
pub focus_stack: FocusStacks,
|
||||
pub pending_buffers: Vec<(ScreencopySession, BufferParams)>,
|
||||
pub screencopy_sessions: Vec<DropableSession>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FocusStacks(HashMap<Seat<State>, IndexSet<CosmicMapped>>);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ManagedState {
|
||||
Tiling,
|
||||
Floating,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn new(idx: u8, handle: WorkspaceHandle) -> Workspace {
|
||||
pub fn new(handle: WorkspaceHandle) -> Workspace {
|
||||
Workspace {
|
||||
idx,
|
||||
space: Space::new(None),
|
||||
tiling_layer: TilingLayout::new(),
|
||||
floating_layer: FloatingLayout::new(),
|
||||
tiling_enabled: true,
|
||||
fullscreen: HashMap::new(),
|
||||
handle,
|
||||
focus_stack: FocusStacks::default(),
|
||||
pending_buffers: Vec::new(),
|
||||
screencopy_sessions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self, dh: &DisplayHandle) {
|
||||
let outputs = self.space.outputs().collect::<Vec<_>>();
|
||||
let dead_output_windows = self
|
||||
.fullscreen
|
||||
.iter()
|
||||
.filter(|(name, _)| !outputs.iter().any(|o| o.name() == **name))
|
||||
.map(|(_, w)| w)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for window in dead_output_windows {
|
||||
self.unfullscreen_request(&window);
|
||||
}
|
||||
pub fn refresh(&mut self) {
|
||||
self.fullscreen.retain(|_, w| w.alive());
|
||||
self.floating_layer.refresh(&mut self.space);
|
||||
self.tiling_layer.refresh(&mut self.space);
|
||||
self.space.refresh(dh);
|
||||
self.floating_layer.refresh();
|
||||
self.tiling_layer.refresh();
|
||||
}
|
||||
|
||||
pub fn commit(&mut self, surface: &WlSurface) {
|
||||
if let Some(mapped) = self.element_for_surface(surface) {
|
||||
mapped
|
||||
.windows()
|
||||
.find(|(w, _)| w.toplevel().wl_surface() == surface)
|
||||
.unwrap()
|
||||
.0
|
||||
.on_commit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_output(&mut self, output: &Output, position: Point<i32, Logical>) {
|
||||
self.tiling_layer.map_output(output, position);
|
||||
self.floating_layer.map_output(output, position);
|
||||
}
|
||||
|
||||
pub fn unmap_output(&mut self, output: &Output) {
|
||||
if let Some(dead_output_window) = self.fullscreen.remove(output) {
|
||||
self.unfullscreen_request(&dead_output_window);
|
||||
}
|
||||
self.tiling_layer.unmap_output(output);
|
||||
self.floating_layer.unmap_output(output);
|
||||
self.refresh();
|
||||
}
|
||||
|
||||
pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option<ManagedState> {
|
||||
let was_floating = self.floating_layer.unmap(&mapped);
|
||||
let was_tiling = self.tiling_layer.unmap(&mapped).is_some();
|
||||
if was_floating || was_tiling {
|
||||
assert!(was_floating != was_tiling);
|
||||
}
|
||||
self.focus_stack
|
||||
.0
|
||||
.values_mut()
|
||||
.for_each(|set| set.retain(|m| m != mapped));
|
||||
if was_floating {
|
||||
Some(ManagedState::Floating)
|
||||
} else if was_tiling {
|
||||
Some(ManagedState::Tiling)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> {
|
||||
self.floating_layer
|
||||
.mapped()
|
||||
.chain(self.tiling_layer.mapped().map(|(_, w, _)| w))
|
||||
.find(|e| {
|
||||
e.windows()
|
||||
.any(|(w, _)| w.toplevel().wl_surface() == surface)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn outputs_for_element(&self, elem: &CosmicMapped) -> impl Iterator<Item = Output> {
|
||||
self.floating_layer
|
||||
.space
|
||||
.outputs_for_element(elem)
|
||||
.into_iter()
|
||||
.chain(self.tiling_layer.output_for_element(elem).cloned())
|
||||
}
|
||||
|
||||
pub fn output_under(&self, point: Point<i32, Logical>) -> Option<&Output> {
|
||||
let space = &self.floating_layer.space;
|
||||
space.outputs().find(|o| {
|
||||
let internal_output_geo = space.output_geometry(o).unwrap();
|
||||
let external_output_geo = o.geometry();
|
||||
internal_output_geo.contains(point - external_output_geo.loc + internal_output_geo.loc)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn element_under(
|
||||
&self,
|
||||
location: Point<f64, Logical>,
|
||||
) -> Option<(&CosmicMapped, Point<i32, Logical>)> {
|
||||
self.floating_layer
|
||||
.space
|
||||
.element_under(location)
|
||||
.or_else(|| {
|
||||
self.tiling_layer.mapped().find_map(|(_, mapped, loc)| {
|
||||
let test_point = location - loc.to_f64() + mapped.geometry().loc.to_f64();
|
||||
mapped
|
||||
.is_in_input_region(&test_point)
|
||||
.then_some((mapped, loc - mapped.geometry().loc))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Logical>> {
|
||||
let space = &self.floating_layer.space;
|
||||
let outputs = space.outputs().collect::<Vec<_>>();
|
||||
let offset = if outputs.len() == 1
|
||||
&& space.output_geometry(&outputs[0]).unwrap().loc == Point::from((0, 0))
|
||||
{
|
||||
outputs[0].geometry().loc
|
||||
} else {
|
||||
(0, 0).into()
|
||||
};
|
||||
|
||||
self.floating_layer
|
||||
.space
|
||||
.element_geometry(elem)
|
||||
.or_else(|| self.tiling_layer.element_geometry(elem))
|
||||
.map(|mut geo| {
|
||||
geo.loc += offset;
|
||||
geo
|
||||
})
|
||||
}
|
||||
|
||||
pub fn maximize_request(&mut self, window: &Window, output: &Output) {
|
||||
if self.fullscreen.values().any(|w| w == window) {
|
||||
if self.fullscreen.contains_key(output) {
|
||||
return;
|
||||
}
|
||||
if self.floating_layer.windows.contains(window) {
|
||||
self.floating_layer
|
||||
.maximize_request(&mut self.space, window, output);
|
||||
}
|
||||
}
|
||||
|
||||
self.floating_layer.maximize_request(window);
|
||||
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Kind::Xdg(xdg) = &window.toplevel() {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Maximized);
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
});
|
||||
}
|
||||
|
||||
self.set_fullscreen(window, output)
|
||||
}
|
||||
pub fn unmaximize_request(&mut self, window: &Window) {
|
||||
if self.fullscreen.values().any(|w| w == window) {
|
||||
return self.unfullscreen_request(window);
|
||||
}
|
||||
if self.floating_layer.windows.contains(window) {
|
||||
self.floating_layer
|
||||
.unmaximize_request(&mut self.space, window);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize_request(
|
||||
state: &mut State,
|
||||
surface: &WlSurface,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
start_data: PointerGrabStartData<State>,
|
||||
edges: ResizeEdge,
|
||||
) {
|
||||
let workspace = state.common.shell.space_for_window_mut(surface).unwrap();
|
||||
let window = workspace
|
||||
.space
|
||||
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
if workspace.fullscreen.values().any(|w| w == &window) {
|
||||
return;
|
||||
}
|
||||
if workspace.floating_layer.windows.contains(&window) {
|
||||
FloatingLayout::resize_request(state, &window, seat, serial, start_data.clone(), edges)
|
||||
} else if workspace.tiling_layer.windows.contains(&window) {
|
||||
TilingLayout::resize_request(state, &window, seat, serial, start_data, edges)
|
||||
self.unfullscreen_request(window);
|
||||
self.floating_layer.unmaximize_request(window);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen_request(&mut self, window: &Window, output: &Output) {
|
||||
if self.fullscreen.contains_key(&output.name()) {
|
||||
if self.fullscreen.contains_key(output) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +223,24 @@ impl Workspace {
|
|||
if let Kind::Xdg(xdg) = &window.toplevel() {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||
state.states.unset(xdg_toplevel::State::Maximized);
|
||||
});
|
||||
}
|
||||
|
||||
self.set_fullscreen(window, output)
|
||||
}
|
||||
|
||||
fn set_fullscreen(&mut self, window: &Window, output: &Output) {
|
||||
if let Some(mapped) = self
|
||||
.mapped()
|
||||
.find(|m| m.windows().any(|(w, _)| &w == window))
|
||||
{
|
||||
mapped.set_active(window);
|
||||
}
|
||||
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Kind::Xdg(xdg) = &window.toplevel() {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
output
|
||||
.current_mode()
|
||||
|
|
@ -123,8 +253,8 @@ impl Workspace {
|
|||
});
|
||||
|
||||
xdg.send_configure();
|
||||
self.fullscreen.insert(output.name(), window.clone());
|
||||
}
|
||||
self.fullscreen.insert(output.clone(), window.clone());
|
||||
}
|
||||
|
||||
pub fn unfullscreen_request(&mut self, window: &Window) {
|
||||
|
|
@ -133,45 +263,127 @@ impl Workspace {
|
|||
if let Kind::Xdg(xdg) = &window.toplevel() {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
state.states.unset(xdg_toplevel::State::Maximized);
|
||||
state.size = None;
|
||||
});
|
||||
self.floating_layer.refresh(&mut self.space);
|
||||
self.tiling_layer.refresh(&mut self.space);
|
||||
self.floating_layer.refresh();
|
||||
self.tiling_layer.refresh();
|
||||
xdg.send_configure();
|
||||
}
|
||||
self.fullscreen.retain(|_, w| w != window);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) {
|
||||
if self.fullscreen.contains_key(&output.name()) {
|
||||
self.unfullscreen_request(window)
|
||||
pub fn maximize_toggle(&mut self, window: &Window, output: &Output) {
|
||||
if self.fullscreen.contains_key(output) {
|
||||
self.unmaximize_request(window)
|
||||
} else {
|
||||
self.fullscreen_request(window, output)
|
||||
self.maximize_request(window, output)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> {
|
||||
if !self.space.outputs().any(|o| o == output) {
|
||||
self.fullscreen.get(output).filter(|w| w.alive())
|
||||
}
|
||||
|
||||
pub fn resize_request(
|
||||
&mut self,
|
||||
mapped: &CosmicMapped,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
start_data: PointerGrabStartData<State>,
|
||||
edges: ResizeEdge,
|
||||
) -> Option<ResizeGrab> {
|
||||
if mapped.is_fullscreen() || mapped.is_maximized() {
|
||||
return None;
|
||||
}
|
||||
self.fullscreen.get(&output.name()).filter(|w| w.alive())
|
||||
|
||||
let edges = edges.into();
|
||||
if self.floating_layer.mapped().any(|m| m == mapped) {
|
||||
self.floating_layer
|
||||
.resize_request(mapped, seat, serial, start_data.clone(), edges)
|
||||
.map(Into::into)
|
||||
} else if self.tiling_layer.mapped().any(|(_, m, _)| m == mapped) {
|
||||
self.tiling_layer
|
||||
.resize_request(mapped, seat, serial, start_data, edges)
|
||||
.map(Into::into)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_request(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
seat: &Seat<State>,
|
||||
output: &Output,
|
||||
_serial: Serial,
|
||||
start_data: PointerGrabStartData<State>,
|
||||
) -> Option<MoveSurfaceGrab> {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
let pos = pointer.current_location();
|
||||
|
||||
let mapped = self
|
||||
.element_for_surface(window.toplevel().wl_surface())?
|
||||
.clone();
|
||||
let mut initial_window_location = self.element_geometry(&mapped).unwrap().loc;
|
||||
|
||||
if mapped.is_fullscreen() || mapped.is_maximized() {
|
||||
// If surface is maximized then unmaximize it
|
||||
self.unmaximize_request(window);
|
||||
let new_size = match window.toplevel() {
|
||||
Kind::Xdg(toplevel) => toplevel.with_pending_state(|state| state.size),
|
||||
//_ => unreachable!(), // TODO x11
|
||||
};
|
||||
let ratio = pos.x / output.geometry().size.w as f64;
|
||||
|
||||
initial_window_location = new_size
|
||||
.map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into())
|
||||
.unwrap_or_else(|| pos)
|
||||
.to_i32_round();
|
||||
}
|
||||
|
||||
let was_floating = self.floating_layer.unmap(&mapped);
|
||||
//let was_tiled = self.tiling_layer.unmap(&mapped);
|
||||
//assert!(was_floating != was_tiled);
|
||||
|
||||
if was_floating {
|
||||
Some(MoveSurfaceGrab::new(
|
||||
start_data,
|
||||
mapped,
|
||||
seat,
|
||||
pos,
|
||||
initial_window_location,
|
||||
))
|
||||
} else {
|
||||
None // TODO
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_tiling(&mut self, seat: &Seat<State>) {
|
||||
if self.tiling_enabled {
|
||||
for window in self.tiling_layer.windows.clone().into_iter() {
|
||||
self.tiling_layer.unmap_window(&mut self.space, &window);
|
||||
self.floating_layer
|
||||
.map_window(&mut self.space, window, seat, None);
|
||||
for window in self
|
||||
.tiling_layer
|
||||
.mapped()
|
||||
.map(|(_, m, _)| m.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
{
|
||||
self.tiling_layer.unmap(&window);
|
||||
self.floating_layer.map(window, seat, None);
|
||||
}
|
||||
self.tiling_enabled = false;
|
||||
} else {
|
||||
let focus_stack = self.focus_stack(seat);
|
||||
for window in self.floating_layer.windows.clone().into_iter() {
|
||||
self.floating_layer.unmap_window(&mut self.space, &window);
|
||||
self.tiling_layer
|
||||
.map_window(&mut self.space, window, seat, focus_stack.iter())
|
||||
let focus_stack = self.focus_stack.get(seat);
|
||||
for window in self
|
||||
.floating_layer
|
||||
.mapped()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
{
|
||||
self.floating_layer.unmap(&window);
|
||||
self.tiling_layer.map(window, seat, focus_stack.iter())
|
||||
}
|
||||
self.tiling_enabled = true;
|
||||
}
|
||||
|
|
@ -179,18 +391,290 @@ impl Workspace {
|
|||
|
||||
pub fn toggle_floating_window(&mut self, seat: &Seat<State>) {
|
||||
if self.tiling_enabled {
|
||||
if let Some(window) = self.focus_stack(seat).iter().next().cloned() {
|
||||
if self.tiling_layer.windows.contains(&window) {
|
||||
self.tiling_layer.unmap_window(&mut self.space, &window);
|
||||
self.floating_layer
|
||||
.map_window(&mut self.space, window, seat, None);
|
||||
} else if self.floating_layer.windows.contains(&window) {
|
||||
let focus_stack = self.focus_stack(seat);
|
||||
self.floating_layer.unmap_window(&mut self.space, &window);
|
||||
self.tiling_layer
|
||||
.map_window(&mut self.space, window, seat, focus_stack.iter())
|
||||
if let Some(window) = self.focus_stack.get(seat).iter().next().cloned() {
|
||||
if self.tiling_layer.mapped().any(|(_, m, _)| m == &window) {
|
||||
self.tiling_layer.unmap(&window);
|
||||
self.floating_layer.map(window, seat, None);
|
||||
} else if self.floating_layer.mapped().any(|w| w == &window) {
|
||||
let focus_stack = self.focus_stack.get(seat);
|
||||
self.floating_layer.unmap(&window);
|
||||
self.tiling_layer.map(window, seat, focus_stack.iter())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mapped(&self) -> impl Iterator<Item = &CosmicMapped> {
|
||||
self.floating_layer
|
||||
.mapped()
|
||||
.chain(self.tiling_layer.mapped().map(|(_, w, _)| w))
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> impl Iterator<Item = Window> + '_ {
|
||||
self.floating_layer
|
||||
.windows()
|
||||
.chain(self.tiling_layer.windows().map(|(_, w, _)| w))
|
||||
}
|
||||
|
||||
pub fn render_output<R>(
|
||||
&self,
|
||||
output: &Output,
|
||||
) -> Result<Vec<WorkspaceRenderElement<R>>, OutputNotMapped>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGlowRenderer,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
{
|
||||
let mut render_elements = Vec::new();
|
||||
|
||||
let output_scale = output.current_scale().fractional_scale();
|
||||
let layer_map = layer_map_for_output(output);
|
||||
|
||||
if let Some(fullscreen) = self.fullscreen.get(output) {
|
||||
// overlay layer surfaces
|
||||
render_elements.extend(
|
||||
layer_map
|
||||
.layers()
|
||||
.rev()
|
||||
.filter(|s| s.layer() == Layer::Overlay)
|
||||
.filter_map(|surface| {
|
||||
layer_map
|
||||
.layer_geometry(surface)
|
||||
.map(|geo| (geo.loc, surface))
|
||||
})
|
||||
.flat_map(|(loc, surface)| {
|
||||
AsRenderElements::<R>::render_elements::<WorkspaceRenderElement<R>>(
|
||||
surface,
|
||||
loc.to_physical_precise_round(output_scale),
|
||||
Scale::from(output_scale),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
// fullscreen window
|
||||
render_elements.extend(AsRenderElements::<R>::render_elements::<
|
||||
WorkspaceRenderElement<R>,
|
||||
>(fullscreen, (0, 0).into(), output_scale.into()));
|
||||
} else {
|
||||
// TODO: Handle modes like
|
||||
// - keyboard window swapping
|
||||
// - resizing / moving in tiling
|
||||
|
||||
// overlay and top layer surfaces
|
||||
let lower = {
|
||||
let (lower, upper): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map
|
||||
.layers()
|
||||
.rev()
|
||||
.partition(|s| matches!(s.layer(), Layer::Background | Layer::Bottom));
|
||||
|
||||
render_elements.extend(
|
||||
upper
|
||||
.into_iter()
|
||||
.filter_map(|surface| {
|
||||
layer_map
|
||||
.layer_geometry(surface)
|
||||
.map(|geo| (geo.loc, surface))
|
||||
})
|
||||
.flat_map(|(loc, surface)| {
|
||||
AsRenderElements::<R>::render_elements::<WorkspaceRenderElement<R>>(
|
||||
surface,
|
||||
loc.to_physical_precise_round(output_scale),
|
||||
Scale::from(output_scale),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
lower
|
||||
};
|
||||
|
||||
// floating surfaces
|
||||
render_elements.extend(
|
||||
self.floating_layer
|
||||
.render_output::<R>(output)?
|
||||
.into_iter()
|
||||
.map(WorkspaceRenderElement::from),
|
||||
);
|
||||
|
||||
//tiling surfaces
|
||||
render_elements.extend(
|
||||
self.tiling_layer
|
||||
.render_output::<R>(output)?
|
||||
.into_iter()
|
||||
.map(WorkspaceRenderElement::from),
|
||||
);
|
||||
|
||||
// bottom and background layer surfaces
|
||||
{
|
||||
render_elements.extend(
|
||||
lower
|
||||
.into_iter()
|
||||
.filter_map(|surface| {
|
||||
layer_map
|
||||
.layer_geometry(surface)
|
||||
.map(|geo| (geo.loc, surface))
|
||||
})
|
||||
.flat_map(|(loc, surface)| {
|
||||
AsRenderElements::<R>::render_elements::<WorkspaceRenderElement<R>>(
|
||||
surface,
|
||||
loc.to_physical_precise_round(output_scale),
|
||||
Scale::from(output_scale),
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(render_elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusStacks {
|
||||
pub fn get<'a>(&'a self, seat: &Seat<State>) -> FocusStack<'a> {
|
||||
FocusStack(self.0.get(seat))
|
||||
}
|
||||
|
||||
pub fn get_mut<'a>(&'a mut self, seat: &Seat<State>) -> FocusStackMut<'a> {
|
||||
FocusStackMut(self.0.entry(seat.clone()).or_default())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutputNotMapped;
|
||||
|
||||
pub enum WorkspaceRenderElement<R>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGlowRenderer,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
{
|
||||
Wayland(WaylandSurfaceRenderElement),
|
||||
Window(CosmicMappedRenderElement<R>),
|
||||
}
|
||||
|
||||
impl<R> Element for WorkspaceRenderElement<R>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGlowRenderer,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
{
|
||||
fn id(&self) -> &smithay::backend::renderer::element::Id {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.id(),
|
||||
WorkspaceRenderElement::Window(elem) => elem.id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.current_commit(),
|
||||
WorkspaceRenderElement::Window(elem) => elem.current_commit(),
|
||||
}
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, smithay::utils::Buffer> {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.src(),
|
||||
WorkspaceRenderElement::Window(elem) => elem.src(),
|
||||
}
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, smithay::utils::Physical> {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.geometry(scale),
|
||||
WorkspaceRenderElement::Window(elem) => elem.geometry(scale),
|
||||
}
|
||||
}
|
||||
|
||||
fn location(&self, scale: Scale<f64>) -> Point<i32, smithay::utils::Physical> {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.location(scale),
|
||||
WorkspaceRenderElement::Window(elem) => elem.location(scale),
|
||||
}
|
||||
}
|
||||
|
||||
fn transform(&self) -> smithay::utils::Transform {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.transform(),
|
||||
WorkspaceRenderElement::Window(elem) => elem.transform(),
|
||||
}
|
||||
}
|
||||
|
||||
fn damage_since(
|
||||
&self,
|
||||
scale: Scale<f64>,
|
||||
commit: Option<smithay::backend::renderer::utils::CommitCounter>,
|
||||
) -> Vec<Rectangle<i32, smithay::utils::Physical>> {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.damage_since(scale, commit),
|
||||
WorkspaceRenderElement::Window(elem) => elem.damage_since(scale, commit),
|
||||
}
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, smithay::utils::Physical>> {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.opaque_regions(scale),
|
||||
WorkspaceRenderElement::Window(elem) => elem.opaque_regions(scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderElement<R> for WorkspaceRenderElement<R>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGlowRenderer,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
frame: &mut <R as Renderer>::Frame,
|
||||
location: Point<i32, smithay::utils::Physical>,
|
||||
scale: Scale<f64>,
|
||||
damage: &[Rectangle<i32, smithay::utils::Physical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), <R as Renderer>::Error> {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => {
|
||||
elem.draw(renderer, frame, location, scale, damage, log)
|
||||
}
|
||||
WorkspaceRenderElement::Window(elem) => {
|
||||
elem.draw(renderer, frame, location, scale, damage, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn underlying_storage(
|
||||
&self,
|
||||
renderer: &R,
|
||||
) -> Option<smithay::backend::renderer::element::UnderlyingStorage<'_, R>> {
|
||||
match self {
|
||||
WorkspaceRenderElement::Wayland(elem) => elem.underlying_storage(renderer),
|
||||
WorkspaceRenderElement::Window(elem) => elem.underlying_storage(renderer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> From<WaylandSurfaceRenderElement> for WorkspaceRenderElement<R>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGlowRenderer,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
{
|
||||
fn from(elem: WaylandSurfaceRenderElement) -> Self {
|
||||
WorkspaceRenderElement::Wayland(elem)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> From<CosmicMappedRenderElement<R>> for WorkspaceRenderElement<R>
|
||||
where
|
||||
R: Renderer + ImportAll + AsGlowRenderer,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Frame: AsGles2Frame,
|
||||
CosmicMappedRenderElement<R>: RenderElement<R>,
|
||||
{
|
||||
fn from(elem: CosmicMappedRenderElement<R>) -> Self {
|
||||
WorkspaceRenderElement::Window(elem)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
539
src/state.rs
539
src/state.rs
|
|
@ -7,37 +7,46 @@ use crate::{
|
|||
shell::Shell,
|
||||
utils::prelude::*,
|
||||
wayland::protocols::{
|
||||
drm::WlDrmState, export_dmabuf::ExportDmabufState,
|
||||
output_configuration::OutputConfigurationState, workspace::WorkspaceClientState,
|
||||
drm::WlDrmState,
|
||||
output_configuration::OutputConfigurationState,
|
||||
screencopy::{BufferParams, ScreencopyState, Session as ScreencopySession},
|
||||
workspace::WorkspaceClientState,
|
||||
},
|
||||
};
|
||||
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode;
|
||||
use smithay::{
|
||||
backend::drm::DrmNode,
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
renderer::{
|
||||
element::{default_primary_scanout_output_compare, RenderElementStates},
|
||||
glow::GlowRenderer,
|
||||
},
|
||||
},
|
||||
desktop::utils::{
|
||||
surface_presentation_feedback_flags_from_states, surface_primary_scanout_output,
|
||||
update_surface_primary_scanout_output, OutputPresentationFeedback,
|
||||
},
|
||||
input::{Seat, SeatState},
|
||||
output::{Mode as OutputMode, Output, Scale},
|
||||
reexports::{
|
||||
calloop::{LoopHandle, LoopSignal},
|
||||
wayland_server::{
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::wl_shm,
|
||||
Display, DisplayHandle,
|
||||
},
|
||||
},
|
||||
utils::{Buffer, Size},
|
||||
utils::{Clock, Monotonic},
|
||||
wayland::{
|
||||
compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState,
|
||||
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState,
|
||||
primary_selection::PrimarySelectionState, shm::ShmState, viewporter::ViewporterState,
|
||||
presentation::PresentationState, primary_selection::PrimarySelectionState, shm::ShmState,
|
||||
viewporter::ViewporterState,
|
||||
},
|
||||
};
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::OsString,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
time::Instant,
|
||||
};
|
||||
#[cfg(feature = "debug")]
|
||||
use std::{collections::VecDeque, time::Duration};
|
||||
use std::{cell::RefCell, ffi::OsString, time::Duration};
|
||||
use std::{collections::VecDeque, time::Instant};
|
||||
|
||||
pub struct ClientState {
|
||||
pub workspace_client_state: WorkspaceClientState,
|
||||
|
|
@ -69,12 +78,11 @@ pub struct Common {
|
|||
|
||||
//pub output_conf: ConfigurationManager,
|
||||
pub shell: Shell,
|
||||
pub dirty_flag: Arc<AtomicBool>,
|
||||
|
||||
pub seats: Vec<Seat<State>>,
|
||||
pub last_active_seat: Seat<State>,
|
||||
seats: Vec<Seat<State>>,
|
||||
last_active_seat: Option<Seat<State>>,
|
||||
|
||||
pub start_time: Instant,
|
||||
pub clock: Clock<Monotonic>,
|
||||
pub should_stop: bool,
|
||||
|
||||
pub log: LogState,
|
||||
|
|
@ -85,34 +93,18 @@ pub struct Common {
|
|||
pub compositor_state: CompositorState,
|
||||
pub data_device_state: DataDeviceState,
|
||||
pub dmabuf_state: DmabufState,
|
||||
pub export_dmabuf_state: ExportDmabufState,
|
||||
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
|
||||
pub output_state: OutputManagerState,
|
||||
pub output_configuration_state: OutputConfigurationState<State>,
|
||||
pub presentation_state: PresentationState,
|
||||
pub primary_selection_state: PrimarySelectionState,
|
||||
pub screencopy_state: ScreencopyState,
|
||||
pub seat_state: SeatState<State>,
|
||||
pub shm_state: ShmState,
|
||||
pub wl_drm_state: WlDrmState,
|
||||
pub viewporter_state: ViewporterState,
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub struct Egui {
|
||||
pub debug_state: smithay_egui::EguiState,
|
||||
pub log_state: smithay_egui::EguiState,
|
||||
pub modifiers: smithay::wayland::seat::ModifiersState,
|
||||
pub active: bool,
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub struct Fps {
|
||||
pub state: smithay_egui::EguiState,
|
||||
pub modifiers: smithay::wayland::seat::ModifiersState,
|
||||
pub frames: VecDeque<(Instant, Duration)>,
|
||||
pub start: Instant,
|
||||
}
|
||||
|
||||
pub enum BackendData {
|
||||
X11(X11State),
|
||||
Winit(WinitState),
|
||||
|
|
@ -149,11 +141,12 @@ impl BackendData {
|
|||
output: &Output,
|
||||
test_only: bool,
|
||||
shell: &mut Shell,
|
||||
seats: impl Iterator<Item = Seat<State>>,
|
||||
loop_handle: &LoopHandle<'_, Data>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let result = match self {
|
||||
BackendData::Kms(ref mut state) => {
|
||||
state.apply_config_for_output(output, shell, test_only, loop_handle)
|
||||
state.apply_config_for_output(output, seats, shell, test_only, loop_handle)
|
||||
}
|
||||
BackendData::Winit(ref mut state) => state.apply_config_for_output(output, test_only),
|
||||
BackendData::X11(ref mut state) => state.apply_config_for_output(output, test_only),
|
||||
|
|
@ -187,102 +180,25 @@ impl BackendData {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn schedule_render(&mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output) {
|
||||
pub fn schedule_render(
|
||||
&mut self,
|
||||
loop_handle: &LoopHandle<'_, Data>,
|
||||
output: &Output,
|
||||
screencopy: Option<Vec<(ScreencopySession, BufferParams)>>,
|
||||
) {
|
||||
match self {
|
||||
BackendData::Winit(_) => {} // We cannot do this on the winit backend.
|
||||
BackendData::Winit(ref mut state) => state.pending_screencopy(screencopy), // We cannot do this on the winit backend.
|
||||
// Winit has a very strict render-loop and skipping frames breaks atleast the wayland winit-backend.
|
||||
// Swapping with damage (which should be empty on these frames) is likely good enough anyway.
|
||||
BackendData::X11(ref mut state) => state.schedule_render(output),
|
||||
BackendData::X11(ref mut state) => state.schedule_render(output, screencopy),
|
||||
BackendData::Kms(ref mut state) => {
|
||||
if let Err(err) = state.schedule_render(loop_handle, output) {
|
||||
if let Err(err) = state.schedule_render(loop_handle, output, None, screencopy) {
|
||||
slog_scope::crit!("Failed to schedule event, are we shutting down? {:?}", err);
|
||||
}
|
||||
}
|
||||
_ => unreachable!("No backend was initialized"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offscreen_for_output(
|
||||
&mut self,
|
||||
output: &Output,
|
||||
state: &mut Common,
|
||||
) -> anyhow::Result<(Vec<u8>, Size<i32, Buffer>)> {
|
||||
use crate::backend::render::{render_output, AsGles2Renderer, CustomElem};
|
||||
use anyhow::Context;
|
||||
use smithay::backend::renderer::{ImportAll, Renderer};
|
||||
use smithay::desktop::space::RenderElement;
|
||||
use smithay::{
|
||||
backend::{
|
||||
drm::NodeType,
|
||||
renderer::{gles2::Gles2Renderbuffer, Bind, ExportMem, Offscreen},
|
||||
},
|
||||
utils::Rectangle,
|
||||
};
|
||||
|
||||
fn capture<E, T, R>(
|
||||
gpu: Option<DrmNode>,
|
||||
renderer: &mut R,
|
||||
output: &Output,
|
||||
state: &mut Common,
|
||||
) -> anyhow::Result<(Vec<u8>, Size<i32, Buffer>)>
|
||||
where
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
T: Clone + 'static,
|
||||
R: Renderer<Error = E, TextureId = T>
|
||||
+ ImportAll
|
||||
+ AsGles2Renderer
|
||||
+ Offscreen<Gles2Renderbuffer>
|
||||
+ Bind<Gles2Renderbuffer>
|
||||
+ ExportMem,
|
||||
CustomElem: RenderElement<R>,
|
||||
{
|
||||
let size = output
|
||||
.geometry()
|
||||
.size
|
||||
.to_f64()
|
||||
.to_buffer(
|
||||
output.current_scale().fractional_scale(),
|
||||
output.current_transform().into(),
|
||||
)
|
||||
.to_i32_round();
|
||||
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)?;
|
||||
renderer.bind(buffer)?;
|
||||
render_output(
|
||||
gpu.as_ref(),
|
||||
renderer,
|
||||
0,
|
||||
state,
|
||||
output,
|
||||
false,
|
||||
#[cfg(feature = "debug")]
|
||||
None,
|
||||
)
|
||||
.map_err(|err| anyhow::anyhow!("Failed to render output: {:?}", err))?; // lifetime issue, grrr
|
||||
let mapping = renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), size))?;
|
||||
let data = Vec::from(renderer.map_texture(&mapping)?);
|
||||
|
||||
Ok((data, size))
|
||||
}
|
||||
|
||||
match self {
|
||||
BackendData::Winit(winit) => capture(None, winit.backend.renderer(), output, state),
|
||||
BackendData::X11(x11) => capture(None, &mut x11.renderer, output, state),
|
||||
BackendData::Kms(kms) => {
|
||||
let node = kms
|
||||
.target_node_for_output(output)
|
||||
.unwrap_or(kms.primary)
|
||||
.node_with_type(NodeType::Render)
|
||||
.with_context(|| "Unable to find node")??;
|
||||
capture(
|
||||
Some(node),
|
||||
&mut kms.api.renderer::<Gles2Renderbuffer>(&node, &node)?,
|
||||
output,
|
||||
state,
|
||||
)
|
||||
}
|
||||
BackendData::Unset => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
@ -293,31 +209,31 @@ impl State {
|
|||
signal: LoopSignal,
|
||||
log: LogState,
|
||||
) -> State {
|
||||
let clock = Clock::new().expect("Failed to initialize clock");
|
||||
let config = Config::load();
|
||||
let compositor_state = CompositorState::new::<Self, _>(dh, None);
|
||||
let data_device_state = DataDeviceState::new::<Self, _>(dh, None);
|
||||
let dmabuf_state = DmabufState::new();
|
||||
let export_dmabuf_state = ExportDmabufState::new::<Self, _>(
|
||||
dh,
|
||||
//|client| client.get_data::<ClientState>().unwrap().privileged,
|
||||
|_| true,
|
||||
);
|
||||
let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::<Self>(dh);
|
||||
let output_state = OutputManagerState::new_with_xdg_output::<Self>(dh);
|
||||
let output_configuration_state = OutputConfigurationState::new(dh, |_| true);
|
||||
let presentation_state = PresentationState::new::<Self>(dh, clock.id() as u32);
|
||||
let primary_selection_state = PrimarySelectionState::new::<Self, _>(dh, None);
|
||||
let shm_state = ShmState::new::<Self, _>(dh, vec![], None);
|
||||
let mut seat_state = SeatState::<Self>::new();
|
||||
let screencopy_state = ScreencopyState::new::<Self, _, _>(
|
||||
dh,
|
||||
vec![CursorMode::Embedded, CursorMode::Hidden],
|
||||
|_| true,
|
||||
); // TODO: privileged
|
||||
let shm_state = ShmState::new::<Self, _>(
|
||||
dh,
|
||||
vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888],
|
||||
None,
|
||||
);
|
||||
let seat_state = SeatState::<Self>::new();
|
||||
let viewporter_state = ViewporterState::new::<Self, _>(dh, None);
|
||||
let wl_drm_state = WlDrmState;
|
||||
|
||||
let shell = Shell::new(&config, dh);
|
||||
let initial_seat = crate::input::add_seat(dh, &mut seat_state, &config, "seat-0".into());
|
||||
|
||||
#[cfg(not(feature = "debug"))]
|
||||
let dirty_flag = Arc::new(AtomicBool::new(false));
|
||||
#[cfg(feature = "debug")]
|
||||
let dirty_flag = log.dirty_flag.clone();
|
||||
|
||||
State {
|
||||
common: Common {
|
||||
|
|
@ -328,38 +244,27 @@ impl State {
|
|||
event_loop_signal: signal,
|
||||
|
||||
shell,
|
||||
dirty_flag,
|
||||
|
||||
seats: vec![initial_seat.clone()],
|
||||
last_active_seat: initial_seat,
|
||||
seats: Vec::new(),
|
||||
last_active_seat: None,
|
||||
|
||||
start_time: Instant::now(),
|
||||
clock,
|
||||
should_stop: false,
|
||||
|
||||
log,
|
||||
#[cfg(feature = "debug")]
|
||||
egui: Egui {
|
||||
debug_state: smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous),
|
||||
log_state: {
|
||||
let mut state =
|
||||
smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous);
|
||||
state.set_zindex(0);
|
||||
state
|
||||
},
|
||||
modifiers: Default::default(),
|
||||
active: false,
|
||||
alpha: 1.0,
|
||||
},
|
||||
egui: Egui { active: false },
|
||||
|
||||
compositor_state,
|
||||
data_device_state,
|
||||
dmabuf_state,
|
||||
export_dmabuf_state,
|
||||
screencopy_state,
|
||||
shm_state,
|
||||
seat_state,
|
||||
keyboard_shortcuts_inhibit_state,
|
||||
output_state,
|
||||
output_configuration_state,
|
||||
presentation_state,
|
||||
primary_selection_state,
|
||||
viewporter_state,
|
||||
wl_drm_state,
|
||||
|
|
@ -376,10 +281,9 @@ impl State {
|
|||
match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) {
|
||||
Ok(val) if val == "y" || val == "yes" || val == "true" => Some(
|
||||
kms_state
|
||||
.target_node_for_output(&active_output(
|
||||
&self.common.last_active_seat,
|
||||
&self.common,
|
||||
))
|
||||
.target_node_for_output(
|
||||
&self.common.last_active_seat().active_output(),
|
||||
)
|
||||
.unwrap_or(kms_state.primary),
|
||||
),
|
||||
_ => Some(kms_state.primary),
|
||||
|
|
@ -415,49 +319,275 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl Fps {
|
||||
const WINDOW_SIZE: usize = 100;
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.start = Instant::now();
|
||||
impl Common {
|
||||
pub fn add_seat(&mut self, seat: Seat<State>) {
|
||||
if self.seats.is_empty() {
|
||||
self.last_active_seat = Some(seat.clone());
|
||||
}
|
||||
self.seats.push(seat);
|
||||
}
|
||||
|
||||
pub fn end(&mut self) {
|
||||
let frame_time = Instant::now().duration_since(self.start);
|
||||
|
||||
self.frames.push_back((self.start, frame_time));
|
||||
if self.frames.len() > Fps::WINDOW_SIZE {
|
||||
self.frames.pop_front();
|
||||
pub fn remove_seat(&mut self, seat: &Seat<State>) {
|
||||
self.seats.retain(|s| s != seat);
|
||||
if self.seats.is_empty() {
|
||||
self.last_active_seat = None;
|
||||
} else if self.last_active_seat() == seat {
|
||||
self.last_active_seat = Some(self.seats[0].clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_frametime(&self) -> &Duration {
|
||||
self.frames
|
||||
.iter()
|
||||
.map(|(_, f)| f)
|
||||
.max()
|
||||
.unwrap_or(&Duration::ZERO)
|
||||
pub fn seats(&self) -> impl Iterator<Item = &Seat<State>> {
|
||||
self.seats.iter()
|
||||
}
|
||||
|
||||
pub fn min_frametime(&self) -> &Duration {
|
||||
pub fn last_active_seat(&self) -> &Seat<State> {
|
||||
self.last_active_seat.as_ref().expect("No seat?")
|
||||
}
|
||||
|
||||
pub fn send_frames(&self, output: &Output, render_element_states: &RenderElementStates) {
|
||||
let time = self.clock.now();
|
||||
let throttle = Some(Duration::from_secs(1));
|
||||
|
||||
let active = self.shell.active_space(output);
|
||||
active.mapped().for_each(|mapped| {
|
||||
if active.outputs_for_element(mapped).any(|o| &o == output) {
|
||||
let window = mapped.active_window();
|
||||
window.with_surfaces(|surface, states| {
|
||||
update_surface_primary_scanout_output(
|
||||
surface,
|
||||
output,
|
||||
states,
|
||||
render_element_states,
|
||||
default_primary_scanout_output_compare,
|
||||
)
|
||||
});
|
||||
window.send_frame(output, time, throttle, surface_primary_scanout_output);
|
||||
}
|
||||
});
|
||||
|
||||
for space in self
|
||||
.shell
|
||||
.workspaces
|
||||
.spaces()
|
||||
.filter(|w| w.handle != active.handle)
|
||||
{
|
||||
space.mapped().for_each(|mapped| {
|
||||
if space.outputs_for_element(mapped).any(|o| &o == output) {
|
||||
let window = mapped.active_window();
|
||||
window.send_frame(output, time, throttle, |_, _| None);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let map = smithay::desktop::layer_map_for_output(output);
|
||||
for layer_surface in map.layers() {
|
||||
layer_surface.with_surfaces(|surface, states| {
|
||||
update_surface_primary_scanout_output(
|
||||
surface,
|
||||
output,
|
||||
states,
|
||||
render_element_states,
|
||||
default_primary_scanout_output_compare,
|
||||
)
|
||||
});
|
||||
layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_presentation_feedback(
|
||||
&self,
|
||||
output: &Output,
|
||||
render_element_states: &RenderElementStates,
|
||||
) -> OutputPresentationFeedback {
|
||||
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
|
||||
|
||||
let active = self.shell.active_space(output);
|
||||
active.mapped().for_each(|mapped| {
|
||||
mapped.active_window().take_presentation_feedback(
|
||||
&mut output_presentation_feedback,
|
||||
surface_primary_scanout_output,
|
||||
|surface, _| {
|
||||
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let map = smithay::desktop::layer_map_for_output(output);
|
||||
for layer_surface in map.layers() {
|
||||
layer_surface.take_presentation_feedback(
|
||||
&mut output_presentation_feedback,
|
||||
surface_primary_scanout_output,
|
||||
|surface, _| {
|
||||
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
output_presentation_feedback
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub struct Egui {
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
pub struct Fps {
|
||||
#[cfg(feature = "debug")]
|
||||
pub state: smithay_egui::EguiState,
|
||||
pending_frame: Option<PendingFrame>,
|
||||
pub frames: VecDeque<Frame>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PendingFrame {
|
||||
start: Instant,
|
||||
duration_elements: Option<Duration>,
|
||||
duration_render: Option<Duration>,
|
||||
duration_screencopy: Option<Duration>,
|
||||
duration_displayed: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Frame {
|
||||
pub start: Instant,
|
||||
pub duration_elements: Duration,
|
||||
pub duration_render: Duration,
|
||||
pub duration_screencopy: Option<Duration>,
|
||||
pub duration_displayed: Duration,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
fn render_time(&self) -> Duration {
|
||||
self.duration_elements + self.duration_render
|
||||
}
|
||||
|
||||
fn frame_time(&self) -> Duration {
|
||||
self.duration_elements
|
||||
+ self.duration_render
|
||||
+ self.duration_screencopy.clone().unwrap_or(Duration::ZERO)
|
||||
}
|
||||
|
||||
fn time_to_display(&self) -> Duration {
|
||||
self.duration_elements
|
||||
+ self.duration_render
|
||||
+ self.duration_screencopy.clone().unwrap_or(Duration::ZERO)
|
||||
+ self.duration_displayed
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PendingFrame> for Frame {
|
||||
fn from(pending: PendingFrame) -> Self {
|
||||
Frame {
|
||||
start: pending.start,
|
||||
duration_elements: pending.duration_elements.unwrap_or(Duration::ZERO),
|
||||
duration_render: pending.duration_render.unwrap_or(Duration::ZERO),
|
||||
duration_screencopy: pending.duration_screencopy,
|
||||
duration_displayed: pending.duration_displayed.unwrap_or(Duration::ZERO),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fps {
|
||||
const WINDOW_SIZE: usize = 360;
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.pending_frame = Some(PendingFrame {
|
||||
start: Instant::now(),
|
||||
duration_elements: None,
|
||||
duration_render: None,
|
||||
duration_screencopy: None,
|
||||
duration_displayed: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn elements(&mut self) {
|
||||
if let Some(frame) = self.pending_frame.as_mut() {
|
||||
frame.duration_elements = Some(Instant::now().duration_since(frame.start));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self) {
|
||||
if let Some(frame) = self.pending_frame.as_mut() {
|
||||
frame.duration_render = Some(
|
||||
Instant::now().duration_since(frame.start)
|
||||
- frame.duration_elements.clone().unwrap_or(Duration::ZERO),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screencopy(&mut self) {
|
||||
if let Some(frame) = self.pending_frame.as_mut() {
|
||||
frame.duration_screencopy = Some(
|
||||
Instant::now().duration_since(frame.start)
|
||||
- frame.duration_elements.clone().unwrap_or(Duration::ZERO)
|
||||
- frame.duration_render.clone().unwrap_or(Duration::ZERO),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn displayed(&mut self) {
|
||||
if let Some(mut frame) = self.pending_frame.take() {
|
||||
frame.duration_displayed = Some(
|
||||
Instant::now().duration_since(frame.start)
|
||||
- frame.duration_elements.clone().unwrap_or(Duration::ZERO)
|
||||
- frame.duration_render.clone().unwrap_or(Duration::ZERO)
|
||||
- frame.duration_screencopy.clone().unwrap_or(Duration::ZERO),
|
||||
);
|
||||
|
||||
self.frames.push_back(frame.into());
|
||||
while self.frames.len() > Fps::WINDOW_SIZE {
|
||||
self.frames.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_frametime(&self) -> Duration {
|
||||
self.frames
|
||||
.iter()
|
||||
.map(|(_, f)| f)
|
||||
.map(|f| f.frame_time())
|
||||
.max()
|
||||
.unwrap_or(Duration::ZERO)
|
||||
}
|
||||
|
||||
pub fn min_frametime(&self) -> Duration {
|
||||
self.frames
|
||||
.iter()
|
||||
.map(|f| f.frame_time())
|
||||
.min()
|
||||
.unwrap_or(&Duration::ZERO)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
}
|
||||
|
||||
pub fn max_time_to_display(&self) -> Duration {
|
||||
self.frames
|
||||
.iter()
|
||||
.map(|f| f.time_to_display())
|
||||
.max()
|
||||
.unwrap_or(Duration::ZERO)
|
||||
}
|
||||
|
||||
pub fn min_time_to_display(&self) -> Duration {
|
||||
self.frames
|
||||
.iter()
|
||||
.map(|f| f.time_to_display())
|
||||
.min()
|
||||
.unwrap_or(Duration::ZERO)
|
||||
}
|
||||
|
||||
pub fn avg_frametime(&self) -> Duration {
|
||||
if self.frames.is_empty() {
|
||||
return Duration::ZERO;
|
||||
}
|
||||
self.frames.iter().map(|f| f.frame_time()).sum::<Duration>() / (self.frames.len() as u32)
|
||||
}
|
||||
|
||||
pub fn avg_rendertime(&self, window: usize) -> Duration {
|
||||
self.frames
|
||||
.iter()
|
||||
.map(|(_, f)| f)
|
||||
.cloned()
|
||||
.take(window)
|
||||
.map(|f| f.render_time())
|
||||
.sum::<Duration>()
|
||||
/ (self.frames.len() as u32)
|
||||
/ window as u32
|
||||
}
|
||||
|
||||
pub fn avg_fps(&self) -> f64 {
|
||||
|
|
@ -465,7 +595,9 @@ impl Fps {
|
|||
return 0.0;
|
||||
}
|
||||
let secs = match (self.frames.front(), self.frames.back()) {
|
||||
(Some((start, _)), Some((end, dur))) => end.duration_since(*start) + *dur,
|
||||
(Some(Frame { start, .. }), Some(end_frame)) => {
|
||||
end_frame.start.duration_since(*start) + end_frame.frame_time()
|
||||
}
|
||||
_ => Duration::ZERO,
|
||||
}
|
||||
.as_secs_f64();
|
||||
|
|
@ -474,25 +606,44 @@ impl Fps {
|
|||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl Default for Fps {
|
||||
fn default() -> Fps {
|
||||
static INTEL_LOGO: &'static [u8] = include_bytes!("../resources/icons/intel.svg");
|
||||
#[cfg(feature = "debug")]
|
||||
static AMD_LOGO: &'static [u8] = include_bytes!("../resources/icons/amd.svg");
|
||||
#[cfg(feature = "debug")]
|
||||
static NVIDIA_LOGO: &'static [u8] = include_bytes!("../resources/icons/nvidia.svg");
|
||||
|
||||
impl Fps {
|
||||
pub fn new(_renderer: &mut GlowRenderer) -> Fps {
|
||||
#[cfg(feature = "debug")]
|
||||
let state = {
|
||||
let state = smithay_egui::EguiState::new(smithay::utils::Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
(400, 800),
|
||||
));
|
||||
let mut visuals: egui::style::Visuals = Default::default();
|
||||
visuals.window_shadow.extrusion = 0.0;
|
||||
state.context().set_visuals(visuals);
|
||||
state
|
||||
.load_svg(_renderer, String::from("intel"), INTEL_LOGO)
|
||||
.unwrap();
|
||||
state
|
||||
.load_svg(_renderer, String::from("amd"), AMD_LOGO)
|
||||
.unwrap();
|
||||
state
|
||||
.load_svg(_renderer, String::from("nvidia"), NVIDIA_LOGO)
|
||||
.unwrap();
|
||||
state
|
||||
};
|
||||
|
||||
Fps {
|
||||
state: {
|
||||
let mut state = smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous);
|
||||
let mut visuals: egui::style::Visuals = Default::default();
|
||||
visuals.window_shadow.extrusion = 0.0;
|
||||
state.context().set_visuals(visuals);
|
||||
state.set_zindex(110); // always render on top
|
||||
state
|
||||
},
|
||||
modifiers: Default::default(),
|
||||
#[cfg(feature = "debug")]
|
||||
state,
|
||||
pending_frame: None,
|
||||
frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1),
|
||||
start: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn avg_fps<'a>(iter: impl Iterator<Item = &'a Duration>) -> f64 {
|
||||
let sum_secs = iter.map(|d| d.as_secs_f64()).sum::<f64>();
|
||||
1.0 / (sum_secs / Fps::WINDOW_SIZE as f64)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
use crate::input::{ActiveOutput, SeatId};
|
||||
use smithay::{
|
||||
input::Seat,
|
||||
output::Output,
|
||||
utils::{Logical, Rectangle, Transform},
|
||||
use std::{cell::RefCell, sync::Mutex, time::Duration};
|
||||
|
||||
use crate::{
|
||||
backend::render::cursor::CursorState,
|
||||
input::{ActiveOutput, SeatId},
|
||||
};
|
||||
use smithay::{
|
||||
desktop::utils::bbox_from_surface_tree,
|
||||
input::{
|
||||
pointer::{CursorImageAttributes, CursorImageStatus},
|
||||
Seat,
|
||||
},
|
||||
output::Output,
|
||||
utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Time, Transform},
|
||||
wayland::compositor::with_states,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub use crate::shell::{Shell, Workspace};
|
||||
pub use crate::state::{Common, State};
|
||||
|
|
@ -32,38 +41,93 @@ impl OutputExt for Output {
|
|||
|
||||
pub trait SeatExt {
|
||||
fn id(&self) -> usize;
|
||||
|
||||
fn active_output(&self) -> Output;
|
||||
fn set_active_output(&self, output: &Output);
|
||||
fn cursor_geometry(
|
||||
&self,
|
||||
loc: impl Into<Point<f64, Buffer>>,
|
||||
time: Time<Monotonic>,
|
||||
) -> Option<(Rectangle<i32, Buffer>, Point<i32, Buffer>)>;
|
||||
}
|
||||
|
||||
impl SeatExt for Seat<State> {
|
||||
fn id(&self) -> usize {
|
||||
self.user_data().get::<SeatId>().unwrap().0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active_output(seat: &Seat<State>, state: &Common) -> Output {
|
||||
seat.user_data()
|
||||
.get::<ActiveOutput>()
|
||||
.map(|x| x.0.borrow().clone())
|
||||
.unwrap_or_else(|| {
|
||||
state
|
||||
.shell
|
||||
.outputs()
|
||||
.next()
|
||||
.cloned()
|
||||
.expect("Backend has no outputs?")
|
||||
})
|
||||
}
|
||||
fn active_output(&self) -> Output {
|
||||
self.user_data()
|
||||
.get::<ActiveOutput>()
|
||||
.map(|x| x.0.borrow().clone())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn set_active_output(seat: &Seat<State>, output: &Output) {
|
||||
if !seat
|
||||
.user_data()
|
||||
.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone())))
|
||||
{
|
||||
*seat
|
||||
fn set_active_output(&self, output: &Output) {
|
||||
*self
|
||||
.user_data()
|
||||
.get::<ActiveOutput>()
|
||||
.unwrap()
|
||||
.0
|
||||
.borrow_mut() = output.clone();
|
||||
}
|
||||
|
||||
fn cursor_geometry(
|
||||
&self,
|
||||
loc: impl Into<Point<f64, Buffer>>,
|
||||
time: Time<Monotonic>,
|
||||
) -> Option<(Rectangle<i32, Buffer>, Point<i32, Buffer>)> {
|
||||
let location = loc.into().to_i32_round();
|
||||
|
||||
let cursor_status = self
|
||||
.user_data()
|
||||
.get::<RefCell<CursorImageStatus>>()
|
||||
.map(|cell| {
|
||||
let mut cursor_status = cell.borrow_mut();
|
||||
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
|
||||
if !surface.alive() {
|
||||
*cursor_status = CursorImageStatus::Default;
|
||||
}
|
||||
}
|
||||
cursor_status.clone()
|
||||
})
|
||||
.unwrap_or(CursorImageStatus::Default);
|
||||
|
||||
match cursor_status {
|
||||
CursorImageStatus::Surface(surface) => {
|
||||
let hotspot = with_states(&surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
});
|
||||
let geo = bbox_from_surface_tree(&surface, (location.x, location.y));
|
||||
let buffer_geo = Rectangle::from_loc_and_size(
|
||||
(geo.loc.x, geo.loc.y),
|
||||
geo.size.to_buffer(1, Transform::Normal),
|
||||
);
|
||||
Some((buffer_geo, (hotspot.x, hotspot.y).into()))
|
||||
}
|
||||
CursorImageStatus::Default => {
|
||||
let seat_userdata = self.user_data();
|
||||
seat_userdata.insert_if_missing(CursorState::default);
|
||||
let state = seat_userdata.get::<CursorState>().unwrap();
|
||||
let frame = state
|
||||
.cursor
|
||||
.get_image(1, Into::<Duration>::into(time).as_millis() as u32);
|
||||
|
||||
Some((
|
||||
Rectangle::from_loc_and_size(
|
||||
location,
|
||||
(frame.width as i32, frame.height as i32),
|
||||
),
|
||||
(frame.xhot as i32, frame.yhot as i32).into(),
|
||||
))
|
||||
}
|
||||
CursorImageStatus::Hidden => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{state::BackendData, utils::prelude::*};
|
||||
use crate::{state::BackendData, utils::prelude::*, wayland::protocols::screencopy::SessionType};
|
||||
use smithay::{
|
||||
backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state},
|
||||
delegate_compositor,
|
||||
|
|
@ -18,11 +18,13 @@ use smithay::{
|
|||
};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::screencopy::PendingScreencopyBuffers;
|
||||
|
||||
impl State {
|
||||
fn early_import_surface(&mut self, surface: &WlSurface) {
|
||||
let mut import_nodes = std::collections::HashSet::new();
|
||||
let dh = &self.common.display_handle;
|
||||
for output in self.common.shell.outputs_for_surface(&surface) {
|
||||
for output in self.common.shell.visible_outputs_for_surface(&surface) {
|
||||
if let BackendData::Kms(ref mut kms_state) = &mut self.backend {
|
||||
if let Some(target) = kms_state.target_node_for_output(&output) {
|
||||
if import_nodes.insert(target) {
|
||||
|
|
@ -90,7 +92,7 @@ impl State {
|
|||
if !initial_configure_sent {
|
||||
// compute initial dimensions by mapping
|
||||
Shell::map_layer(self, &surface);
|
||||
// this will also send a configure
|
||||
surface.layer_surface().send_configure();
|
||||
}
|
||||
initial_configure_sent
|
||||
}
|
||||
|
|
@ -121,7 +123,7 @@ impl CompositorHandler for State {
|
|||
state.wl_buffer().is_some()
|
||||
})
|
||||
{
|
||||
let output = active_output(&seat, &self.common);
|
||||
let output = seat.active_output();
|
||||
Shell::map_window(self, &window, &output);
|
||||
} else {
|
||||
return;
|
||||
|
|
@ -152,46 +154,25 @@ impl CompositorHandler for State {
|
|||
// If we would re-position the window inside the grab we would get a weird jittery animation.
|
||||
// We only want to resize once the client has acknoledged & commited the new size,
|
||||
// so we need to carefully track the state through different handlers.
|
||||
if let Some((space, window)) =
|
||||
self.common
|
||||
.shell
|
||||
.space_for_window_mut(surface)
|
||||
.and_then(|workspace| {
|
||||
workspace
|
||||
.space
|
||||
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.cloned()
|
||||
.map(|window| (&mut workspace.space, window))
|
||||
})
|
||||
{
|
||||
let new_location =
|
||||
crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_state(
|
||||
&window,
|
||||
space.window_location(&window).unwrap(),
|
||||
window.geometry().size,
|
||||
if let Some(element) = self.common.shell.element_for_surface(surface).cloned() {
|
||||
if let Some(workspace) = self.common.shell.space_for_mut(&element) {
|
||||
crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_to_location(
|
||||
element.clone(),
|
||||
workspace,
|
||||
);
|
||||
if let Some(location) = new_location {
|
||||
space.map_window(
|
||||
&window,
|
||||
location,
|
||||
crate::shell::layout::floating::FLOATING_INDEX,
|
||||
true,
|
||||
);
|
||||
for window in space.windows() {
|
||||
update_reactive_popups(space, window);
|
||||
}
|
||||
workspace.commit(surface);
|
||||
}
|
||||
}
|
||||
|
||||
//handle window screencopy sessions
|
||||
self.schedule_window_session(surface);
|
||||
|
||||
// We need to know every potential output for importing to the right gpu and scheduling a render,
|
||||
// so call this only after every potential surface map operation has been done.
|
||||
self.early_import_surface(surface);
|
||||
|
||||
// and refresh smithays internal state
|
||||
self.common.shell.popups.commit(surface);
|
||||
for workspace in &self.common.shell.spaces {
|
||||
workspace.space.commit(surface);
|
||||
}
|
||||
|
||||
// re-arrange layer-surfaces (commits may change size and positioning)
|
||||
if let Some(output) = self.common.shell.outputs().find(|o| {
|
||||
|
|
@ -199,14 +180,37 @@ impl CompositorHandler for State {
|
|||
map.layer_for_surface(surface, WindowSurfaceType::ALL)
|
||||
.is_some()
|
||||
}) {
|
||||
let dh = &self.common.display_handle;
|
||||
layer_map_for_output(output).arrange(dh);
|
||||
layer_map_for_output(output).arrange();
|
||||
}
|
||||
|
||||
let mut scheduled_sessions = self.schedule_workspace_sessions(surface);
|
||||
|
||||
// schedule a new render
|
||||
for output in self.common.shell.outputs_for_surface(surface) {
|
||||
self.backend
|
||||
.schedule_render(&self.common.event_loop_handle, &output);
|
||||
for output in self.common.shell.visible_outputs_for_surface(surface) {
|
||||
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>() {
|
||||
scheduled_sessions
|
||||
.get_or_insert_with(Vec::new)
|
||||
.extend(sessions.borrow_mut().drain(..));
|
||||
}
|
||||
|
||||
self.backend.schedule_render(
|
||||
&self.common.event_loop_handle,
|
||||
&output,
|
||||
scheduled_sessions.as_ref().map(|sessions| {
|
||||
sessions
|
||||
.iter()
|
||||
.filter(|(s, _)| match s.session_type() {
|
||||
SessionType::Output(o) | SessionType::Workspace(o, _)
|
||||
if o == output =>
|
||||
{
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
use crate::utils::prelude::*;
|
||||
use smithay::{
|
||||
delegate_layer_shell,
|
||||
desktop::{LayerSurface, PopupKind},
|
||||
desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurfaceType},
|
||||
output::Output,
|
||||
reexports::wayland_server::protocol::wl_output::WlOutput,
|
||||
wayland::shell::{
|
||||
|
|
@ -14,6 +14,8 @@ use smithay::{
|
|||
},
|
||||
};
|
||||
|
||||
use super::screencopy::PendingScreencopyBuffers;
|
||||
|
||||
impl WlrLayerShellHandler for State {
|
||||
fn shell_state(&mut self) -> &mut WlrLayerShellState {
|
||||
&mut self.common.shell.layer_shell_state
|
||||
|
|
@ -26,12 +28,11 @@ impl WlrLayerShellHandler for State {
|
|||
_layer: Layer,
|
||||
namespace: String,
|
||||
) {
|
||||
super::mark_dirty_on_drop(&self.common, surface.wl_surface());
|
||||
let seat = self.common.last_active_seat.clone();
|
||||
let seat = self.common.last_active_seat().clone();
|
||||
let output = wl_output
|
||||
.as_ref()
|
||||
.and_then(Output::from_resource)
|
||||
.unwrap_or_else(|| active_output(&seat, &self.common));
|
||||
.unwrap_or_else(|| seat.active_output());
|
||||
self.common.shell.pending_layers.push((
|
||||
LayerSurface::new(surface, namespace),
|
||||
output,
|
||||
|
|
@ -51,6 +52,44 @@ impl WlrLayerShellHandler for State {
|
|||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn layer_destroyed(&mut self, surface: WlrLayerSurface) {
|
||||
let maybe_output = self
|
||||
.common
|
||||
.shell
|
||||
.outputs()
|
||||
.find(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
|
||||
.is_some()
|
||||
})
|
||||
.cloned();
|
||||
|
||||
if let Some(output) = maybe_output {
|
||||
{
|
||||
let mut map = layer_map_for_output(&output);
|
||||
let layer = map
|
||||
.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap()
|
||||
.clone();
|
||||
map.unmap_layer(&layer);
|
||||
}
|
||||
|
||||
// collect screencopy sessions needing an update
|
||||
let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface());
|
||||
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>() {
|
||||
scheduled_sessions
|
||||
.get_or_insert_with(Vec::new)
|
||||
.extend(sessions.borrow_mut().drain(..));
|
||||
}
|
||||
|
||||
self.backend.schedule_render(
|
||||
&self.common.event_loop_handle,
|
||||
&output,
|
||||
scheduled_sessions,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_layer_shell!(State);
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ pub mod buffer;
|
|||
pub mod compositor;
|
||||
pub mod data_device;
|
||||
pub mod dmabuf;
|
||||
pub mod export_dmabuf;
|
||||
pub mod keyboard_shortcuts_inhibit;
|
||||
pub mod layer_shell;
|
||||
pub mod output;
|
||||
pub mod output_configuration;
|
||||
pub mod presentation;
|
||||
pub mod primary_selection;
|
||||
pub mod screencopy;
|
||||
pub mod seat;
|
||||
pub mod shm;
|
||||
pub mod toplevel_info;
|
||||
|
|
@ -18,28 +19,3 @@ pub mod viewporter;
|
|||
pub mod wl_drm;
|
||||
pub mod workspace;
|
||||
pub mod xdg_shell;
|
||||
|
||||
use crate::state::Common;
|
||||
use smithay::{
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
wayland::compositor::{add_destruction_hook, with_states},
|
||||
};
|
||||
|
||||
fn mark_dirty_on_drop(state: &Common, wl_surface: &WlSurface) {
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
let dirty = state.dirty_flag.clone();
|
||||
struct DirtyFlag(Arc<AtomicBool>);
|
||||
|
||||
with_states(wl_surface, |data| {
|
||||
data.data_map.insert_if_missing(|| DirtyFlag(dirty));
|
||||
});
|
||||
add_destruction_hook(wl_surface, |data| {
|
||||
if let Some(DirtyFlag(dirty)) = data.data_map.get::<DirtyFlag>() {
|
||||
dirty.store(true, Ordering::SeqCst);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,10 +81,12 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
let seats = self.common.seats().cloned().collect::<Vec<_>>();
|
||||
if let Err(err) = self.backend.apply_config_for_output(
|
||||
output,
|
||||
test_only,
|
||||
&mut self.common.shell,
|
||||
seats.iter().cloned(),
|
||||
&self.common.event_loop_handle,
|
||||
) {
|
||||
slog_scope::warn!(
|
||||
|
|
@ -106,6 +108,7 @@ impl State {
|
|||
output,
|
||||
false,
|
||||
&mut self.common.shell,
|
||||
seats.iter().cloned(),
|
||||
&self.common.event_loop_handle,
|
||||
) {
|
||||
slog_scope::error!(
|
||||
|
|
|
|||
6
src/wayland/handlers/presentation.rs
Normal file
6
src/wayland/handlers/presentation.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::state::State;
|
||||
use smithay::delegate_presentation;
|
||||
|
||||
delegate_presentation!(State);
|
||||
1052
src/wayland/handlers/screencopy.rs
Normal file
1052
src/wayland/handlers/screencopy.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::state::State;
|
||||
use crate::{
|
||||
shell::focus::target::{KeyboardFocusTarget, PointerFocusTarget},
|
||||
state::State,
|
||||
};
|
||||
use smithay::{
|
||||
delegate_seat,
|
||||
input::{pointer::CursorImageStatus, SeatHandler, SeatState},
|
||||
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource},
|
||||
wayland::{data_device::set_data_device_focus, primary_selection::set_primary_focus},
|
||||
reexports::wayland_server::Resource,
|
||||
wayland::{
|
||||
data_device::set_data_device_focus, primary_selection::set_primary_focus,
|
||||
seat::WaylandFocus,
|
||||
},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
impl SeatHandler for State {
|
||||
type KeyboardFocus = WlSurface;
|
||||
type PointerFocus = WlSurface;
|
||||
type KeyboardFocus = KeyboardFocusTarget;
|
||||
type PointerFocus = PointerFocusTarget;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
||||
&mut self.common.seat_state
|
||||
|
|
@ -35,10 +41,12 @@ impl SeatHandler for State {
|
|||
focused: Option<&Self::KeyboardFocus>,
|
||||
) {
|
||||
let dh = &self.common.display_handle;
|
||||
if let Some(client) = focused.and_then(|s| dh.get_client(s.id()).ok()) {
|
||||
set_data_device_focus(dh, seat, Some(client));
|
||||
let client2 = focused.and_then(|s| dh.get_client(s.id()).ok()).unwrap();
|
||||
set_primary_focus(dh, seat, Some(client2))
|
||||
if let Some(client) = focused
|
||||
.and_then(|t| t.wl_surface())
|
||||
.and_then(|s| dh.get_client(s.id()).ok())
|
||||
{
|
||||
set_data_device_focus(dh, seat, Some(client.clone()));
|
||||
set_primary_focus(dh, seat, Some(client))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,18 +19,35 @@ impl ToplevelManagementHandler for State {
|
|||
}
|
||||
|
||||
fn activate(&mut self, _dh: &DisplayHandle, window: &Window, seat: Option<Seat<Self>>) {
|
||||
if let Some(idx) = self
|
||||
for output in self
|
||||
.common
|
||||
.shell
|
||||
.space_for_window(window.toplevel().wl_surface())
|
||||
.map(|w| w.idx)
|
||||
.outputs()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.iter()
|
||||
{
|
||||
let seat = seat.unwrap_or(self.common.last_active_seat.clone());
|
||||
let output = active_output(&seat, &self.common);
|
||||
if self.common.shell.active_space(&output).idx != idx {
|
||||
self.common.shell.activate(&seat, &output, idx as usize);
|
||||
let maybe = self
|
||||
.common
|
||||
.shell
|
||||
.workspaces
|
||||
.spaces_for_output(output)
|
||||
.enumerate()
|
||||
.find(|(_, w)| w.windows().any(|w| &w == window));
|
||||
if let Some((idx, workspace)) = maybe {
|
||||
let seat = seat.unwrap_or(self.common.last_active_seat().clone());
|
||||
let mapped = workspace
|
||||
.mapped()
|
||||
.find(|m| m.windows().any(|(w, _)| &w == window))
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
std::mem::drop(workspace);
|
||||
self.common.shell.activate(&output, idx as usize);
|
||||
mapped.focus_window(window);
|
||||
Common::set_focus(self, Some(&mapped.clone().into()), &seat, None);
|
||||
return;
|
||||
}
|
||||
Common::set_focus(self, Some(window.toplevel().wl_surface()), &seat, None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
shell::WorkspaceMode,
|
||||
state::ClientState,
|
||||
utils::prelude::*,
|
||||
wayland::protocols::workspace::{
|
||||
|
|
@ -29,16 +30,22 @@ impl WorkspaceHandler for State {
|
|||
for request in requests.into_iter() {
|
||||
match request {
|
||||
Request::Activate(handle) => {
|
||||
if let Some(idx) = self
|
||||
.common
|
||||
.shell
|
||||
.spaces
|
||||
.iter()
|
||||
.position(|w| w.handle == handle)
|
||||
{
|
||||
let seat = &self.common.last_active_seat;
|
||||
let output = active_output(seat, &self.common);
|
||||
self.common.shell.activate(seat, &output, idx);
|
||||
let maybe = match &self.common.shell.workspaces {
|
||||
WorkspaceMode::Global(set) => set
|
||||
.workspaces
|
||||
.iter()
|
||||
.position(|w| w.handle == handle)
|
||||
.map(|i| (self.common.last_active_seat().active_output(), i)),
|
||||
WorkspaceMode::OutputBound(sets, _) => sets.iter().find_map(|(o, set)| {
|
||||
set.workspaces
|
||||
.iter()
|
||||
.position(|w| w.handle == handle)
|
||||
.map(|i| (o.clone(), i))
|
||||
}),
|
||||
};
|
||||
|
||||
if let Some((output, idx)) = maybe {
|
||||
self.common.shell.activate(&output, idx);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::utils::prelude::*;
|
||||
use crate::{utils::prelude::*, wayland::protocols::screencopy::SessionType};
|
||||
use smithay::{
|
||||
delegate_xdg_shell,
|
||||
desktop::{
|
||||
find_popup_root_surface, Kind, PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab,
|
||||
PopupUngrabStrategy, Window, WindowSurfaceType,
|
||||
PopupUngrabStrategy, Window,
|
||||
},
|
||||
input::{
|
||||
pointer::{Focus, GrabStartData as PointerGrabStartData},
|
||||
|
|
@ -17,12 +17,17 @@ use smithay::{
|
|||
wayland_server::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
|
||||
},
|
||||
utils::Serial,
|
||||
wayland::shell::xdg::{
|
||||
Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||
wayland::{
|
||||
seat::WaylandFocus,
|
||||
shell::xdg::{
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use std::cell::Cell;
|
||||
|
||||
use super::screencopy::PendingScreencopyBuffers;
|
||||
|
||||
pub mod popup;
|
||||
|
||||
pub type PopupGrabData = Cell<Option<PopupGrab<State>>>;
|
||||
|
|
@ -33,21 +38,14 @@ impl XdgShellHandler for State {
|
|||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
super::mark_dirty_on_drop(&self.common, surface.wl_surface());
|
||||
|
||||
let seat = &self.common.last_active_seat;
|
||||
let seat = self.common.last_active_seat().clone();
|
||||
let window = Window::new(Kind::Xdg(surface));
|
||||
self.common.shell.toplevel_info_state.new_toplevel(&window);
|
||||
self.common
|
||||
.shell
|
||||
.pending_windows
|
||||
.push((window, seat.clone()));
|
||||
self.common.shell.pending_windows.push((window, seat));
|
||||
// We will position the window after the first commit, when we know its size hints
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
|
||||
super::mark_dirty_on_drop(&self.common, surface.wl_surface());
|
||||
|
||||
surface.with_pending_state(|state| {
|
||||
state.geometry = positioner.get_geometry();
|
||||
state.positioner = positioner;
|
||||
|
|
@ -67,35 +65,19 @@ impl XdgShellHandler for State {
|
|||
}
|
||||
}
|
||||
|
||||
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
|
||||
if let Configure::Toplevel(configure) = configure {
|
||||
// If we would re-position the window inside the grab we would get a weird jittery animation.
|
||||
// We only want to resize once the client has acknoledged & commited the new size,
|
||||
// so we need to carefully track the state through different handlers.
|
||||
if let Some(window) =
|
||||
self.common
|
||||
.shell
|
||||
.space_for_window(&surface)
|
||||
.and_then(|workspace| {
|
||||
workspace
|
||||
.space
|
||||
.window_for_surface(&surface, WindowSurfaceType::TOPLEVEL)
|
||||
})
|
||||
{
|
||||
crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure(window, configure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) {
|
||||
let seat = Seat::from_resource(&seat).unwrap();
|
||||
let kind = PopupKind::Xdg(surface);
|
||||
if let Ok(root) = find_popup_root_surface(&kind) {
|
||||
if let Some(root) = find_popup_root_surface(&kind)
|
||||
.ok()
|
||||
.and_then(|root| self.common.shell.element_for_surface(&root))
|
||||
{
|
||||
let target = root.clone().into();
|
||||
let ret = self
|
||||
.common
|
||||
.shell
|
||||
.popups
|
||||
.grab_popup(root, kind, &seat, serial);
|
||||
.grab_popup(target, kind, &seat, serial);
|
||||
|
||||
if let Ok(mut grab) = ret {
|
||||
if let Some(keyboard) = seat.get_keyboard() {
|
||||
|
|
@ -157,18 +139,36 @@ impl XdgShellHandler for State {
|
|||
fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) {
|
||||
let seat = Seat::from_resource(&seat).unwrap();
|
||||
if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) {
|
||||
let workspace = self
|
||||
if let Some(mapped) = self
|
||||
.common
|
||||
.shell
|
||||
.space_for_window_mut(surface.wl_surface())
|
||||
.unwrap();
|
||||
let window = workspace
|
||||
.space
|
||||
.window_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
Shell::move_request(self, &window, &seat, serial, start_data);
|
||||
.element_for_surface(surface.wl_surface())
|
||||
.cloned()
|
||||
{
|
||||
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
|
||||
let output = seat.active_output();
|
||||
let (window, _) = mapped
|
||||
.windows()
|
||||
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
|
||||
.unwrap();
|
||||
if let Some(grab) =
|
||||
workspace.move_request(&window, &seat, &output, serial, start_data)
|
||||
{
|
||||
let handle = workspace.handle;
|
||||
self.common
|
||||
.shell
|
||||
.toplevel_info_state
|
||||
.toplevel_leave_workspace(&window, &handle);
|
||||
self.common
|
||||
.shell
|
||||
.toplevel_info_state
|
||||
.toplevel_leave_output(&window, &output);
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,35 +181,59 @@ impl XdgShellHandler for State {
|
|||
) {
|
||||
let seat = Seat::from_resource(&seat).unwrap();
|
||||
if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) {
|
||||
Workspace::resize_request(self, surface.wl_surface(), &seat, serial, start_data, edges);
|
||||
if let Some(mapped) = self
|
||||
.common
|
||||
.shell
|
||||
.element_for_surface(surface.wl_surface())
|
||||
.cloned()
|
||||
{
|
||||
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
|
||||
if let Some(grab) =
|
||||
workspace.resize_request(&mapped, &seat, serial, start_data, edges)
|
||||
{
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maximize_request(&mut self, surface: ToplevelSurface) {
|
||||
let surface = surface.wl_surface();
|
||||
let seat = &self.common.last_active_seat;
|
||||
let output = active_output(seat, &self.common);
|
||||
let seat = self.common.last_active_seat();
|
||||
let output = seat.active_output();
|
||||
|
||||
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
|
||||
let window = workspace
|
||||
.space
|
||||
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap()
|
||||
.clone();
|
||||
workspace.maximize_request(&window, &output)
|
||||
if let Some(mapped) = self
|
||||
.common
|
||||
.shell
|
||||
.element_for_surface(surface.wl_surface())
|
||||
.cloned()
|
||||
{
|
||||
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
|
||||
let (window, _) = mapped
|
||||
.windows()
|
||||
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
|
||||
.unwrap();
|
||||
workspace.maximize_request(&window, &output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
|
||||
let surface = surface.wl_surface();
|
||||
|
||||
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
|
||||
let window = workspace
|
||||
.space
|
||||
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap()
|
||||
.clone();
|
||||
workspace.unmaximize_request(&window)
|
||||
if let Some(mapped) = self
|
||||
.common
|
||||
.shell
|
||||
.element_for_surface(surface.wl_surface())
|
||||
.cloned()
|
||||
{
|
||||
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
|
||||
let (window, _) = mapped
|
||||
.windows()
|
||||
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
|
||||
.unwrap();
|
||||
workspace.unmaximize_request(&window)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,30 +242,79 @@ impl XdgShellHandler for State {
|
|||
.as_ref()
|
||||
.and_then(Output::from_resource)
|
||||
.unwrap_or_else(|| {
|
||||
let seat = &self.common.last_active_seat;
|
||||
active_output(seat, &self.common)
|
||||
let seat = self.common.last_active_seat();
|
||||
seat.active_output()
|
||||
});
|
||||
|
||||
let surface = surface.wl_surface();
|
||||
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
|
||||
let window = workspace
|
||||
.space
|
||||
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap()
|
||||
.clone();
|
||||
workspace.fullscreen_request(&window, &output)
|
||||
if let Some(mapped) = self
|
||||
.common
|
||||
.shell
|
||||
.element_for_surface(surface.wl_surface())
|
||||
.cloned()
|
||||
{
|
||||
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
|
||||
let (window, _) = mapped
|
||||
.windows()
|
||||
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
|
||||
.unwrap();
|
||||
workspace.fullscreen_request(&window, &output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
|
||||
let surface = surface.wl_surface();
|
||||
if let Some(workspace) = self.common.shell.space_for_window_mut(surface) {
|
||||
let window = workspace
|
||||
.space
|
||||
.window_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap()
|
||||
.clone();
|
||||
workspace.unfullscreen_request(&window)
|
||||
if let Some(mapped) = self
|
||||
.common
|
||||
.shell
|
||||
.element_for_surface(surface.wl_surface())
|
||||
.cloned()
|
||||
{
|
||||
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
|
||||
let (window, _) = mapped
|
||||
.windows()
|
||||
.find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface))
|
||||
.unwrap();
|
||||
workspace.unfullscreen_request(&window)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
|
||||
let outputs = self
|
||||
.common
|
||||
.shell
|
||||
.visible_outputs_for_surface(surface.wl_surface())
|
||||
.collect::<Vec<_>>();
|
||||
for output in outputs.iter() {
|
||||
self.common.shell.active_space_mut(output).refresh();
|
||||
}
|
||||
|
||||
// screencopy
|
||||
let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface());
|
||||
for output in outputs.into_iter() {
|
||||
if let Some(sessions) = output.user_data().get::<PendingScreencopyBuffers>() {
|
||||
scheduled_sessions
|
||||
.get_or_insert_with(Vec::new)
|
||||
.extend(sessions.borrow_mut().drain(..));
|
||||
}
|
||||
self.backend.schedule_render(
|
||||
&self.common.event_loop_handle,
|
||||
&output,
|
||||
scheduled_sessions.as_ref().map(|sessions| {
|
||||
sessions
|
||||
.iter()
|
||||
.filter(|(s, _)| match s.session_type() {
|
||||
SessionType::Output(o) | SessionType::Workspace(o, _)
|
||||
if o == output =>
|
||||
{
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -270,7 +343,6 @@ fn check_grab_preconditions(
|
|||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.id()
|
||||
.same_client_as(&surface.id())
|
||||
{
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
use crate::{shell::Shell, utils::prelude::*};
|
||||
use smithay::{
|
||||
desktop::{
|
||||
layer_map_for_output, LayerSurface, PopupKind, PopupManager, Space, Window,
|
||||
WindowSurfaceType,
|
||||
layer_map_for_output, LayerSurface, PopupKind, PopupManager, Window, WindowSurfaceType,
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
|
|
@ -27,12 +26,19 @@ use std::sync::Mutex;
|
|||
impl Shell {
|
||||
pub fn unconstrain_popup(&self, surface: &PopupSurface, positioner: &PositionerState) {
|
||||
if let Some(parent) = get_popup_toplevel(&surface) {
|
||||
if let Some(workspace) = self.space_for_window(&parent) {
|
||||
let window = workspace
|
||||
.space
|
||||
.window_for_surface(&parent, WindowSurfaceType::ALL)
|
||||
if let Some(elem) = self.element_for_surface(&parent) {
|
||||
let workspace = self.space_for(elem).unwrap();
|
||||
let element_loc = workspace.element_geometry(elem).unwrap().loc;
|
||||
let (window, offset) = elem
|
||||
.windows()
|
||||
.find(|(w, _)| w.toplevel().wl_surface() == &parent)
|
||||
.unwrap();
|
||||
unconstrain_xdg_popup(surface, positioner, &workspace.space, window);
|
||||
let window_geo_offset = window.geometry().loc;
|
||||
let window_loc = element_loc + offset + window_geo_offset;
|
||||
let anchor_point = get_anchor_point(&positioner) + window_loc;
|
||||
if let Some(output) = workspace.output_under(anchor_point) {
|
||||
unconstrain_xdg_popup(surface, positioner, window_loc, output.geometry());
|
||||
}
|
||||
} else if let Some((output, layer_surface)) = self.outputs().find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(&parent, WindowSurfaceType::ALL)
|
||||
|
|
@ -42,15 +48,14 @@ impl Shell {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_reactive_popups(&self, window: &Window) {
|
||||
if let Some(workspace) = self.space_for_window(window.toplevel().wl_surface()) {
|
||||
update_reactive_popups(&workspace.space, window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_reactive_popups(space: &Space, window: &Window) {
|
||||
pub fn update_reactive_popups<'a>(
|
||||
window: &Window,
|
||||
loc: Point<i32, Logical>,
|
||||
outputs: impl Iterator<Item = &'a Output>,
|
||||
) {
|
||||
let output_geo = outputs.map(|o| o.geometry()).collect::<Vec<_>>();
|
||||
for (popup, _) in PopupManager::popups_for_surface(window.toplevel().wl_surface()) {
|
||||
match popup {
|
||||
PopupKind::Xdg(surface) => {
|
||||
|
|
@ -64,12 +69,19 @@ pub fn update_reactive_popups(space: &Space, window: &Window) {
|
|||
attributes.current.positioner.clone()
|
||||
});
|
||||
if positioner.reactive {
|
||||
unconstrain_xdg_popup(&surface, &positioner, space, window);
|
||||
if let Err(err) = surface.send_configure() {
|
||||
slog_scope::warn!(
|
||||
"Compositor bug: Unable to re-configure reactive popup: {}",
|
||||
err
|
||||
);
|
||||
let anchor_point = get_anchor_point(&positioner) + loc;
|
||||
if let Some(rect) = output_geo
|
||||
.iter()
|
||||
.find(|geo| geo.contains(anchor_point))
|
||||
.copied()
|
||||
{
|
||||
unconstrain_xdg_popup(&surface, &positioner, loc, rect);
|
||||
if let Err(err) = surface.send_configure() {
|
||||
slog_scope::warn!(
|
||||
"Compositor bug: Unable to re-configure reactive popup: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,32 +92,18 @@ pub fn update_reactive_popups(space: &Space, window: &Window) {
|
|||
fn unconstrain_xdg_popup(
|
||||
surface: &PopupSurface,
|
||||
positioner: &PositionerState,
|
||||
space: &Space,
|
||||
window: &Window,
|
||||
window_loc: Point<i32, Logical>,
|
||||
rect: Rectangle<i32, Logical>,
|
||||
) {
|
||||
let anchor_point = get_anchor_point(&positioner) + space.window_location(&window).unwrap();
|
||||
if let Some(output_rect) = space
|
||||
.outputs_for_window(window)
|
||||
.into_iter()
|
||||
.find(|o| {
|
||||
space
|
||||
.output_geometry(o)
|
||||
.map(|rect| rect.contains(anchor_point))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|o| space.output_geometry(&o).unwrap())
|
||||
{
|
||||
// the output_rect represented relative to the parents coordinate system
|
||||
let mut relative = output_rect;
|
||||
relative.loc -= space.window_location(&window).unwrap();
|
||||
let offset = check_constrained(&surface, positioner.get_geometry(), relative);
|
||||
let mut relative = rect;
|
||||
relative.loc -= window_loc;
|
||||
let offset = check_constrained(&surface, positioner.get_geometry(), relative);
|
||||
|
||||
if offset.x != 0 || offset.y != 0 {
|
||||
slog_scope::debug!("Unconstraining popup: {:?}", surface);
|
||||
if !unconstrain_flip(&surface, &positioner, relative) {
|
||||
if !unconstrain_slide(&surface, &positioner, relative) {
|
||||
unconstrain_resize(&surface, &positioner, relative);
|
||||
}
|
||||
if offset.x != 0 || offset.y != 0 {
|
||||
slog_scope::debug!("Unconstraining popup: {:?}", surface);
|
||||
if !unconstrain_flip(&surface, &positioner, relative) {
|
||||
if !unconstrain_slide(&surface, &positioner, relative) {
|
||||
unconstrain_resize(&surface, &positioner, relative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -319,7 +317,7 @@ fn get_anchor_point(positioner: &PositionerState) -> Point<i32, Logical> {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn get_popup_toplevel(popup: &PopupSurface) -> Option<WlSurface> {
|
||||
pub fn get_popup_toplevel(popup: &PopupSurface) -> Option<WlSurface> {
|
||||
let mut parent = popup.get_parent_surface()?;
|
||||
while get_role(&parent) == Some(XDG_POPUP_ROLE) {
|
||||
parent = with_states(&parent, |states| {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod drm;
|
||||
pub mod export_dmabuf;
|
||||
//pub mod export_dmabuf;
|
||||
pub mod output_configuration;
|
||||
pub mod screencopy;
|
||||
pub mod toplevel_info;
|
||||
pub mod toplevel_management;
|
||||
pub mod workspace;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use smithay::{
|
||||
output::{Mode, Output, OutputData},
|
||||
output::{Mode, Output},
|
||||
reexports::{
|
||||
wayland_protocols_wlr::output_management::v1::server::{
|
||||
zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1},
|
||||
|
|
@ -17,6 +17,7 @@ use smithay::{
|
|||
},
|
||||
},
|
||||
utils::{Logical, Physical, Point, Size, Transform},
|
||||
wayland::output::WlOutputData,
|
||||
};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
|
|
@ -498,7 +499,7 @@ where
|
|||
impl<D> OutputConfigurationState<D>
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ GlobalDispatch<WlOutput, OutputData>
|
||||
+ GlobalDispatch<WlOutput, WlOutputData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
|
|
|
|||
946
src/wayland/protocols/screencopy.rs
Normal file
946
src/wayland/protocols/screencopy.rs
Normal 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;
|
||||
|
|
@ -413,9 +413,9 @@ fn send_toplevel_to_client<D>(
|
|||
.iter()
|
||||
.filter(|o| !handle_state.outputs.contains(o))
|
||||
{
|
||||
new_output.with_client_outputs(dh, &client, |_dh, wl_output| {
|
||||
instance.output_enter(wl_output);
|
||||
});
|
||||
for wl_output in new_output.client_outputs(&client) {
|
||||
instance.output_enter(&wl_output);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
for old_output in handle_state
|
||||
|
|
@ -423,9 +423,9 @@ fn send_toplevel_to_client<D>(
|
|||
.iter()
|
||||
.filter(|o| !state.outputs.contains(o))
|
||||
{
|
||||
old_output.with_client_outputs(dh, &client, |_dh, wl_output| {
|
||||
instance.output_leave(wl_output);
|
||||
});
|
||||
for wl_output in old_output.client_outputs(&client) {
|
||||
instance.output_leave(&wl_output);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
handle_state.outputs = state.outputs.clone();
|
||||
|
|
|
|||
|
|
@ -182,12 +182,12 @@ where
|
|||
let window = window_from_handle(toplevel).unwrap();
|
||||
if let Some(toplevel_state) = window.user_data().get::<ToplevelState>() {
|
||||
let mut toplevel_state = toplevel_state.lock().unwrap();
|
||||
if let Some(client) = surface.client_id() {
|
||||
if let Some(client) = surface.client() {
|
||||
if width == 0 && height == 0 {
|
||||
toplevel_state.rectangles.remove(&client);
|
||||
toplevel_state.rectangles.remove(&client.id());
|
||||
} else {
|
||||
toplevel_state.rectangles.insert(
|
||||
client,
|
||||
client.id(),
|
||||
(
|
||||
surface,
|
||||
Rectangle::from_loc_and_size((x, y), (width, height)),
|
||||
|
|
@ -207,7 +207,7 @@ where
|
|||
if !mng_state
|
||||
.instances
|
||||
.iter()
|
||||
.any(|i| i.client_id().map(|c| c == client).unwrap_or(false))
|
||||
.any(|i| i.client().map(|c| c.id() == client).unwrap_or(false))
|
||||
{
|
||||
for toplevel in state.toplevel_info_state_mut().toplevels.iter() {
|
||||
if let Some(toplevel_state) = toplevel.user_data().get::<ToplevelState>() {
|
||||
|
|
|
|||
|
|
@ -821,9 +821,9 @@ where
|
|||
.iter()
|
||||
.filter(|o| !handle_state.outputs.contains(o))
|
||||
{
|
||||
new_output.with_client_outputs(dh, &client, |_dh, wl_output| {
|
||||
instance.output_enter(wl_output);
|
||||
});
|
||||
for wl_output in new_output.client_outputs(&client) {
|
||||
instance.output_enter(&wl_output);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
for old_output in handle_state
|
||||
|
|
@ -831,9 +831,9 @@ where
|
|||
.iter()
|
||||
.filter(|o| !group.outputs.contains(o))
|
||||
{
|
||||
old_output.with_client_outputs(dh, &client, |_dh, wl_output| {
|
||||
instance.output_leave(wl_output);
|
||||
});
|
||||
for wl_output in old_output.client_outputs(&client) {
|
||||
instance.output_leave(&wl_output);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
handle_state.outputs = group.outputs.clone();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue