Compare commits

..

87 commits

Author SHA1 Message Date
d1849f639b fix: resolve rebase conflicts and fix package name 2026-07-22 15:21:58 +02:00
e6173b93ca chore: restore rust-toolchain 1.93 + refresh lockfile post-rebase
Leyoda 2026 – GPLv3
2026-07-22 15:02:35 +02:00
ba1232cffc fix: absorb libcosmic theme-v2 API (private containers, text::Style, wgpu 28)
Leyoda 2026 – GPLv3
2026-07-22 15:02:24 +02:00
4fa39e5ea3 chore: use local cosmic-text checkout 2026-07-22 15:00:14 +02:00
bbf7effda2 yoda: refresh local dbus binding lock 2026-07-22 14:59:51 +02:00
3d01a21900 fix: keep tokio runtime available for compositor 2026-07-22 14:59:37 +02:00
3e87e769a7 chore: align compositor with local cosmic stack 2026-07-22 14:59:37 +02:00
Ashley Wulber
4d370cdf92 fix: no ceil for scaled outline thickness 2026-07-21 22:22:07 +02:00
littlezabi
1d72227a30 fix: release active popup grabs on toplevel destruction
If a window with an active popup grab is closed or crashes unexpectedly, the input grab state can get stuck, freezing keyboard and mouse clicks.
    This patch iterates over all seats and releases the active seat grab if it is owned by the destroyed window or one of its child popups.

Signed-off-by: littlezabi <zohaibjozvi@gmail.com>
2026-07-21 18:04:33 +02:00
Ian Douglas Scott
d7d6be3cdb image-copy: Only embed cursor in toplevel capture if focused 2026-07-21 17:02:58 +02:00
Ian Douglas Scott
270ba7bf80 image-copy: Update toplevel cursor position for cursor_follows_focus 2026-07-21 17:02:58 +02:00
Ian Douglas Scott
6c6b56a7f3 input: Deduplicate into update_output_image_copy_cursor_position 2026-07-21 17:02:58 +02:00
Ian Douglas Scott
dda2d14832 input: Send wl_pointer.motion on warp, and applying constraint hint
Gnome seems to send `motion` in these cases. The spec isn't 100%
explicit, but it seems `motion` events are needed for the client to have
the same understanding of where the pointer is as the compositor, which
generally seems to be desired.

This is also needed for toplevel cursor image-copy to get the position
change. Likewise, we want image-copy to have the same understanding of
where the cursor is as the compositor and client.
2026-07-21 17:02:58 +02:00
Ian Douglas Scott
c92c18e09e Use Global coordinate instead of inferring as Logical
Inference behavior can be confusing, and the coordinate here is global,
so use `Global` like the `PointerMotion*` event handlers.
2026-07-21 17:02:58 +02:00
Ian Douglas Scott
2648aea0df image-copy: Move duplicate code to a cursor_capture_constraints()
This ensures we avoid advertising a `0,0` buffer size (which will
protocol error to actually allocate), and avoids defining the supported
formats for cursor capture buffers in a bunch of places.
2026-07-21 17:02:58 +02:00
Ian Douglas Scott
27956e6e00 Make cursor_geometry() return a CursorGeometry struct
A bit better than just a tuple.
2026-07-21 17:02:58 +02:00
Ian Douglas Scott
dd6ebd03c9 image-copy: Hold references to Buffers to block release
The `wl_buffer` now won't be released until sync fence polls ready.
2026-07-21 17:02:58 +02:00
Ian Douglas Scott
d84d5d46ce image-copy: Use seat from WlPointer instead of last_active() 2026-07-21 17:02:58 +02:00
Vukašin Vojinović
60137c0d9e chore(render): use Affine2 where possible 2026-07-21 15:53:38 +02:00
RipleyTom
8955024bff Fix: improve edge pointer accuracy 2026-07-21 15:50:19 +02:00
RipleyTom
9789187416 fix: map_window_request should update pending state 2026-07-21 13:08:47 +02:00
Hojjat
aabf1cdc08 fix: flicker on wayland context menu pop up
fix
2026-07-21 12:59:20 +02:00
Victoria Brekenfeld
9b5a2a0f83 chore: Fix feature gate for logind 2026-07-20 17:28:15 +02:00
Vukašin Vojinović
d41124a51d chore: clippy fixes 2026-07-18 09:09:30 +02:00
Vukašin Vojinović
160bd39ba4 render: cgmath -> glam 2026-07-18 09:09:30 +02:00
Victoria Brekenfeld
2d95d46a37 chore: formatting fixes 2026-07-16 19:43:10 +02:00
Victoria Brekenfeld
8ab946f10a chore: clippy fixes 2026-07-16 19:43:10 +02:00
Alyssa Ross
9feaa865ab main: fix kiosk command with command line options
We were just running whatever was given on the command line as the
kiosk command, which meant that if --no-xwayland was given, we'd try
execing --no-xwayland.  Preserve the processed command line from the
option parser, and run only what's left over as the kiosk command to
fix this.
2026-07-15 22:56:25 +02:00
Alyssa Ross
2b360a8dd7 main: notify readiness on non-systemd systems
Other service supervisors implement systemd's readiness notification
protocol.  For example, s6 implements it with the
s6-notify-fd-from-socket[1] program.  libsystemd::daemon::notify
already checks for NOTIFY_SOCKET being set, which should be sufficient
to tell if systemd-style readiness notification is wanted.

Link: https://skarnet.org/software/s6/s6-notify-fd-from-socket.html [1]
2026-07-15 22:55:25 +02:00
Hojjat
2329cea5c9 fix: input to cropped tiled windows should be bound to the tile 2026-07-15 22:55:05 +02:00
Graham Walker
b8807a541f Fix detached active window hint 2026-07-15 13:16:28 -06:00
Ashley Wulber
8f82cade68 cleanup: remove attempts to force redraw
They seem to always fail anyways and it has no negative effect as far as I can tell
2026-07-15 14:48:33 -04:00
Victoria Brekenfeld
dec1ee8633 fixup: logic error 2026-07-14 18:16:50 -04:00
Victoria Brekenfeld
603c90b103 element/surface: Avoid scanning out translucent surfaces 2026-07-14 18:16:50 -04:00
Erik
7ee78d9c13 Forward axis relative direction to Wayland clients.
Libinput reports natural scrolling via AxisRelativeDirection. Qt sets
QWheelEvent::inverted() from wl_pointer.axis_relative_direction. COSMIC
was forwarding axis values but not the direction hint.

Tested on Fedora 44 COSMIC Wayland with Telegram Desktop (native Wayland),
natural scrolling enabled — horizontal touchpad swipes fixed.

Assisted-by: AI (root-cause analysis)

Signed-off-by: Erik <erik-balfe@users.noreply.github.com>
2026-07-13 21:19:27 +02:00
Victoria Brekenfeld
a69d8d2dba
Merge pull request #2490 from weblate/weblate-pop-os-cosmic-comp
Translations update from Hosted Weblate
2026-07-13 17:25:19 +02:00
Ilia Malanin
7a7c6b7fd4 fix: take into account pending maximized state when recalculating window geometry 2026-07-13 17:24:56 +02:00
Victoria Brekenfeld
80a440fcb0
Merge branch 'master' into weblate-pop-os-cosmic-comp 2026-07-13 17:19:34 +02:00
Tom Grushka
0596cce40d remove redundant level condition; simplify level early return on ZoomMovement::Centered 2026-07-13 13:08:45 +02:00
Tom Grushka
4d14f5568f improve comments 2026-07-13 13:08:45 +02:00
Tom Grushka
01c0cb020e fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in
- I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes.
- I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system.

- Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway.

- Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues.

- ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688).

- ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503).

- `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping.

- In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-13 13:08:45 +02:00
Victoria Brekenfeld
693d1ad775 shell/window: Fix radii for server-side decorations 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
2971845d40 render/blur: Fix rotated outputs having cut off blur regions 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
3e17157ccf chore: update smithay 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
1f48a5e866 shell/menu: Use new MenuColumn widget 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
4f8aed4149 blur: Fix invalid matrix inversions 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
0c805d9b30 chore: cleanup new blur code 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
7fbeb8787f examples: Fix new libcosmic build 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
f6cb4519a0 render/blur: Cache native texture type 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
c92b91221c render/element: Support converting gl-textures 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
1f0456a5af render: Include OutputId in namespaces 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
361a2282c7 output: Add increasing output id 2026-07-09 18:08:01 +02:00
Ashley Wulber
f6d3dafda7 fix: use configured blur strength for lock surface and its subsurfaces 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
e0b6784eb5 render/blur: Wipe blur_texture before blit 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
f424abbc29 render/blur: Fix div-by-zero in shaders 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
43d6145afd chore: fix warnings with no features 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
58938ef8ee render/blur: Fix transformed outputs 2026-07-09 18:08:01 +02:00
Ashley Wulber
b890f7b0fa fix(corner_radius): loosen padding size constraint 2026-07-09 18:08:01 +02:00
Ashley Wulber
77f57a656d fix: pad_rect typos 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
db06770a6b element: Refactor remaining libcosmic elements for blur 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
6c76fdaa79 element/window/stack: Use new IcedRenderElement 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
ab9f13b36f iced: Handle blur internally 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
5ee2fa911a wayland: Support cosmic-corner-radius v2 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
55d57ddba2 chore: theme-v2 libcosmic update 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
667414bd92 renderer/blur: Fix incorrectly offset blur region 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
6868c24501 renderer/blur: Use proper commit counter 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
d4cd9b674c chore: Adjust log level for blur debugging 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
cfed174e9b chore: Fix clippy lints 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
de8b47839c render: Make blur_strength configurable 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
1c5ef576f4 render: Don't use separate damage_output calls 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
eb41b076ed render: Use NamespaceElements to fix blurred lower layer-shell surfaces 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
a9bf89d8b2 element/wayland: Add blur background-effect 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
e4c0716951 element: Refactor element acquisition to be push based 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
178c6593dc element: Introduce SurfaceRenderElement 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
8b990e5658 render: Merge FromGlesError into AsGlowRenderer 2026-07-09 18:08:01 +02:00
Victoria Brekenfeld
0ce7f11445 element/surface: Fix reported corner radius 2026-07-09 18:08:01 +02:00
maskedsyntax
067be10474 fix: fall back to legacy X11 names when loading pointer themes
Smithay requests modern shape-v1 names while themes like DMZ-White
only provide legacy X11 aliases. Try known aliases before failing to load.
2026-07-08 16:10:15 -06:00
Ian Douglas Scott
373a44d431 shell: Restore fullscreen to single-element stack
If a surface was previously a single-element stack, restore it normally
using the floating/tiling restore data, but map as a single element
stack instead of a plain window.
2026-07-07 17:46:23 -07:00
Ian Douglas Scott
98e1dd7f6b shell: Also restore stack element on sticky layer to stack 2026-07-07 17:46:23 -07:00
Ian Douglas Scott
5c0c22988e Don't unnecessarily use Option in WorkspaceRestoreData
Unless we expect to need this again in the future, it's best not to have
an `Option` here unnecessarily.
2026-07-07 17:46:23 -07:00
Ian Douglas Scott
9e6d46c17e shell: Restore fullscreen surface to stack
Previously, a surface that was in a stack that is fullscreened would be
removed from the stack, and on unfullscreening, restored to the
floating/tiling layer as though it were a new window outside the stack.

Instead, if the stack still exists, add the window to it at the same
position it previously occupied.
2026-07-07 17:46:23 -07:00
Ian Douglas Scott
f765696834 Avoid unnecessarily unwrapping stack_ref() after is_stack()
In a lot of cases, matching on `stack_ref()` also works.
2026-07-07 17:46:23 -07:00
Ian Douglas Scott
26ee83aaa7 Implement Debug and comparison with CosmicMapped for CosmicMappedKey 2026-07-07 17:46:23 -07:00
Ian Douglas Scott
29320bdab5 Add cosmic-keyboard-layout-unstable-v1 protocol
https://github.com/pop-os/cosmic-protocols/pull/28

Allows clients like `cosmic-applet-input-sources` to show which layout
group is active and update it. Currently this is implemented by update
the `xkb_config` to change the layout order.
2026-07-07 16:44:09 +02:00
Ericky Dos Santos
031fdc389a kms: change from thread_local approach to wiring scanout node through function calls to detect scanout candidates 2026-07-06 11:26:22 -06:00
Ericky Dos Santos
012c77ec81 fix(kms): gate overlay scanout per-element by buffer GPU node 2026-07-06 11:26:22 -06:00
Hosted Weblate
4be4263480
i18n: translation updates from weblate
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/kmr/
Translation: Pop OS/COSMIC Comp
2026-06-27 23:01:23 +02:00
62 changed files with 5096 additions and 2439 deletions

28
Cargo.lock generated
View file

@ -866,7 +866,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
dependencies = [ dependencies = [
"serde", "serde",
"termcolor", "termcolor",
"unicode-width 0.1.14", "unicode-width 0.2.2",
] ]
[[package]] [[package]]
@ -989,7 +989,6 @@ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags 2.11.0",
"calloop", "calloop",
"cgmath",
"clap_lex", "clap_lex",
"cosmic-comp-config", "cosmic-comp-config",
"cosmic-config", "cosmic-config",
@ -1001,14 +1000,16 @@ dependencies = [
"egui_plot", "egui_plot",
"futures-executor", "futures-executor",
"futures-util", "futures-util",
"glam 0.33.2",
"i18n-embed", "i18n-embed",
"i18n-embed-fl", "i18n-embed-fl",
"iced_graphics",
"iced_tiny_skia", "iced_tiny_skia",
"id_tree", "id_tree",
"indexmap 2.13.0", "indexmap 2.13.0",
"jiff", "jiff",
"keyframe", "keyframe",
"libcosmic-yoda", "libcosmic",
"libdisplay-info", "libdisplay-info",
"libsystemd", "libsystemd",
"log-panics", "log-panics",
@ -1025,6 +1026,7 @@ dependencies = [
"sanitize-filename", "sanitize-filename",
"serde", "serde",
"serde_json", "serde_json",
"smallvec",
"smithay", "smithay",
"smithay-egui", "smithay-egui",
"thiserror 2.0.18", "thiserror 2.0.18",
@ -2160,6 +2162,12 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
[[package]]
name = "glam"
version = "0.33.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f22fb22f065b308be0d8724e3706c7fa3fc2a6c7d6899df4cad7860e7a75436"
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.16.0" version = "0.16.0"
@ -2474,7 +2482,7 @@ dependencies = [
"bytes", "bytes",
"cosmic-client-toolkit", "cosmic-client-toolkit",
"dnd", "dnd",
"glam", "glam 0.25.0",
"lilt", "lilt",
"log", "log",
"mime 0.1.0", "mime 0.1.0",
@ -2590,7 +2598,7 @@ dependencies = [
"cosmic-client-toolkit", "cosmic-client-toolkit",
"cryoglyph", "cryoglyph",
"futures", "futures",
"glam", "glam 0.25.0",
"guillotiere", "guillotiere",
"iced_debug", "iced_debug",
"iced_graphics", "iced_graphics",
@ -3131,7 +3139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "libcosmic-yoda" name = "libcosmic"
version = "0.1.0-yoda.2" version = "0.1.0-yoda.2"
dependencies = [ dependencies = [
"apply", "apply",
@ -5249,14 +5257,14 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.1" version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]] [[package]]
name = "smithay" name = "smithay"
version = "0.7.0" version = "0.7.0"
source = "git+https://github.com/smithay/smithay.git?rev=8eb4076#8eb4076cad8705c56f5e3bac8577b83576b6ce76" source = "git+https://github.com/smithay/smithay.git?rev=1ed69cb#1ed69cb55e9d0e9f38887c3b7d53d24365c4b14f"
dependencies = [ dependencies = [
"aliasable", "aliasable",
"appendlist", "appendlist",
@ -5265,7 +5273,6 @@ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"calloop", "calloop",
"cc", "cc",
"cgmath",
"cursor-icon", "cursor-icon",
"downcast-rs", "downcast-rs",
"drm", "drm",
@ -5275,6 +5282,7 @@ dependencies = [
"errno", "errno",
"gbm", "gbm",
"gl_generator", "gl_generator",
"glam 0.33.2",
"glow", "glow",
"indexmap 2.13.0", "indexmap 2.13.0",
"input", "input",

View file

@ -4,7 +4,7 @@ edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"
name = "cosmic-comp" name = "cosmic-comp"
version = "1.0.0" version = "1.0.0"
rust-version = "1.90" rust-version = "1.93"
[workspace] [workspace]
members = ["cosmic-comp-config"] members = ["cosmic-comp-config"]
@ -40,9 +40,10 @@ i18n-embed = { version = "0.16", features = [
] } ] }
i18n-embed-fl = "0.10" i18n-embed-fl = "0.10"
iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" } iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" }
iced_graphics = { path = "../libcosmic/iced/graphics", features = ["geometry"] }
indexmap = "2.13" indexmap = "2.13"
keyframe = "1.1.1" keyframe = "1.1.1"
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [ cosmic = { package = "libcosmic", path = "../libcosmic", default-features = false, features = [
"tokio", "tokio",
"wayland", "wayland",
"multi-window", "multi-window",
@ -87,7 +88,8 @@ parking_lot = "0.12.5"
logind-zbus = { version = "5.3.2", optional = true } logind-zbus = { version = "5.3.2", optional = true }
futures-executor = { version = "0.3.32" } futures-executor = { version = "0.3.32" }
futures-util = "0.3.32" futures-util = "0.3.32"
cgmath = "0.18.0" glam = { version = "0.33", default-features = false, features = ["std"] }
smallvec = "1.15.2"
[dependencies.id_tree] [dependencies.id_tree]
branch = "feature/copy_clone" branch = "feature/copy_clone"
@ -143,7 +145,7 @@ inherits = "release"
[profile.release] [profile.release]
lto = "fat" lto = "fat"
[patch.'https://github.com/pop-os/libcosmic'] [patch."https://github.com/pop-os/libcosmic"]
cosmic-config = { path = "../libcosmic/cosmic-config" } cosmic-config = { path = "../libcosmic/cosmic-config" }
cosmic-theme = { path = "../libcosmic/cosmic-theme" } cosmic-theme = { path = "../libcosmic/cosmic-theme" }
iced = { path = "../libcosmic/iced" } iced = { path = "../libcosmic/iced" }
@ -158,21 +160,21 @@ iced_wgpu = { path = "../libcosmic/iced/wgpu" }
iced_widget = { path = "../libcosmic/iced/widget" } iced_widget = { path = "../libcosmic/iced/widget" }
iced_winit = { path = "../libcosmic/iced/winit" } iced_winit = { path = "../libcosmic/iced/winit" }
[patch.'https://github.com/pop-os/cosmic-protocols'] [patch."https://github.com/pop-os/cosmic-protocols"]
cosmic-protocols = { path = "../cosmic-protocols" } cosmic-protocols = { path = "../cosmic-protocols" }
cosmic-client-toolkit = { path = "../cosmic-protocols/client-toolkit" } cosmic-client-toolkit = { path = "../cosmic-protocols/client-toolkit" }
[patch.'https://github.com/pop-os/cosmic-settings-daemon'] [patch."https://github.com/pop-os/cosmic-settings-daemon"]
cosmic-settings-config = { path = "../cosmic-settings-daemon/config" } cosmic-settings-config = { path = "../cosmic-settings-daemon/config" }
cosmic-settings-daemon-config = { path = "../cosmic-settings-daemon/cosmic-settings-daemon-config" } cosmic-settings-daemon-config = { path = "../cosmic-settings-daemon/cosmic-settings-daemon-config" }
[patch.'https://github.com/pop-os/cosmic-text.git'] [patch."https://github.com/pop-os/cosmic-text.git"]
cosmic-text = { path = "../cosmic-text" } cosmic-text = { path = "../cosmic-text" }
[patch.'https://forge.aditua.com/leyoda/window_clipboard.git'] [patch."https://forge.aditua.com/leyoda/window_clipboard.git"]
window_clipboard = { path = "../window_clipboard" } window_clipboard = { path = "../window_clipboard" }
dnd = { path = "../window_clipboard/dnd" } dnd = { path = "../window_clipboard/dnd" }
mime = { path = "../window_clipboard/mime" } mime = { path = "../window_clipboard/mime" }
[patch.crates-io] [patch.crates-io]
smithay = { git = "https://github.com/smithay/smithay.git", rev = "8eb4076" } smithay = { git = "https://github.com/smithay/smithay.git", rev = "1ed69cb" }

View file

@ -23,7 +23,7 @@ struct Circle {
color: Color, color: Color,
} }
impl<Message, Theme, Renderer: cosmic::iced_renderer::geometry::Renderer> impl<Message, Theme, Renderer: iced_graphics::geometry::Renderer>
canvas::Program<Message, Theme, Renderer> for Circle canvas::Program<Message, Theme, Renderer> for Circle
{ {
type State = (); type State = ();
@ -50,17 +50,17 @@ impl<Internal, Message: std::clone::Clone + 'static, Lower: Decorations<Internal
{ {
fn view(&self, window: &Internal) -> cosmic::Element<'_, Message> { fn view(&self, window: &Internal) -> cosmic::Element<'_, Message> {
let orig = self.lower.view(window); let orig = self.lower.view(window);
widget::row() widget::row([
.push( widget::column([canvas(Circle {
widget::column() radius: (self.height as f32 / 2.) * 0.8,
.push(canvas(Circle { color: Color::from_rgba(1.0, 0.0, 0.0, 1.0),
radius: (self.height as f32 / 2.) * 0.8, })
color: Color::from_rgba(1.0, 0.0, 0.0, 1.0), .into()])
})) .width(self.height as f32)
.width(self.height as f32), .into(),
) orig,
.push(orig) ])
.into() .into()
} }
} }

View file

@ -1,7 +1,7 @@
a11y-zoom-move-continuously = Ansicht bewegt sich kontinuierlich mit dem Cursor a11y-zoom-move-continuously = Ansicht bewegt sich kontinuierlich mit dem Cursor
a11y-zoom-move-onedge = Ansicht bewegt sich, sobald der Cursor den Rand erreicht a11y-zoom-move-onedge = Ansicht bewegt sich, sobald der Cursor den Rand erreicht
a11y-zoom-move-centered = Ansicht bewegt sich, um den Cursor zentiert zu halten a11y-zoom-move-centered = Ansicht bewegt sich, um den Cursor zentiert zu halten
a11y-zoom-settings = Vergrößerungsglas-Einstellungen a11y-zoom-settings = Vergrößerungsglas-Einstellungen ...
grow-window = Vergrößern grow-window = Vergrößern
shrink-window = Verkleinern shrink-window = Verkleinern
swap-windows = Fenster tauschen swap-windows = Fenster tauschen

View file

@ -0,0 +1,28 @@
grow-window = Mezin bike
shrink-window = Biçûk bike
window-menu-move = Bilivîne
window-menu-close = Bigire
window-menu-resize-edge-top = Jor
window-menu-resize-edge-left = Çep
window-menu-resize-edge-right = Rast
window-menu-resize-edge-bottom = Jêr
unknown-keybinding = <unset>
window-menu-minimize = Biçûk bike
window-menu-maximize = Mezin bike
window-menu-fullscreen = Dîmendera tije
window-menu-tiled = Çarçoveya herikbar
window-menu-screenshot = Wêneya dîmenderê bigire
window-menu-resize = Mezinahiyê ji nû ve saz bike
window-menu-move-prev-workspace = Bilivîne bo qada xebatê ya paş
window-menu-move-next-workspace = Bilivîne bo qada xebatê ya pêş
window-menu-stack = Komkirina çarçoveyê biafrîne
window-menu-unstack-all = Çarçoveyan kom neke
window-menu-unstack = Çarçoveyê kom neke
window-menu-sticky = Çarçoveya daliqandî
window-menu-close-all = Hemû çarçoveyan bigire
a11y-zoom-move-continuously = Dîtin bi nîşanker re bi awayekî domdar dilive
a11y-zoom-move-onedge = Dîtin dema nîşanker dikeve qeraxê dilive
a11y-zoom-move-centered = Dîtin dilive da ku nîşanker navîn bimîne
a11y-zoom-settings = Sazkariyên mezinkerê...
swap-windows = Çarçoveyan biguhêrîne
stack-windows = Çarçoveyan dalqîne

View file

@ -943,6 +943,7 @@ impl LockedDevice<'_> {
output, output,
CursorMode::All, CursorMode::All,
None, None,
None,
) )
.with_context(|| "Failed to render outputs")?, .with_context(|| "Failed to render outputs")?,
None => Vec::new(), None => Vec::new(),

View file

@ -978,6 +978,7 @@ impl KmsGuard<'_> {
output, output,
CursorMode::All, CursorMode::All,
None, None,
None,
) )
.with_context(|| "Failed to render outputs")?; .with_context(|| "Failed to render outputs")?;
@ -1088,6 +1089,7 @@ impl KmsGuard<'_> {
output, output,
CursorMode::All, CursorMode::All,
None, None,
None,
) )
.with_context(|| "Failed to render outputs")?; .with_context(|| "Failed to render outputs")?;
@ -1132,6 +1134,7 @@ impl KmsGuard<'_> {
output, output,
CursorMode::All, CursorMode::All,
None, None,
None,
) )
.with_context(|| "Failed to render outputs")?; .with_context(|| "Failed to render outputs")?;

View file

@ -12,10 +12,9 @@ use smithay::{
}, },
drm::{CreateDrmNodeError, DrmNode}, drm::{CreateDrmNodeError, DrmNode},
renderer::{ renderer::{
RendererSuper,
gles::{GlesError, GlesRenderer, ffi}, gles::{GlesError, GlesRenderer, ffi},
glow::GlowRenderer, glow::GlowRenderer,
multigpu::{ApiDevice, Error as MultiError, GraphicsApi}, multigpu::{ApiDevice, GraphicsApi},
}, },
}, },
reexports::drm::control::Device, reexports::drm::control::Device,
@ -32,8 +31,6 @@ use std::{
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
}; };
use crate::backend::render::element::FromGlesError;
/// Errors raised by the [`GbmGlesBackend`] /// Errors raised by the [`GbmGlesBackend`]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -213,15 +210,3 @@ impl ApiDevice for GbmGlowDevice {
!self.intel_export_quirk !self.intel_export_quirk
} }
} }
impl<T: GraphicsApi, A: AsFd + Device + Clone + 'static> FromGlesError
for MultiError<GbmGlowBackend<A>, T>
where
T::Error: 'static,
<<T::Device as ApiDevice>::Renderer as RendererSuper>::Error: 'static,
{
#[inline]
fn from_gles_error(err: GlesError) -> MultiError<GbmGlowBackend<A>, T> {
MultiError::Render(err)
}
}

View file

@ -36,7 +36,7 @@ impl Common {
) -> Result<Socket> { ) -> Result<Socket> {
let socket_name = format!( let socket_name = format!(
"{}-{}", "{}-{}",
&self.socket.to_string_lossy(), self.socket.to_string_lossy(),
render_node render_node
.dev_path() .dev_path()
.unwrap() .unwrap()

View file

@ -1068,6 +1068,7 @@ impl SurfaceThreadState {
None, None,
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
Some((&self.egui, &self.timings)), Some((&self.egui, &self.timings)),
Some(self.target_node),
) )
.map_err(|err| { .map_err(|err| {
anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err)
@ -1673,7 +1674,7 @@ fn send_screencopy_result<'a>(
pre_postprocess_data: &mut PrePostprocessData, pre_postprocess_data: &mut PrePostprocessData,
tx: &std::sync::mpsc::Sender<PendingImageCopyData>, tx: &std::sync::mpsc::Sender<PendingImageCopyData>,
frame_result: &RenderFrameResult<GbmBuffer, GbmFramebuffer, CosmicElement<GlMultiRenderer<'a>>>, frame_result: &RenderFrameResult<GbmBuffer, GbmFramebuffer, CosmicElement<GlMultiRenderer<'a>>>,
elements: &[CosmicElement<GlMultiRenderer>], elements: &[CosmicElement<GlMultiRenderer<'a>>],
(session, frame, res): ( (session, frame, res): (
&ScreencopySessionRef, &ScreencopySessionRef,
ScreencopyFrame, ScreencopyFrame,
@ -1854,6 +1855,8 @@ fn send_screencopy_result<'a>(
transform, transform,
damage.as_deref(), damage.as_deref(),
sync, sync,
// Don't reference `Buffer`s since we blit from framebuffer/postprocess buffer
vec![],
)? { )? {
if frame_result.is_empty { if frame_result.is_empty {
data.frame data.frame

View file

@ -1,6 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::{utils::prelude::*, wayland::handlers::compositor::FRAME_TIME_FILTER}; use crate::{
backend::render::{
element::AsGlowRenderer,
wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree},
},
utils::prelude::*,
wayland::handlers::compositor::FRAME_TIME_FILTER,
};
use smithay::{ use smithay::{
backend::{ backend::{
allocator::Fourcc, allocator::Fourcc,
@ -9,10 +16,10 @@ use smithay::{
element::{ element::{
Kind, Kind,
memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement}, memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
}, },
}, },
}, },
desktop::utils::bbox_from_surface_tree,
input::{ input::{
Seat, Seat,
pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus}, pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus},
@ -114,24 +121,63 @@ enum Error {
NoDefaultCursor, NoDefaultCursor,
#[error("Error opening xcursor file: {0}")] #[error("Error opening xcursor file: {0}")]
File(#[from] std::io::Error), File(#[from] std::io::Error),
#[error("Failed to parse XCursor file")] }
Parse,
fn cursor_aliases(name: &str) -> &[&str] {
match name {
"default" => &["default", "left_ptr", "arrow"],
"pointer" => &["pointer", "hand2", "hand"],
"text" => &["text", "xterm"],
"wait" => &["wait", "watch"],
"progress" => &["progress", "left_ptr_watch"],
"ew-resize" => &["ew-resize", "h_double_arrow", "sb_h_double_arrow"],
"ns-resize" => &["ns-resize", "v_double_arrow", "sb_v_double_arrow"],
"nw-resize" => &["nw-resize", "top_left_corner"],
"ne-resize" => &["ne-resize", "top_right_corner"],
"sw-resize" => &["sw-resize", "bottom_left_corner"],
"se-resize" => &["se-resize", "bottom_right_corner"],
"w-resize" => &["w-resize", "left_side"],
"e-resize" => &["e-resize", "right_side"],
"n-resize" => &["n-resize", "top_side"],
"s-resize" => &["s-resize", "bottom_side"],
"move" => &["move", "fleur"],
"not-allowed" => &["not-allowed", "crossed_circle"],
"crosshair" => &["crosshair", "cross"],
"help" => &["help", "question_arrow", "left_ptr_help"],
_ => &[],
}
} }
fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result<Vec<Image>, Error> { fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result<Vec<Image>, Error> {
let icon_path = theme let shape_name = shape.to_string();
.load_icon(&shape.to_string())
.ok_or(Error::NoDefaultCursor)?; for name in cursor_aliases(&shape_name)
let mut cursor_file = std::fs::File::open(&icon_path)?; .iter()
let mut cursor_data = Vec::new(); .copied()
cursor_file.read_to_end(&mut cursor_data)?; .chain(std::iter::once(shape_name.as_str()))
parse_xcursor(&cursor_data).ok_or(Error::Parse) {
if let Some(icon_path) = theme.load_icon(name) {
let mut cursor_file = std::fs::File::open(&icon_path)?;
let mut cursor_data = Vec::new();
cursor_file.read_to_end(&mut cursor_data)?;
if let Some(images) = parse_xcursor(&cursor_data) {
return Ok(images);
}
}
}
Err(Error::NoDefaultCursor)
} }
render_elements! { render_elements! {
pub CursorRenderElement<R> where R: ImportAll + ImportMem; pub CursorRenderElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
Static=MemoryRenderBufferRenderElement<R>, Static=MemoryRenderBufferRenderElement<R>,
Surface=WaylandSurfaceRenderElement<R>, Surface=SurfaceRenderElement<R>,
} }
pub fn draw_surface_cursor<R>( pub fn draw_surface_cursor<R>(
@ -139,9 +185,10 @@ pub fn draw_surface_cursor<R>(
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
location: Point<f64, Logical>, location: Point<f64, Logical>,
scale: impl Into<Scale<f64>>, scale: impl Into<Scale<f64>>,
) -> Vec<(CursorRenderElement<R>, Point<i32, Physical>)> blur_strength: usize,
where push: &mut dyn FnMut(CursorRenderElement<R>, Point<i32, Physical>),
R: Renderer + ImportAll, ) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static, R::TextureId: Clone + 'static,
{ {
let scale = scale.into(); let scale = scale.into();
@ -156,17 +203,21 @@ where
.to_physical_precise_round(scale) .to_physical_precise_round(scale)
}); });
render_elements_from_surface_tree( push_render_elements_from_surface_tree(
renderer, renderer,
surface, surface,
location.to_physical(scale).to_i32_round(), location.to_physical(scale).to_i32_round(),
bbox_from_surface_tree(surface, location.to_i32_round()).to_f64(),
scale, scale,
1.0, 1.0,
false,
[0; 4],
None,
blur_strength,
Kind::Cursor, Kind::Cursor,
) &mut |elem| push(elem.into(), h),
.into_iter() None,
.map(|elem| (elem, h)) );
.collect()
} }
#[profiling::function] #[profiling::function]
@ -175,9 +226,10 @@ pub fn draw_dnd_icon<R>(
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
location: Point<f64, Logical>, location: Point<f64, Logical>,
scale: impl Into<Scale<f64>>, scale: impl Into<Scale<f64>>,
) -> Vec<WaylandSurfaceRenderElement<R>> blur_strength: usize,
where push: &mut dyn FnMut(SurfaceRenderElement<R>),
R: Renderer + ImportAll, ) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static, R::TextureId: Clone + 'static,
{ {
if get_role(surface) != Some("dnd_icon") { if get_role(surface) != Some("dnd_icon") {
@ -187,14 +239,21 @@ where
); );
} }
let scale = scale.into(); let scale = scale.into();
render_elements_from_surface_tree( push_render_elements_from_surface_tree(
renderer, renderer,
surface, surface,
location.to_physical(scale).to_i32_round(), location.to_physical(scale).to_i32_round(),
bbox_from_surface_tree(surface, location.to_i32_round()).to_f64(),
scale, scale,
1.0, 1.0,
false,
[0; 4],
None,
blur_strength,
FRAME_TIME_FILTER, FRAME_TIME_FILTER,
) push,
None,
);
} }
pub type CursorState = Mutex<CursorStateInner>; pub type CursorState = Mutex<CursorStateInner>;
@ -277,10 +336,11 @@ pub fn draw_cursor<R>(
scale: Scale<f64>, scale: Scale<f64>,
buffer_scale: f64, buffer_scale: f64,
time: Time<Monotonic>, time: Time<Monotonic>,
blur_strength: usize,
draw_default: bool, draw_default: bool,
) -> Vec<(CursorRenderElement<R>, Point<i32, Physical>)> push: &mut dyn FnMut(CursorRenderElement<R>, Point<i32, Physical>),
where ) where
R: Renderer + ImportMem + ImportAll, R: Renderer + ImportMem + ImportAll + AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
{ {
// draw the cursor as relevant // draw the cursor as relevant
@ -291,7 +351,7 @@ where
let state = &mut *state_ref; let state = &mut *state_ref;
if state.hidden { if state.hidden {
return Vec::new(); return;
} }
let named_cursor = state.current_cursor.or(match cursor_status { let named_cursor = state.current_cursor.or(match cursor_status {
@ -300,7 +360,7 @@ where
}); });
if let Some(current_cursor) = named_cursor { if let Some(current_cursor) = named_cursor {
if !draw_default && current_cursor == CursorIcon::Default { if !draw_default && current_cursor == CursorIcon::Default {
return Vec::new(); return;
} }
let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32; let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32;
@ -337,7 +397,7 @@ where
); );
state.current_image = Some(frame); state.current_image = Some(frame);
return vec![( push(
CursorRenderElement::Static( CursorRenderElement::Static(
MemoryRenderBufferRenderElement::from_buffer( MemoryRenderBufferRenderElement::from_buffer(
renderer, renderer,
@ -351,11 +411,9 @@ where
.expect("Failed to import cursor bitmap"), .expect("Failed to import cursor bitmap"),
), ),
hotspot.to_physical_precise_round(scale), hotspot.to_physical_precise_round(scale),
)]; );
} else if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { } else if let CursorImageStatus::Surface(ref wl_surface) = cursor_status {
return draw_surface_cursor(renderer, wl_surface, location, scale); draw_surface_cursor(renderer, wl_surface, location, scale, blur_strength, push);
} else {
Vec::new()
} }
} }

View file

@ -1,20 +1,27 @@
use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement}; use crate::{
backend::{
kms::render::gles::GbmGlowBackend,
render::{GlMultiError, wayland::SurfaceRenderElement},
},
shell::{CosmicMappedRenderElement, WorkspaceRenderElement},
utils::iced::IcedRenderElement,
};
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
use smithay::backend::renderer::element::texture::TextureRenderElement; use smithay::backend::renderer::element::texture::TextureRenderElement;
use smithay::{ use smithay::{
backend::{ backend::{
allocator::dmabuf::Dmabuf, allocator::dmabuf::Dmabuf,
drm::DrmDeviceFd,
renderer::{ renderer::{
Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, Bind, Blit, ContextId, ExportMem, ImportAll, ImportMem, Offscreen, Renderer,
element::{ element::{
Element, Id, Kind, RenderElement, UnderlyingStorage, Element, Id, Kind, RenderElement, UnderlyingStorage,
memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement},
}, },
gles::{GlesError, GlesRenderbuffer, GlesTexture, element::TextureShaderElement}, gles::{GlesError, GlesRenderbuffer, GlesTexture, element::TextureShaderElement},
glow::{GlowFrame, GlowRenderer}, glow::{GlowFrame, GlowRenderer},
multigpu::MultiTexture,
utils::{CommitCounter, DamageSet, OpaqueRegions}, utils::{CommitCounter, DamageSet, OpaqueRegions},
}, },
}, },
@ -28,19 +35,20 @@ use super::{GlMultiRenderer, cursor::CursorRenderElement};
pub enum CosmicElement<R> pub enum CosmicElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
Workspace( Workspace(
RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>, RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>,
), ),
Cursor(RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>), Cursor(RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>),
Dnd(WaylandSurfaceRenderElement<R>), Dnd(SurfaceRenderElement<R>),
MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>), MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>),
Postprocess( Postprocess(
CropRenderElement<RelocateRenderElement<RescaleRenderElement<TextureShaderElement>>>, CropRenderElement<RelocateRenderElement<RescaleRenderElement<TextureShaderElement>>>,
), ),
Zoom(MemoryRenderBufferRenderElement<R>), Zoom(IcedRenderElement<R>),
Damage(DamageElement),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
Egui(TextureRenderElement<GlesTexture>), Egui(TextureRenderElement<GlesTexture>),
} }
@ -48,7 +56,7 @@ where
impl<R> Element for CosmicElement<R> impl<R> Element for CosmicElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn id(&self) -> &Id { fn id(&self) -> &Id {
@ -59,6 +67,7 @@ where
CosmicElement::MoveGrab(elem) => elem.id(), CosmicElement::MoveGrab(elem) => elem.id(),
CosmicElement::Postprocess(elem) => elem.id(), CosmicElement::Postprocess(elem) => elem.id(),
CosmicElement::Zoom(elem) => elem.id(), CosmicElement::Zoom(elem) => elem.id(),
CosmicElement::Damage(elem) => elem.id(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.id(), CosmicElement::Egui(elem) => elem.id(),
} }
@ -72,6 +81,7 @@ where
CosmicElement::MoveGrab(elem) => elem.current_commit(), CosmicElement::MoveGrab(elem) => elem.current_commit(),
CosmicElement::Postprocess(elem) => elem.current_commit(), CosmicElement::Postprocess(elem) => elem.current_commit(),
CosmicElement::Zoom(elem) => elem.current_commit(), CosmicElement::Zoom(elem) => elem.current_commit(),
CosmicElement::Damage(elem) => elem.current_commit(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.current_commit(), CosmicElement::Egui(elem) => elem.current_commit(),
} }
@ -85,6 +95,7 @@ where
CosmicElement::MoveGrab(elem) => elem.src(), CosmicElement::MoveGrab(elem) => elem.src(),
CosmicElement::Postprocess(elem) => elem.src(), CosmicElement::Postprocess(elem) => elem.src(),
CosmicElement::Zoom(elem) => elem.src(), CosmicElement::Zoom(elem) => elem.src(),
CosmicElement::Damage(elem) => elem.src(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.src(), CosmicElement::Egui(elem) => elem.src(),
} }
@ -98,6 +109,7 @@ where
CosmicElement::MoveGrab(elem) => elem.geometry(scale), CosmicElement::MoveGrab(elem) => elem.geometry(scale),
CosmicElement::Postprocess(elem) => elem.geometry(scale), CosmicElement::Postprocess(elem) => elem.geometry(scale),
CosmicElement::Zoom(elem) => elem.geometry(scale), CosmicElement::Zoom(elem) => elem.geometry(scale),
CosmicElement::Damage(elem) => elem.geometry(scale),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.geometry(scale), CosmicElement::Egui(elem) => elem.geometry(scale),
} }
@ -111,6 +123,7 @@ where
CosmicElement::MoveGrab(elem) => elem.location(scale), CosmicElement::MoveGrab(elem) => elem.location(scale),
CosmicElement::Postprocess(elem) => elem.location(scale), CosmicElement::Postprocess(elem) => elem.location(scale),
CosmicElement::Zoom(elem) => elem.location(scale), CosmicElement::Zoom(elem) => elem.location(scale),
CosmicElement::Damage(elem) => elem.location(scale),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.location(scale), CosmicElement::Egui(elem) => elem.location(scale),
} }
@ -124,6 +137,7 @@ where
CosmicElement::MoveGrab(elem) => elem.transform(), CosmicElement::MoveGrab(elem) => elem.transform(),
CosmicElement::Postprocess(elem) => elem.transform(), CosmicElement::Postprocess(elem) => elem.transform(),
CosmicElement::Zoom(elem) => elem.transform(), CosmicElement::Zoom(elem) => elem.transform(),
CosmicElement::Damage(elem) => elem.transform(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.transform(), CosmicElement::Egui(elem) => elem.transform(),
} }
@ -141,6 +155,7 @@ where
CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit),
CosmicElement::Postprocess(elem) => elem.damage_since(scale, commit), CosmicElement::Postprocess(elem) => elem.damage_since(scale, commit),
CosmicElement::Zoom(elem) => elem.damage_since(scale, commit), CosmicElement::Zoom(elem) => elem.damage_since(scale, commit),
CosmicElement::Damage(elem) => elem.damage_since(scale, commit),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.damage_since(scale, commit), CosmicElement::Egui(elem) => elem.damage_since(scale, commit),
} }
@ -154,6 +169,7 @@ where
CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale),
CosmicElement::Postprocess(elem) => elem.opaque_regions(scale), CosmicElement::Postprocess(elem) => elem.opaque_regions(scale),
CosmicElement::Zoom(elem) => elem.opaque_regions(scale), CosmicElement::Zoom(elem) => elem.opaque_regions(scale),
CosmicElement::Damage(elem) => elem.opaque_regions(scale),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.opaque_regions(scale), CosmicElement::Egui(elem) => elem.opaque_regions(scale),
} }
@ -167,6 +183,7 @@ where
CosmicElement::MoveGrab(elem) => elem.alpha(), CosmicElement::MoveGrab(elem) => elem.alpha(),
CosmicElement::Postprocess(elem) => elem.alpha(), CosmicElement::Postprocess(elem) => elem.alpha(),
CosmicElement::Zoom(elem) => elem.alpha(), CosmicElement::Zoom(elem) => elem.alpha(),
CosmicElement::Damage(elem) => elem.alpha(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.alpha(), CosmicElement::Egui(elem) => elem.alpha(),
} }
@ -180,6 +197,7 @@ where
CosmicElement::MoveGrab(elem) => elem.kind(), CosmicElement::MoveGrab(elem) => elem.kind(),
CosmicElement::Postprocess(elem) => elem.kind(), CosmicElement::Postprocess(elem) => elem.kind(),
CosmicElement::Zoom(elem) => elem.kind(), CosmicElement::Zoom(elem) => elem.kind(),
CosmicElement::Damage(elem) => elem.kind(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.kind(), CosmicElement::Egui(elem) => elem.kind(),
} }
@ -193,6 +211,7 @@ where
CosmicElement::MoveGrab(elem) => elem.is_framebuffer_effect(), CosmicElement::MoveGrab(elem) => elem.is_framebuffer_effect(),
CosmicElement::Postprocess(elem) => elem.is_framebuffer_effect(), CosmicElement::Postprocess(elem) => elem.is_framebuffer_effect(),
CosmicElement::Zoom(elem) => elem.is_framebuffer_effect(), CosmicElement::Zoom(elem) => elem.is_framebuffer_effect(),
CosmicElement::Damage(elem) => elem.is_framebuffer_effect(),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.is_framebuffer_effect(), CosmicElement::Egui(elem) => elem.is_framebuffer_effect(),
} }
@ -202,8 +221,7 @@ where
impl<R> RenderElement<R> for CosmicElement<R> impl<R> RenderElement<R> for CosmicElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
R::Error: FromGlesError,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn draw( fn draw(
@ -237,9 +255,12 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicElement::Zoom(elem) => elem.draw(frame, src, dst, damage, opaque_regions, cache), CosmicElement::Zoom(elem) => elem.draw(frame, src, dst, damage, opaque_regions, cache),
CosmicElement::Damage(elem) => {
RenderElement::<R>::draw(elem, frame, src, dst, damage, opaque_regions, cache)
}
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => { CosmicElement::Egui(elem) => {
let glow_frame = R::glow_frame_mut(frame); let glow_frame = R::glow_frame_mut(frame);
@ -252,7 +273,7 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
} }
} }
@ -268,6 +289,7 @@ where
elem.underlying_storage(glow_renderer) elem.underlying_storage(glow_renderer)
} }
CosmicElement::Zoom(elem) => elem.underlying_storage(renderer), CosmicElement::Zoom(elem) => elem.underlying_storage(renderer),
CosmicElement::Damage(elem) => elem.underlying_storage(renderer),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => { CosmicElement::Egui(elem) => {
let glow_renderer = renderer.glow_renderer_mut(); let glow_renderer = renderer.glow_renderer_mut();
@ -293,16 +315,19 @@ where
RenderElement::<GlowRenderer>::capture_framebuffer( RenderElement::<GlowRenderer>::capture_framebuffer(
elem, glow_frame, src, dst, cache, elem, glow_frame, src, dst, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicElement::Zoom(elem) => elem.capture_framebuffer(frame, src, dst, cache), CosmicElement::Zoom(elem) => elem.capture_framebuffer(frame, src, dst, cache),
CosmicElement::Damage(elem) => {
RenderElement::<R>::capture_framebuffer(elem, frame, src, dst, cache)
}
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
CosmicElement::Egui(elem) => { CosmicElement::Egui(elem) => {
let glow_frame = R::glow_frame_mut(frame); let glow_frame = R::glow_frame_mut(frame);
RenderElement::<GlowRenderer>::capture_framebuffer( RenderElement::<GlowRenderer>::capture_framebuffer(
elem, glow_frame, src, dst, cache, elem, glow_frame, src, dst, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
} }
} }
@ -312,7 +337,7 @@ impl<R> From<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>
for CosmicElement<R> for CosmicElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>) -> Self { fn from(elem: CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>) -> Self {
@ -324,22 +349,33 @@ where
} }
} }
impl<R> From<MemoryRenderBufferRenderElement<R>> for CosmicElement<R> impl<R> From<IcedRenderElement<R>> for CosmicElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self { fn from(value: IcedRenderElement<R>) -> Self {
Self::Zoom(value) Self::Zoom(value)
} }
} }
impl<R> From<DamageElement> for CosmicElement<R>
where
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(value: DamageElement) -> Self {
Self::Damage(value)
}
}
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
impl<R> From<TextureRenderElement<GlesTexture>> for CosmicElement<R> impl<R> From<TextureRenderElement<GlesTexture>> for CosmicElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: TextureRenderElement<GlesTexture>) -> Self { fn from(elem: TextureRenderElement<GlesTexture>) -> Self {
@ -365,6 +401,12 @@ pub trait AsGlowRenderer:
fn glow_frame_mut<'a, 'frame, 'buffer>( fn glow_frame_mut<'a, 'frame, 'buffer>(
frame: &'a mut Self::Frame<'frame, 'buffer>, frame: &'a mut Self::Frame<'frame, 'buffer>,
) -> &'a mut GlowFrame<'frame, 'buffer>; ) -> &'a mut GlowFrame<'frame, 'buffer>;
fn tex_from_gl(context: &ContextId<GlesTexture>, texture: GlesTexture) -> Self::TextureId;
fn tex_to_gl(
context: &ContextId<GlesTexture>,
texture: &Self::TextureId,
) -> Option<GlesTexture>;
fn from_gles_error(err: GlesError) -> Self::Error;
} }
impl AsGlowRenderer for GlowRenderer { impl AsGlowRenderer for GlowRenderer {
@ -384,6 +426,18 @@ impl AsGlowRenderer for GlowRenderer {
) -> &'a mut GlowFrame<'frame, 'buffer> { ) -> &'a mut GlowFrame<'frame, 'buffer> {
frame frame
} }
fn tex_from_gl(_context: &ContextId<GlesTexture>, texture: GlesTexture) -> Self::TextureId {
texture
}
fn tex_to_gl(
_context: &ContextId<GlesTexture>,
texture: &Self::TextureId,
) -> Option<GlesTexture> {
Some(texture.clone())
}
fn from_gles_error(err: GlesError) -> Self::Error {
err
}
} }
impl AsGlowRenderer for GlMultiRenderer<'_> { impl AsGlowRenderer for GlMultiRenderer<'_> {
@ -403,6 +457,18 @@ impl AsGlowRenderer for GlMultiRenderer<'_> {
) -> &'b mut GlowFrame<'frame, 'buffer> { ) -> &'b mut GlowFrame<'frame, 'buffer> {
frame.as_mut() frame.as_mut()
} }
fn tex_from_gl(context: &ContextId<GlesTexture>, texture: GlesTexture) -> Self::TextureId {
MultiTexture::from_native_texture::<GbmGlowBackend<DrmDeviceFd>>(context, texture).unwrap()
}
fn tex_to_gl(
context: &ContextId<GlesTexture>,
texture: &Self::TextureId,
) -> Option<GlesTexture> {
texture.get::<GbmGlowBackend<DrmDeviceFd>>(context)
}
fn from_gles_error(err: GlesError) -> Self::Error {
GlMultiError::Render(err)
}
} }
pub struct DamageElement { pub struct DamageElement {
@ -458,13 +524,3 @@ impl<R: Renderer> RenderElement<R> for DamageElement {
Ok(()) Ok(())
} }
} }
pub trait FromGlesError {
fn from_gles_error(err: GlesError) -> Self;
}
impl FromGlesError for GlesError {
fn from_gles_error(err: GlesError) -> Self {
err
}
}

View file

@ -15,14 +15,19 @@ use crate::{
backend::{ backend::{
kms::render::gles::GbmGlowBackend, kms::render::gles::GbmGlowBackend,
render::{ render::{
clipped_surface::{CLIPPING_SHADER, ClippingShader},
element::DamageElement, element::DamageElement,
shadow::{SHADOW_SHADER, ShadowShader}, shadow::{SHADOW_SHADER, ShadowShader},
wayland::{
SurfaceRenderElement,
blur_effect::BlurShaders,
clipped_surface::{CLIPPING_SHADER, ClippingShader},
push_render_elements_from_surface_tree,
},
}, },
}, },
config::ScreenFilter, config::ScreenFilter,
shell::{ shell::{
CosmicMappedRenderElement, OverviewMode, SeatExt, Trigger, WorkspaceDelta, CosmicMappedRenderElement, OutputId, OverviewMode, SeatExt, Trigger, WorkspaceDelta,
WorkspaceRenderElement, WorkspaceRenderElement,
element::CosmicMappedKey, element::CosmicMappedKey,
focus::{FocusTarget, Stage, render_input_order, target::WindowGroup}, focus::{FocusTarget, Stage, render_input_order, target::WindowGroup},
@ -34,25 +39,26 @@ use crate::{
wayland::{ wayland::{
handlers::{ handlers::{
compositor::FRAME_TIME_FILTER, compositor::FRAME_TIME_FILTER,
corner_radius::{pad_rect, surface_corners, surface_padding},
data_device::get_dnd_icon, data_device::get_dnd_icon,
image_copy_capture::{FrameHolder, SessionData, render_session}, image_copy_capture::{
FrameHolder, SessionData, render_element_buffers, render_session,
},
}, },
protocols::workspace::WorkspaceHandle, protocols::workspace::WorkspaceHandle,
}, },
}; };
use cosmic::Theme; use cosmic::Theme;
use element::FromGlesError;
use smithay::{ use smithay::{
backend::{ backend::{
allocator::Fourcc, allocator::Fourcc,
drm::{DrmDeviceFd, DrmNode}, drm::{DrmDeviceFd, DrmNode},
renderer::{ renderer::{
Color32F, ImportAll, Offscreen, Renderer, Texture, TextureFilter, Color32F, Offscreen, Texture, TextureFilter,
damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult}, damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult},
element::{ element::{
Element, Id, Kind, RenderElement, WeakId, Element, Id, Kind, NamespacedElement, RenderElement, WeakId,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
texture::{TextureRenderBuffer, TextureRenderElement}, texture::{TextureRenderBuffer, TextureRenderElement},
utils::{ utils::{
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate, ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate,
@ -69,22 +75,23 @@ use smithay::{
sync::SyncPoint, sync::SyncPoint,
}, },
}, },
desktop::utils::bbox_from_surface_tree,
input::Seat, input::Seat,
output::{Output, OutputModeSource, OutputNoMode}, output::{Output, OutputModeSource, OutputNoMode},
utils::{ utils::{
IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform, IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform,
}, },
wayland::{dmabuf::get_dmabuf, session_lock::LockSurface}, wayland::{compositor::with_states, dmabuf::get_dmabuf, session_lock::LockSurface},
}; };
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
use smithay_egui::EguiState; use smithay_egui::EguiState;
pub mod animations; pub mod animations;
pub mod clipped_surface;
pub mod cursor; pub mod cursor;
pub mod element; pub mod element;
pub mod shadow; pub mod shadow;
pub mod wayland;
use self::element::{AsGlowRenderer, CosmicElement}; use self::element::{AsGlowRenderer, CosmicElement};
use super::kms::Timings; use super::kms::Timings;
@ -262,7 +269,7 @@ impl IndicatorShader {
.filter(|(old_settings, _)| &settings == old_settings) .filter(|(old_settings, _)| &settings == old_settings)
.is_none() .is_none()
{ {
let thickness: f32 = ((thickness as f64 * scale).ceil() / scale) as f32; let thickness: f32 = ((thickness as f64 * scale) / scale) as f32;
let shader = Self::get(renderer); let shader = Self::get(renderer);
let elem = PixelShaderElement::new( let elem = PixelShaderElement::new(
@ -422,6 +429,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> {
UniformName::new("geo_size", UniformType::_2f), UniformName::new("geo_size", UniformType::_2f),
UniformName::new("corner_radius", UniformType::_4f), UniformName::new("corner_radius", UniformType::_4f),
UniformName::new("input_to_geo", UniformType::Matrix3x3), UniformName::new("input_to_geo", UniformType::Matrix3x3),
UniformName::new("noise", UniformType::_1f),
], ],
)?; )?;
let shadow_shader = renderer.compile_custom_pixel_shader( let shadow_shader = renderer.compile_custom_pixel_shader(
@ -437,6 +445,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> {
UniformName::new("window_corner_radius", UniformType::_4f), UniformName::new("window_corner_radius", UniformType::_4f),
], ],
)?; )?;
let blur_shaders = BlurShaders::compile(renderer)?;
let egl_context = renderer.egl_context(); let egl_context = renderer.egl_context();
egl_context egl_context
@ -454,6 +463,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> {
egl_context egl_context
.user_data() .user_data()
.insert_if_missing(|| ShadowShader(shadow_shader)); .insert_if_missing(|| ShadowShader(shadow_shader));
egl_context.user_data().insert_if_missing(|| blur_shaders);
Ok(()) Ok(())
} }
@ -471,12 +481,14 @@ pub fn cursor_elements<'a, 'frame, R>(
seats: impl Iterator<Item = &'a Seat<State>>, seats: impl Iterator<Item = &'a Seat<State>>,
zoom_state: Option<&ZoomState>, zoom_state: Option<&ZoomState>,
theme: &Theme, theme: &Theme,
blur_strength: usize,
now: Time<Monotonic>, now: Time<Monotonic>,
output: &Output, output: &Output,
mode: CursorMode, mode: CursorMode,
exclude_dnd_icon: bool, exclude_dnd_icon: bool,
) -> Vec<CosmicElement<R>> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(CosmicElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
@ -490,7 +502,6 @@ where
) )
}) })
.unwrap_or_else(|| ((0., 0.).into(), 1.)); .unwrap_or_else(|| ((0., 0.).into(), 1.));
let mut elements = Vec::new();
for seat in seats { for seat in seats {
let pointer = match seat.get_pointer() { let pointer = match seat.get_pointer() {
@ -500,19 +511,17 @@ where
let location = pointer.current_location() - output.current_location().to_f64(); let location = pointer.current_location() - output.current_location().to_f64();
if mode != CursorMode::None { if mode != CursorMode::None {
elements.extend( cursor::draw_cursor(
cursor::draw_cursor( renderer,
renderer, seat,
seat, location,
location, scale.into(),
scale.into(), zoom_scale,
zoom_scale, now,
now, blur_strength,
mode != CursorMode::NotDefault, mode != CursorMode::NotDefault,
) &mut |elem, hotspot| {
.into_iter() push(CosmicElement::Cursor(RescaleRenderElement::from_element(
.map(|(elem, hotspot)| {
CosmicElement::Cursor(RescaleRenderElement::from_element(
RelocateRenderElement::from_element( RelocateRenderElement::from_element(
elem, elem,
Point::from((-hotspot.x, -hotspot.y)), Point::from((-hotspot.x, -hotspot.y)),
@ -523,63 +532,55 @@ where
.to_physical(output.current_scale().fractional_scale()) .to_physical(output.current_scale().fractional_scale())
.to_i32_round(), .to_i32_round(),
zoom_scale, zoom_scale,
)) )))
}), },
); );
} }
if !exclude_dnd_icon && let Some(dnd_icon) = get_dnd_icon(seat) { if !exclude_dnd_icon && let Some(dnd_icon) = get_dnd_icon(seat) {
elements.extend( cursor::draw_dnd_icon(
cursor::draw_dnd_icon( renderer,
renderer, &dnd_icon.surface,
&dnd_icon.surface, (location + dnd_icon.offset.to_f64()).to_i32_round(),
(location + dnd_icon.offset.to_f64()).to_i32_round(), scale,
scale, blur_strength,
) &mut |elem| push(CosmicElement::Dnd(elem)),
.into_iter()
.map(CosmicElement::Dnd),
); );
} }
let theme = theme.cosmic(); let theme = theme.cosmic();
if let Some(grab_elements) = seat if let Some(grab_state) = seat
.user_data() .user_data()
.get::<SeatMoveGrabState>() .get::<SeatMoveGrabState>()
.unwrap() .unwrap()
.lock() .lock()
.unwrap() .unwrap()
.as_ref() .as_ref()
.map(|state| state.render::<CosmicMappedRenderElement<R>, R>(renderer, output, theme))
{ {
elements.extend(grab_elements.into_iter().map(|elem| { grab_state.render(renderer, output, theme, scanout_node, &mut |elem| {
CosmicElement::MoveGrab(RescaleRenderElement::from_element( push(CosmicElement::MoveGrab(RescaleRenderElement::from_element(
elem, elem,
focal_point focal_point
.as_logical() .as_logical()
.to_physical(output.current_scale().fractional_scale()) .to_physical(output.current_scale().fractional_scale())
.to_i32_round(), .to_i32_round(),
zoom_scale, zoom_scale,
)) )));
})); })
} }
if let Some((grab_elements, should_scale)) = seat if let Some(grab_state) = seat
.user_data() .user_data()
.get::<SeatMenuGrabState>() .get::<SeatMenuGrabState>()
.unwrap() .unwrap()
.lock() .lock()
.unwrap() .unwrap()
.as_ref() .as_ref()
.map(|state| {
(
state.render::<CosmicMappedRenderElement<R>, R>(renderer, output),
!state.is_in_screen_space(),
)
})
{ {
elements.extend(grab_elements.into_iter().map(|elem| { let should_scale = !grab_state.is_in_screen_space();
CosmicElement::MoveGrab(RescaleRenderElement::from_element( grab_state.render(renderer, output, &mut |elem| {
elem, push(CosmicElement::MoveGrab(RescaleRenderElement::from_element(
elem.into(),
if should_scale { if should_scale {
focal_point focal_point
.as_logical() .as_logical()
@ -589,12 +590,10 @@ where
Point::from((0, 0)) Point::from((0, 0))
}, },
if should_scale { zoom_scale } else { 1.0 }, if should_scale { zoom_scale } else { 1.0 },
)) )));
})); })
} }
} }
elements
} }
#[cfg(not(feature = "debug"))] #[cfg(not(feature = "debug"))]
@ -615,11 +614,11 @@ pub fn output_elements<R>(
output: &Output, output: &Output,
cursor_mode: CursorMode, cursor_mode: CursorMode,
_fps: Option<(&EguiState, &Timings)>, _fps: Option<(&EguiState, &Timings)>,
scanout_node: Option<DrmNode>,
) -> Result<Vec<CosmicElement<R>>, RenderError<R::Error>> ) -> Result<Vec<CosmicElement<R>>, RenderError<R::Error>>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>, WorkspaceRenderElement<R>: RenderElement<R>,
{ {
@ -646,7 +645,7 @@ where
), ),
scale, scale,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
.map_err(RenderError::Rendering)? .map_err(RenderError::Rendering)?
.into(), .into(),
] ]
@ -690,6 +689,7 @@ where
workspace, workspace,
cursor_mode, cursor_mode,
element_filter, element_filter,
scanout_node,
)?; )?;
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
@ -713,15 +713,15 @@ pub fn workspace_elements<R>(
current: (WorkspaceHandle, usize), current: (WorkspaceHandle, usize),
cursor_mode: CursorMode, cursor_mode: CursorMode,
element_filter: ElementFilter, element_filter: ElementFilter,
scanout_node: Option<DrmNode>,
) -> Result<Vec<CosmicElement<R>>, RenderError<R::Error>> ) -> Result<Vec<CosmicElement<R>>, RenderError<R::Error>>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>, WorkspaceRenderElement<R>: RenderElement<R>,
{ {
let mut elements = Vec::new(); let mut elements = Vec::<CosmicElement<R>>::new();
let shell_ref = shell.read(); let shell_ref = shell.read();
let seats = shell_ref.seats.iter().cloned().collect::<Vec<_>>(); let seats = shell_ref.seats.iter().cloned().collect::<Vec<_>>();
@ -729,21 +729,25 @@ where
return Ok(Vec::new()); return Ok(Vec::new());
} }
let theme = shell_ref.theme().clone(); let theme = shell_ref.theme().clone();
let blur_strength = (theme.cosmic().frosted as u8 + 1) as usize;
let scale = output.current_scale().fractional_scale(); let scale = output.current_scale().fractional_scale();
// we don't want to hold a shell lock across `cursor_elements`, // we don't want to hold a shell lock across `cursor_elements`,
// that is prone to deadlock with the main-thread on some grabs. // that is prone to deadlock with the main-thread on some grabs.
std::mem::drop(shell_ref); std::mem::drop(shell_ref);
elements.extend(cursor_elements( cursor_elements(
renderer, renderer,
seats.iter(), seats.iter(),
zoom_level, zoom_level,
&theme, &theme,
blur_strength,
now, now,
output, output,
cursor_mode, cursor_mode,
element_filter == ElementFilter::ExcludeWorkspaceOverview, element_filter == ElementFilter::ExcludeWorkspaceOverview,
)); scanout_node,
&mut |elem| elements.push(elem),
);
let shell = shell.read(); let shell = shell.read();
let overview = shell.overview_mode(); let overview = shell.overview_mode();
@ -819,72 +823,132 @@ where
render_input_order::<()>(&shell, output, previous, current, element_filter, |stage| { render_input_order::<()>(&shell, output, previous, current, element_filter, |stage| {
match stage { match stage {
Stage::ZoomUI => { Stage::ZoomUI => {
elements.extend(ZoomState::render(renderer, output)); ZoomState::render(renderer, output, &mut |elem| {
elements.push(CosmicElement::Zoom(elem))
});
} }
Stage::SessionLock(lock_surface) => { Stage::SessionLock(lock_surface) => {
elements.extend( session_lock_elements(renderer, output, lock_surface, blur_strength, &mut |elem| {
session_lock_elements(renderer, output, lock_surface) elements.extend(crop_to_output(elem.into()).map(Into::into))
.into_iter() })
.map(Into::into)
.flat_map(crop_to_output)
.map(Into::into),
);
} }
Stage::LayerPopup { Stage::LayerPopup {
popup, location, .. popup,
location,
workspace_idx,
..
} => { } => {
elements.extend( let mut geometry = popup.geometry().as_global();
render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( geometry.loc += location;
renderer,
popup.wl_surface(), let radii = with_states(popup.wl_surface(), |states| {
location surface_corners(states, geometry.size.as_logical())
.to_local(output) })
.as_logical() .unwrap_or([0; 4]);
.to_physical_precise_round(scale),
Scale::from(scale), let namespace = output
1.0, .user_data()
FRAME_TIME_FILTER, .get::<OutputId>()
) .map(|id| id.namespace_for_workspace(workspace_idx))
.into_iter() .unwrap_or(workspace_idx);
.flat_map(crop_to_output)
.map(Into::into), push_render_elements_from_surface_tree(
); renderer,
popup.wl_surface(),
location
.to_local(output)
.as_logical()
.to_physical_precise_round(scale),
geometry.to_local(output).as_logical().to_f64(),
Scale::from(scale),
1.0,
false,
radii,
None,
blur_strength,
FRAME_TIME_FILTER,
&mut |elem| {
elements.extend(
crop_to_output(NamespacedElement::new(elem, namespace).into())
.map(Into::into),
)
},
None,
)
} }
Stage::LayerSurface { layer, location } => { Stage::LayerSurface {
elements.extend( layer,
render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( location,
renderer, workspace_idx,
layer.wl_surface(), } => {
location let mut geometry = layer.geometry().as_global();
.to_local(output) geometry.loc += location;
.as_logical() let geometry = geometry.to_local(output).as_logical();
.to_physical_precise_round(scale),
Scale::from(scale), let padded = with_states(layer.wl_surface(), |states| {
1.0, surface_padding(states, geometry.size)
FRAME_TIME_FILTER, .and_then(|padding| pad_rect(geometry, &padding))
) })
.into_iter() .unwrap_or(geometry);
.flat_map(crop_to_output) let radii = with_states(layer.wl_surface(), |states| {
.map(Into::into), surface_corners(states, padded.size)
})
.unwrap_or([0; 4]);
let namespace = output
.user_data()
.get::<OutputId>()
.map(|id| id.namespace_for_workspace(workspace_idx))
.unwrap_or(workspace_idx);
push_render_elements_from_surface_tree(
renderer,
layer.wl_surface(),
location
.to_local(output)
.as_logical()
.to_physical_precise_round(scale),
geometry.to_f64(),
Scale::from(scale),
1.0,
false,
radii,
padded.to_f64(),
blur_strength,
FRAME_TIME_FILTER,
&mut |elem| {
elements.extend(
crop_to_output(NamespacedElement::new(elem, namespace).into())
.map(Into::into),
)
},
None,
); );
} }
Stage::OverrideRedirect { surface, location } => { Stage::OverrideRedirect { surface, location } => {
elements.extend(surface.wl_surface().into_iter().flat_map(|surface| { if let Some(wl_surface) = surface.wl_surface() {
render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( let mut geometry = surface.geometry().as_global();
geometry.loc += location;
push_render_elements_from_surface_tree(
renderer, renderer,
&surface, &wl_surface,
location location
.to_local(output) .to_local(output)
.as_logical() .as_logical()
.to_physical_precise_round(scale), .to_physical_precise_round(scale),
geometry.to_local(output).as_logical().to_f64(),
Scale::from(scale), Scale::from(scale),
1.0, 1.0,
false,
[0; 4],
None,
blur_strength,
FRAME_TIME_FILTER, FRAME_TIME_FILTER,
) &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)),
.into_iter() None,
.flat_map(crop_to_output) );
.map(Into::into) }
}));
} }
Stage::StickyPopups(layout) => { Stage::StickyPopups(layout) => {
let alpha = match &overview.0 { let alpha = match &overview.0 {
@ -905,14 +969,11 @@ where
OverviewMode::None => 1.0, OverviewMode::None => 1.0,
}; };
elements.extend( layout.render_popups(renderer, alpha, scanout_node, &mut |elem| {
layout if let Some(elem) = crop_to_output(elem.into()) {
.render_popups(renderer, alpha) elements.push(elem.into())
.into_iter() }
.map(Into::into) });
.flat_map(crop_to_output)
.map(Into::into),
);
} }
Stage::Sticky(layout) => { Stage::Sticky(layout) => {
let alpha = match &overview.0 { let alpha = match &overview.0 {
@ -937,79 +998,66 @@ where
.then_some(last_active_seat) .then_some(last_active_seat)
.map(|seat| workspace.focus_stack.get(seat)); .map(|seat| workspace.focus_stack.get(seat));
elements.extend( layout.render(
layout renderer,
.render( current_focus.as_ref().and_then(|stack| {
renderer, stack.last().and_then(|t| match t {
current_focus.as_ref().and_then(|stack| { FocusTarget::Window(w) => Some(w),
stack.last().and_then(|t| match t { _ => None,
FocusTarget::Window(w) => Some(w), })
_ => None, }),
}) resize_indicator.clone(),
}), active_hint,
resize_indicator.clone(), alpha,
active_hint, theme.cosmic(),
alpha, scanout_node,
theme.cosmic(), &mut |elem| {
) if let Some(elem) = crop_to_output(elem.into()) {
.into_iter() elements.push(elem.into())
.map(Into::into) }
.flat_map(crop_to_output) },
.map(Into::into), );
)
} }
Stage::WorkspacePopups { workspace, offset } => { Stage::WorkspacePopups { workspace, offset } => {
elements.extend( workspace.render_popups(
match workspace.render_popups( renderer,
renderer, last_active_seat,
last_active_seat, !move_active && is_active_space,
!move_active && is_active_space, overview.clone(),
overview.clone(), theme.cosmic(),
theme.cosmic(), scanout_node,
) { &mut |elem| {
Ok(elements) => { if let Some(elem) = crop_to_output(elem) {
elements elements.push(CosmicElement::Workspace(
.into_iter() RelocateRenderElement::from_element(
.flat_map(crop_to_output) elem,
.map(|element| { offset.to_physical_precise_round(scale),
CosmicElement::Workspace(RelocateRenderElement::from_element( Relocate::Relative,
element, ),
offset.to_physical_precise_round(scale), ));
Relocate::Relative,
))
})
}
Err(_) => {
return ControlFlow::Break(Err(OutputNoMode));
} }
}, },
); );
} }
Stage::Workspace { workspace, offset } => { Stage::Workspace { workspace, offset } => {
elements.extend( workspace.render(
match workspace.render( renderer,
renderer, last_active_seat,
last_active_seat, !move_active && is_active_space,
!move_active && is_active_space, overview.clone(),
overview.clone(), resize_indicator.clone(),
resize_indicator.clone(), active_hint,
active_hint, theme.cosmic(),
theme.cosmic(), scanout_node,
) { &mut |elem| {
Ok(elements) => { if let Some(elem) = crop_to_output(elem) {
elements elements.push(CosmicElement::Workspace(
.into_iter() RelocateRenderElement::from_element(
.flat_map(crop_to_output) elem,
.map(|element| { offset.to_physical_precise_round(scale),
CosmicElement::Workspace(RelocateRenderElement::from_element( Relocate::Relative,
element, ),
offset.to_physical_precise_round(scale), ));
Relocate::Relative,
))
})
}
Err(_) => {
return ControlFlow::Break(Err(OutputNoMode));
} }
}, },
); );
@ -1026,23 +1074,29 @@ fn session_lock_elements<R>(
renderer: &mut R, renderer: &mut R,
output: &Output, output: &Output,
lock_surface: Option<&LockSurface>, lock_surface: Option<&LockSurface>,
) -> Vec<WaylandSurfaceRenderElement<R>> blur_strength: usize,
where push: &mut dyn FnMut(SurfaceRenderElement<R>),
R: Renderer + ImportAll, ) where
R: AsGlowRenderer,
R::TextureId: Clone + 'static, R::TextureId: Clone + 'static,
{ {
if let Some(surface) = lock_surface { if let Some(surface) = lock_surface {
let scale = Scale::from(output.current_scale().fractional_scale()); let scale = Scale::from(output.current_scale().fractional_scale());
render_elements_from_surface_tree( push_render_elements_from_surface_tree(
renderer, renderer,
surface.wl_surface(), surface.wl_surface(),
(0, 0), (0, 0),
bbox_from_surface_tree(surface.wl_surface(), (0, 0)).to_f64(),
scale, scale,
1.0, 1.0,
false,
[0; 4],
None,
blur_strength,
FRAME_TIME_FILTER, FRAME_TIME_FILTER,
push,
None,
) )
} else {
Vec::new()
} }
} }
@ -1190,7 +1244,6 @@ pub fn render_output<'d, R>(
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicElement<R>: RenderElement<R>, CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>, WorkspaceRenderElement<R>: RenderElement<R>,
@ -1454,11 +1507,16 @@ where
} }
} }
Ok(RenderOutputResult { let buffers = render_element_buffers(renderer, &elements);
damage: res.0,
sync, Ok((
states: res.1, RenderOutputResult {
}) damage: res.0,
sync,
states: res.1,
},
buffers,
))
}, },
)? { )? {
pending_image_copy_data.send_success_when_ready( pending_image_copy_data.send_success_when_ready(
@ -1495,12 +1553,23 @@ pub fn render_workspace<'d, R>(
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicElement<R>: RenderElement<R>, CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>, WorkspaceRenderElement<R>: RenderElement<R>,
{ {
let elements: Vec<CosmicElement<R>> = workspace_elements( let mut elements: Vec<CosmicElement<R>> = if let Some(additional_damage) = additional_damage {
let output_geo = output.geometry().to_local(output).as_logical();
additional_damage
.into_iter()
.filter_map(|rect| rect.intersection(output_geo))
.map(DamageElement::new)
.map(CosmicElement::from)
.collect()
} else {
Vec::new()
};
elements.extend(workspace_elements(
gpu, gpu,
renderer, renderer,
shell, shell,
@ -1511,17 +1580,8 @@ where
current, current,
cursor_mode, cursor_mode,
element_filter, element_filter,
)?; None,
)?);
if let Some(additional_damage) = additional_damage {
let output_geo = output.geometry().to_local(output).as_logical();
let additional_damage_elements: Vec<_> = additional_damage
.into_iter()
.filter_map(|rect| rect.intersection(output_geo))
.map(DamageElement::new)
.collect();
damage_tracker.damage_output(age, &additional_damage_elements)?;
}
let res = damage_tracker.render_output( let res = damage_tracker.render_output(
renderer, renderer,

View file

@ -0,0 +1,30 @@
#version 100
//_DEFINES_
precision highp float;
varying vec2 v_coords;
uniform sampler2D tex;
uniform vec2 half_pixel;
uniform float offset;
uniform float alpha;
#if defined(DEBUG_FLAGS)
uniform float tint;
#endif
void main() {
vec4 sum = texture2D(tex, v_coords) * 4.0;
sum += texture2D(tex, v_coords - half_pixel * offset);
sum += texture2D(tex, v_coords + half_pixel * offset);
sum += texture2D(tex, v_coords + vec2(half_pixel.x, -half_pixel.y) * offset);
sum += texture2D(tex, v_coords - vec2(half_pixel.x, -half_pixel.y) * offset);
if (sum.a == 0.0) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
} else {
gl_FragColor = sum / sum.a;
}
}

View file

@ -0,0 +1,31 @@
#version 100
precision highp float;
varying vec2 v_coords;
uniform sampler2D tex;
uniform vec2 half_pixel;
uniform float offset;
uniform float alpha;
#if defined(DEBUG_FLAGS)
uniform float tint;
#endif
void main() {
vec4 sum = texture2D(tex, v_coords + vec2(-half_pixel.x * 2.0, 0.0) * offset);
sum += texture2D(tex, v_coords + vec2(-half_pixel.x, half_pixel.y) * offset) * 2.0;
sum += texture2D(tex, v_coords + vec2(0.0, half_pixel.y * 2.0) * offset);
sum += texture2D(tex, v_coords + vec2(half_pixel.x, half_pixel.y) * offset) * 2.0;
sum += texture2D(tex, v_coords + vec2(half_pixel.x * 2.0, 0.0) * offset);
sum += texture2D(tex, v_coords + vec2(half_pixel.x, -half_pixel.y) * offset) * 2.0;
sum += texture2D(tex, v_coords + vec2(0.0, -half_pixel.y * 2.0) * offset);
sum += texture2D(tex, v_coords + vec2(-half_pixel.x, -half_pixel.y) * offset) * 2.0;
if (sum.a == 0.0) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
} else {
gl_FragColor = sum / sum.a;
}
}

View file

@ -25,6 +25,7 @@ uniform float tint;
uniform vec2 geo_size; uniform vec2 geo_size;
uniform vec4 corner_radius; uniform vec4 corner_radius;
uniform mat3 input_to_geo; uniform mat3 input_to_geo;
uniform float noise;
float rounding_alpha(vec2 coords, vec2 size) { float rounding_alpha(vec2 coords, vec2 size) {
vec2 center; vec2 center;
@ -51,15 +52,26 @@ float rounding_alpha(vec2 coords, vec2 size) {
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
} }
void main() { float hash(vec2 p) {
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0); vec3 p3 = fract(vec3(p.xyx) * 727.727);
p3 += dot(p3, p3.xyz + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
void main() {
if (alpha <= 0.0) {
discard;
}
// Sample the texture.
vec4 color = texture2D(tex, v_coords); vec4 color = texture2D(tex, v_coords);
#if defined(NO_ALPHA) #if defined(NO_ALPHA)
color = vec4(color.rgb, 1.0); color = vec4(color.rgb, 1.0);
#endif #endif
if (color.a <= 0.0) {
discard;
}
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0);
if (coords_geo.x < 0.0 || 1.0 < coords_geo.x || coords_geo.y < 0.0 || 1.0 < coords_geo.y) { if (coords_geo.x < 0.0 || 1.0 < coords_geo.x || coords_geo.y < 0.0 || 1.0 < coords_geo.y) {
// Clip outside geometry. // Clip outside geometry.
color = vec4(0.0); color = vec4(0.0);
@ -68,8 +80,20 @@ void main() {
color = color * rounding_alpha(coords_geo.xy * geo_size, geo_size); color = color * rounding_alpha(coords_geo.xy * geo_size, geo_size);
} }
if (color.a <= 0.0) {
discard;
}
if (noise > 0.0) {
// Add noise fx
// This can be used to achieve a glass look
float noiseHash = hash(v_coords);
float noiseAmount = (mod(noiseHash, 1.0) - 0.5);
color.rgb += noiseAmount * noise;
}
// Apply final alpha and tint. // Apply final alpha and tint.
color = color * alpha; color *= alpha;
#if defined(DEBUG_FLAGS) #if defined(DEBUG_FLAGS)
if (tint == 1.0) if (tint == 1.0)

View file

@ -1,6 +1,6 @@
use std::{borrow::Borrow, cell::RefCell, collections::HashMap}; use std::{borrow::Borrow, cell::RefCell, collections::HashMap};
use cgmath::{Matrix3, Vector2}; use glam::{Affine2, Mat3, Vec2};
use smithay::{ use smithay::{
backend::renderer::{ backend::renderer::{
element::Kind, element::Kind,
@ -113,26 +113,26 @@ impl ShadowShader {
let mut shader_geo = Rectangle::new(Point::from((-width, -width)), shader_size); let mut shader_geo = Rectangle::new(Point::from((-width, -width)), shader_size);
let window_geo = Rectangle::new(Point::new(0., 0.) - offset - shader_geo.loc, geo.size); let window_geo = Rectangle::new(Point::new(0., 0.) - offset - shader_geo.loc, geo.size);
let area_size = Vector2::new(shader_geo.size.w, shader_geo.size.h); let area_size = Vec2::new(shader_geo.size.w as f32, shader_geo.size.h as f32);
let geo_loc = Vector2::new(-shader_geo.loc.x, -shader_geo.loc.y); let geo_loc = Vec2::new(-shader_geo.loc.x as f32, -shader_geo.loc.y as f32);
shader_geo.loc += offset + geo.loc; shader_geo.loc += offset + geo.loc;
let input_to_geo = (Matrix3::from_nonuniform_scale(area_size.x, area_size.y) let input_to_geo = Mat3::from(
* Matrix3::from_translation(Vector2::new( Affine2::from_scale(area_size)
-geo_loc.x / area_size.x, * Affine2::from_translation(Vec2::new(
-geo_loc.y / area_size.y, -geo_loc.x / area_size.x,
))) -geo_loc.y / area_size.y,
.cast::<f32>() )),
.unwrap(); );
let window_geo_loc = Vector2::new(window_geo.loc.x, window_geo.loc.y); let window_geo_loc = Vec2::new(window_geo.loc.x as f32, window_geo.loc.y as f32);
let window_input_to_geo = (Matrix3::from_nonuniform_scale(area_size.x, area_size.y) let window_input_to_geo = Mat3::from(
* Matrix3::from_translation(Vector2::new( Affine2::from_scale(area_size)
-window_geo_loc.x / area_size.x, * Affine2::from_translation(Vec2::new(
-window_geo_loc.y / area_size.y, -window_geo_loc.x / area_size.x,
))) -window_geo_loc.y / area_size.y,
.cast::<f32>() )),
.unwrap(); );
let element = PixelShaderElement::new( let element = PixelShaderElement::new(
shader, shader,

View file

@ -0,0 +1,705 @@
use std::{
borrow::{Borrow, BorrowMut},
sync::{LazyLock, Mutex},
};
use glam::{Affine2, Mat3, Vec2};
use smithay::{
backend::{
allocator::Fourcc,
renderer::{
Bind, BlitFrame, Color32F, ContextId, Frame, FrameContext, ImportAll, Offscreen,
Renderer, Texture, TextureFilter,
element::{Element, Id, Kind, RenderElement},
gles::{
GlesError, GlesFrame, GlesRenderer, GlesTexProgram, GlesTexture, Uniform,
UniformName, UniformType, UniformValue, ffi,
},
sync::SyncPoint,
utils::{CommitCounter, DamageSet},
},
},
utils::{
Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform, user_data::UserDataMap,
},
wayland::compositor::SurfaceData,
};
use tracing::trace;
use crate::{
backend::render::{element::AsGlowRenderer, wayland::clipped_surface::ClippingShader},
wayland::handlers::background_effect::ComputedBlurRegionCachedState,
};
pub static BLUR_DOWNSAMPLE_SHADER: &str = include_str!("../shaders/blur_downsample.frag");
pub static BLUR_UPSAMPLE_SHADER: &str = include_str!("../shaders/blur_upsample.frag");
const NOISE: f32 = 0.03;
const MAX_STEPS: usize = 15;
#[derive(Debug, Clone, Copy, PartialEq)]
struct BlurParameters {
passes: usize,
offset: f64,
extended_radius: i32,
}
static BLUR_PARAMS: LazyLock<Vec<BlurParameters>> = LazyLock::new(|| {
let mut params = Vec::new();
let mut remaining_steps = MAX_STEPS as isize;
let offsets = [
// min offset, max offset, extended radius to avoid artifacts
(1.0, 2.0, 10),
(2.0, 3.0, 20),
(2.0, 5.0, 50),
(3.0, 8.0, 150),
];
let sum = offsets.iter().map(|(min, max, _)| *max - *min).sum::<f64>();
for (i, (min, max, extended_radius)) in offsets.into_iter().enumerate() {
let mut iter_num = f64::ceil((max - min) / sum * (MAX_STEPS as f64)) as usize;
remaining_steps -= iter_num as isize;
if remaining_steps < 0 {
iter_num = iter_num.saturating_add_signed(remaining_steps);
}
let diff = max - min;
for j in 1..=iter_num {
params.push(BlurParameters {
passes: i + 1,
offset: min + (diff / iter_num as f64) * j as f64,
extended_radius,
});
}
}
trace!("Computed blur values: {:#?}", &params);
params
});
#[derive(Debug, Clone)]
pub struct BlurShaders {
down: GlesTexProgram,
up: GlesTexProgram,
}
impl BlurShaders {
pub fn compile(renderer: &mut GlesRenderer) -> Result<BlurShaders, GlesError> {
let up = renderer.compile_custom_texture_shader(
BLUR_UPSAMPLE_SHADER,
&[
UniformName::new("half_pixel", UniformType::_2f),
UniformName::new("offset", UniformType::_1f),
],
)?;
let down = renderer.compile_custom_texture_shader(
BLUR_DOWNSAMPLE_SHADER,
&[
UniformName::new("half_pixel", UniformType::_2f),
UniformName::new("offset", UniformType::_1f),
],
)?;
Ok(BlurShaders { up, down })
}
pub fn get<R: AsGlowRenderer>(renderer: &R) -> Self {
Borrow::<GlesRenderer>::borrow(renderer.glow_renderer())
.egl_context()
.user_data()
.get::<BlurShaders>()
.expect("Custom Shaders not initialized")
.clone()
}
}
type BlurTexture<T> = Mutex<Option<T>>;
#[derive(Debug)]
pub struct BlurState {
pub id: Id,
pub renderer_id: Option<ContextId<GlesTexture>>,
pub src: Size<f64, Buffer>,
pub offset: f64,
pub passes: usize,
pub region: Vec<Rectangle<i32, Logical>>,
pub commit: CommitCounter,
}
unsafe impl Send for BlurState {}
unsafe impl Sync for BlurState {}
impl Default for BlurState {
fn default() -> Self {
BlurState {
id: Id::new(),
renderer_id: None,
src: Size::new(0., 0.),
offset: 0.,
passes: 0,
region: Vec::new(),
commit: CommitCounter::default(),
}
}
}
pub struct BlurElement {
id: Id,
commit: CommitCounter,
src: Size<f64, Buffer>,
extended_offset: Point<f64, Logical>,
geometry: Rectangle<f64, Logical>,
scaling_shaders: BlurShaders,
render_shader: GlesTexProgram,
region: Vec<Rectangle<i32, Logical>>,
offset: f64,
passes: usize,
uniforms: Vec<Uniform<'static>>,
}
impl BlurElement {
pub fn from_state<R: ImportAll + AsGlowRenderer>(
renderer: &mut R,
state: &mut BlurState,
geometry: Rectangle<f64, Logical>,
output_scale: f64,
radii: [u8; 4],
strength: usize,
) -> Result<Option<Self>, R::Error> {
let region = vec![Rectangle::from_size(geometry.size.to_i32_round())];
Self::internal(
renderer,
state,
geometry,
&region,
output_scale,
radii,
strength,
)
}
pub fn from_surface<R: ImportAll + AsGlowRenderer>(
renderer: &mut R,
states: &SurfaceData,
geometry: Rectangle<f64, Logical>,
output_scale: f64,
radii: [u8; 4],
strength: usize,
) -> Result<Option<Self>, R::Error> {
let mut blur_region_state = states.cached_state.get::<ComputedBlurRegionCachedState>();
let Some(region) = blur_region_state.current().blur_region.as_ref() else {
return Ok(None);
};
let state = states
.data_map
.get_or_insert_threadsafe::<Mutex<BlurState>, _>(Default::default);
Self::internal(
renderer,
&mut state.lock().unwrap(),
geometry,
region,
output_scale,
radii,
strength,
)
}
pub fn internal<R: ImportAll + AsGlowRenderer>(
renderer: &mut R,
state: &mut BlurState,
geometry: Rectangle<f64, Logical>,
region: &Vec<Rectangle<i32, Logical>>,
output_scale: f64,
radii: [u8; 4],
strength: usize,
) -> Result<Option<Self>, R::Error> {
if strength == 0 || geometry.size.w == 0. || geometry.size.h == 0. {
return Ok(None);
}
let geo = geometry.to_physical_precise_round(output_scale);
let mut extended_geo = geo;
let radius = BLUR_PARAMS[(strength + 2).min(MAX_STEPS - 1)].extended_radius as f64;
extended_geo.loc -= Point::<f64, Physical>::new(radius, radius);
extended_geo.size += Size::<f64, Physical>::new(radius, radius).upscale(2.);
// compute input_to_geo so that it crops the extended capture radius
let geo_scale = {
let Scale { x, y } = geo.size / extended_geo.size;
Affine2::from_scale(Vec2::new(x as f32, y as f32)).inverse()
};
let geo_translation = {
let offset = geo.loc - extended_geo.loc;
Affine2::from_translation(-Vec2::new(
(offset.x / extended_geo.size.w) as f32,
(offset.y / extended_geo.size.h) as f32,
))
};
let input_to_geo = Mat3::from(geo_scale * geo_translation);
let uniforms = vec![
Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)),
Uniform::new(
"corner_radius",
[
radii[3] as f32,
radii[1] as f32,
radii[0] as f32,
radii[2] as f32,
],
),
Uniform::new(
"input_to_geo",
UniformValue::Matrix3x3 {
matrices: vec![*AsRef::<[f32; 9]>::as_ref(&input_to_geo)],
transpose: false,
},
),
Uniform::new("noise", UniformValue::_1f(NOISE)),
];
let geometry = extended_geo.to_logical(output_scale);
let extended_offset = Point::<f64, Physical>::new(radius, radius).to_logical(output_scale);
let renderer_id = renderer.glow_renderer().context_id();
let src = geometry.size.to_buffer(output_scale, Transform::Normal);
let params = &BLUR_PARAMS[strength.min(MAX_STEPS - 1)];
let dirty = !(state
.renderer_id
.as_ref()
.is_some_and(|id| id == &renderer_id)
&& state.offset == params.offset
&& state.passes == params.passes
&& &state.region == region
&& state.src == src);
state.renderer_id = Some(renderer_id);
state.offset = params.offset;
state.passes = params.passes;
state.region = region.clone();
state.src = src;
if dirty {
state.commit.increment();
}
Ok(Some(BlurElement {
id: state.id.clone(),
commit: state.commit,
src,
geometry,
extended_offset,
scaling_shaders: BlurShaders::get(renderer),
render_shader: ClippingShader::get(renderer),
offset: state.offset,
passes: state.passes,
region: region
.iter()
.cloned()
.map(|mut rect| {
rect.loc += extended_offset.to_i32_round();
rect
})
.collect(),
uniforms,
}))
}
}
impl Element for BlurElement {
fn id(&self) -> &Id {
&self.id
}
fn current_commit(&self) -> CommitCounter {
self.commit
}
fn src(&self) -> Rectangle<f64, Buffer> {
Rectangle::from_size(self.src)
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
self.geometry.to_physical_precise_round(scale)
}
fn transform(&self) -> Transform {
Transform::Normal
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
if self.commit.distance(commit).is_none_or(|d| d > 0) {
DamageSet::from_slice(&[Rectangle::new(
self.extended_offset.to_physical_precise_round(scale),
self.geometry.size.to_physical_precise_round(scale)
- self
.extended_offset
.to_size()
.upscale(2.)
.to_physical_precise_round(scale),
)])
} else {
DamageSet::default()
}
}
fn alpha(&self) -> f32 {
1.0
}
fn kind(&self) -> Kind {
Kind::default()
}
fn is_framebuffer_effect(&self) -> bool {
true
}
}
impl<R: Renderer + AsGlowRenderer> RenderElement<R> for BlurElement
where
R::TextureId: Send + 'static,
{
fn capture_framebuffer(
&self,
frame: &mut <R>::Frame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
cache: &UserDataMap,
) -> Result<(), <R>::Error> {
let transform = frame.transformation();
let tex_size = self.src.to_i32_round();
let glow_frame = <R as AsGlowRenderer>::glow_frame_mut(frame);
let gles_frame = BorrowMut::<GlesFrame<'_, '_>>::borrow_mut(glow_frame);
let mut renderer = gles_frame.renderer();
let texture_ref = cache.get_or_insert_threadsafe(BlurTexture::<R::TextureId>::default);
let mut texture_entry = texture_ref.lock().unwrap();
if texture_entry.as_ref().is_some_and(|tex| {
tex.size() != tex_size
|| R::tex_to_gl(
&renderer.as_ref().context_id(),
texture_entry.as_ref().unwrap(),
)
.is_none()
}) {
texture_entry.take();
}
if texture_entry.is_none() {
let gl_texture = renderer
.as_mut()
.create_buffer(Fourcc::Abgr8888, tex_size)
.map_err(R::from_gles_error)?;
*texture_entry = Some(R::tex_from_gl(&renderer.as_ref().context_id(), gl_texture));
}
let mut texture = R::tex_to_gl(
&renderer.as_ref().context_id(),
texture_entry.as_ref().unwrap(),
)
.unwrap();
let mut off_texture = renderer
.as_mut()
.create_buffer(Fourcc::Abgr8888, tex_size)
.map_err(R::from_gles_error)?;
std::mem::drop(renderer);
let sync = blit_from_active_fb(gles_frame, src, dst, transform, &mut texture)
.map_err(R::from_gles_error)?;
gles_frame.wait(&sync).map_err(R::from_gles_error)?;
let mut textures = [&mut texture, &mut off_texture];
render_blur(
gles_frame.renderer().as_mut(),
&self.scaling_shaders,
&mut textures,
self.offset,
self.passes,
)
.map_err(R::from_gles_error)?;
Ok(())
}
fn draw(
&self,
frame: &mut R::Frame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
opaque_regions: &[Rectangle<i32, Physical>],
cache: Option<&UserDataMap>,
) -> Result<(), R::Error> {
let src_to_geo = self.geometry.size / self.src;
let src_log = src
.upscale(src_to_geo)
.to_logical(1., Transform::Normal, &Size::default());
let scale = dst.size.to_f64() / src_log.size;
let damage = self
.region
.iter()
.flat_map(|rect| {
let mut rect = rect.to_f64().intersection(src_log)?;
rect.loc -= src_log.loc;
Some(rect.to_physical_precise_round(scale))
})
.flat_map(|rect| damage.iter().flat_map(move |r| r.intersection(rect)))
.collect::<Vec<_>>();
let cache = cache.expect("Framebuffer element without cache?");
let Some(texture) = cache.get::<BlurTexture<R::TextureId>>() else {
return Err(R::from_gles_error(GlesError::BlitError));
};
let texture_ref = texture.lock().unwrap();
if let Some(tex) = texture_ref.as_ref() {
BorrowMut::<GlesFrame>::borrow_mut(<R as AsGlowRenderer>::glow_frame_mut(frame))
.override_default_tex_program(self.render_shader.clone(), self.uniforms.clone());
frame.render_texture_from_to(
tex,
src,
dst,
&damage,
opaque_regions,
Transform::Normal,
1.0,
)?;
BorrowMut::<GlesFrame>::borrow_mut(<R as AsGlowRenderer>::glow_frame_mut(frame))
.clear_tex_program_override();
}
Ok(())
}
}
fn blit_from_active_fb(
frame: &mut GlesFrame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
transform: Transform,
to_texture: &mut GlesTexture,
) -> Result<SyncPoint, GlesError> {
let tex_size = to_texture.size();
let tex_size_phys = tex_size.to_logical(1, Transform::Normal).to_physical(1);
let fb_size = frame.output_size();
let mut renderer = frame.renderer();
let mut fb = renderer.as_mut().bind(to_texture)?;
let sync = {
let mut subframe = renderer
.as_mut()
.render(&mut fb, tex_size_phys, Transform::Normal)?;
subframe.clear(
Color32F::TRANSPARENT,
&[Rectangle::from_size(tex_size_phys)],
)?;
subframe.finish()?
};
if transform != Transform::Normal {
// We need to copy to a temporary texture to do an actual
// render pass with `render_texture_from_to` to do the rotation.
// dst is in screen space, but we just want to do a 1:1 copy in
// buffer space from dst of the current fb, so we need to undo any
// transforms for the blit.
let dst_phys = transform.transform_rect_in(dst, &fb_size);
let dst_buffer = dst_phys
.to_logical(1)
.to_buffer(1, Transform::Normal, &Size::default());
let mut tmp_texture = renderer
.as_mut()
.create_buffer(Fourcc::Abgr8888, dst_buffer.size)?;
let mut fb_tmp = renderer.as_mut().bind(&mut tmp_texture)?;
std::mem::drop(renderer);
frame.wait(&sync)?;
let sync = frame.blit_to(
&mut fb_tmp,
dst_phys,
Rectangle::from_size(dst_phys.size),
TextureFilter::Linear,
)?;
frame.wait(&sync)?;
std::mem::drop(fb_tmp);
// now we bind the target texture with `Transform::Normal`
// and render the temporary texture with inverse transform
// into src.
let mut renderer = frame.renderer();
let mut frame = renderer
.as_mut()
.render(&mut fb, tex_size_phys, Transform::Normal)?;
frame.wait(&sync)?;
Frame::render_texture_from_to(
&mut frame,
&tmp_texture,
Rectangle::from_size(dst_buffer.size.to_f64()),
src.to_logical(1., Transform::Normal, &Size::default())
.to_physical(1.)
.to_i32_round(),
&[Rectangle::from_size(dst.size)],
&[Rectangle::from_size(dst.size)],
transform.invert(),
1.0,
)?;
std::mem::drop(tmp_texture);
frame.finish()
} else {
std::mem::drop(renderer);
frame.wait(&sync)?;
frame.blit_to(
&mut fb,
dst,
src.to_logical(1., Transform::Normal, &Size::default())
.to_physical(1.)
.to_i32_round(),
TextureFilter::Linear,
)
}
}
fn render_blur(
renderer: &mut GlesRenderer,
shaders: &BlurShaders,
textures: &mut [&mut GlesTexture; 2],
offset: f64,
passes: usize,
) -> Result<(), GlesError> {
for i in 0..passes {
let tex_size = textures[0].size();
let [src_tex, target_tex] = textures;
let mut fb = renderer.bind(*target_tex)?;
let adjusted_tex_size = tex_size.downscale(1 << i);
let target_tex_size = tex_size
.downscale(1 << (i + 1))
.to_logical(1, Transform::Normal)
.to_physical(1);
let half_pixel = [
0.5 / (adjusted_tex_size.w as f32),
0.5 / (adjusted_tex_size.h as f32),
];
let mut frame = renderer.render(
&mut fb,
tex_size.to_logical(1, Transform::Normal).to_physical(1),
Transform::Normal,
)?;
frame.clear(
Color32F::new(0., 0., 0., 0.),
&[Rectangle::from_size(
tex_size.to_logical(1, Transform::Normal).to_physical(1),
)],
)?;
frame.with_context(|gl| unsafe {
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_S,
ffi::CLAMP_TO_EDGE as i32,
);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_T,
ffi::CLAMP_TO_EDGE as i32,
);
})?;
frame.render_texture_from_to(
src_tex,
Rectangle::from_size(adjusted_tex_size.to_f64()),
Rectangle::from_size(target_tex_size),
&[Rectangle::from_size(target_tex_size)],
&[Rectangle::from_size(target_tex_size)],
Transform::Normal,
1.0,
Some(&shaders.down),
&[
Uniform::new("half_pixel", half_pixel),
Uniform::new("offset", (offset / (1 << i) as f64) as f32),
],
)?;
frame.with_context(|gl| unsafe {
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::REPEAT as i32);
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::REPEAT as i32);
})?;
let sync = frame.finish()?;
std::mem::drop(fb);
renderer.wait(&sync)?;
textures.swap(0, 1);
}
for i in 0..passes {
let tex_size = textures[0].size();
let [src_tex, target_tex] = textures;
let mut fb = renderer.bind(*target_tex)?;
let adjusted_tex_size = tex_size.downscale(1 << (passes - i));
let target_tex_size = tex_size
.downscale(1 << (passes - i - 1))
.to_logical(1, Transform::Normal)
.to_physical(1);
let half_pixel = [
0.5 / (adjusted_tex_size.w as f32),
0.5 / (adjusted_tex_size.h as f32),
];
let mut frame = renderer.render(
&mut fb,
tex_size.to_logical(1, Transform::Normal).to_physical(1),
Transform::Normal,
)?;
frame.clear(
Color32F::new(0., 0., 0., 0.),
&[Rectangle::from_size(
tex_size.to_logical(1, Transform::Normal).to_physical(1),
)],
)?;
frame.with_context(|gl| unsafe {
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_S,
ffi::CLAMP_TO_EDGE as i32,
);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_T,
ffi::CLAMP_TO_EDGE as i32,
);
})?;
frame.render_texture_from_to(
src_tex,
Rectangle::from_size(adjusted_tex_size.to_f64()),
Rectangle::from_size(target_tex_size),
&[Rectangle::from_size(target_tex_size)],
&[Rectangle::from_size(target_tex_size)],
Transform::Normal,
1.0,
Some(&shaders.up),
&[
Uniform::new("half_pixel", half_pixel),
Uniform::new("offset", (offset / (1 << (passes - i)) as f64) as f32),
],
)?;
frame.with_context(|gl| unsafe {
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::REPEAT as i32);
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::REPEAT as i32);
})?;
let sync = frame.finish()?;
std::mem::drop(fb);
renderer.wait(&sync)?;
textures.swap(0, 1);
}
// textures always end up the right way around with `self.texture` containing our final render,
// since we render PASSES * 2 (downscale and upscale), so the number of swaps is always even.
Ok(())
}

View file

@ -2,11 +2,11 @@
use std::borrow::{Borrow, BorrowMut}; use std::borrow::{Borrow, BorrowMut};
use cgmath::{Matrix3, Vector2}; use glam::{Affine2, Mat3, Vec2};
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform}; use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use smithay::{ use smithay::{
backend::renderer::{ backend::renderer::{
ImportAll, ImportMem, Renderer, ImportAll, Renderer,
element::{ element::{
Element, Id, Kind, RenderElement, UnderlyingStorage, Element, Id, Kind, RenderElement, UnderlyingStorage,
surface::WaylandSurfaceRenderElement, surface::WaylandSurfaceRenderElement,
@ -19,7 +19,7 @@ use smithay::{
use crate::backend::render::element::AsGlowRenderer; use crate::backend::render::element::AsGlowRenderer;
pub static CLIPPING_SHADER: &str = include_str!("./shaders/clipped_surface.frag"); pub static CLIPPING_SHADER: &str = include_str!("../shaders/clipped_surface.frag");
pub struct ClippingShader(pub GlesTexProgram); pub struct ClippingShader(pub GlesTexProgram);
impl ClippingShader { impl ClippingShader {
@ -35,10 +35,7 @@ impl ClippingShader {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ClippedSurfaceRenderElement<R> pub struct ClippedSurfaceRenderElement<R: Renderer> {
where
R: Renderer + ImportAll + ImportMem,
{
inner: WaylandSurfaceRenderElement<R>, inner: WaylandSurfaceRenderElement<R>,
program: GlesTexProgram, program: GlesTexProgram,
radius: [u8; 4], radius: [u8; 4],
@ -48,7 +45,7 @@ where
impl<R> ClippedSurfaceRenderElement<R> impl<R> ClippedSurfaceRenderElement<R>
where where
R: Renderer + ImportAll + ImportMem, R: Renderer + ImportAll,
{ {
pub fn new( pub fn new(
renderer: &mut R, renderer: &mut R,
@ -66,18 +63,18 @@ where
let view = elem.view(); let view = elem.view();
let transform = elem.transform(); let transform = elem.transform();
let transform_matrix = Matrix3::<f32>::from_translation(Vector2::new(0.5, 0.5)) let transform_matrix = Affine2::from_translation(Vec2::new(0.5, 0.5))
* transform.matrix() * transform.matrix()
* Matrix3::<f32>::from_translation(-Vector2::new(0.5, 0.5)); * Affine2::from_translation(-Vec2::new(0.5, 0.5));
let geo_scale = { let geo_scale = {
let Scale { x, y } = elem_geo.size.to_f64() / geo.size.to_f64(); let Scale { x, y } = elem_geo.size.to_f64() / geo.size.to_f64();
Matrix3::from_nonuniform_scale(x as f32, y as f32) Affine2::from_scale(Vec2::new(x as f32, y as f32))
}; };
let geo_translation = { let geo_translation = {
let offset = (elem_geo.loc - geo.loc).to_f64(); let offset = (elem_geo.loc - geo.loc).to_f64();
Matrix3::from_translation(Vector2::new( Affine2::from_translation(Vec2::new(
(offset.x / elem_geo.size.w as f64) as f32, (offset.x / elem_geo.size.w as f64) as f32,
(offset.y / elem_geo.size.h as f64) as f32, (offset.y / elem_geo.size.h as f64) as f32,
)) ))
@ -85,16 +82,17 @@ where
let buf_scale = { let buf_scale = {
let Scale { x, y } = buf_size.to_f64() / view.src.size.to_f64(); let Scale { x, y } = buf_size.to_f64() / view.src.size.to_f64();
Matrix3::from_nonuniform_scale(x as f32, y as f32) Affine2::from_scale(Vec2::new(x as f32, y as f32))
}; };
let buf_translation = Matrix3::from_translation(Vector2::new( let buf_translation = Affine2::from_translation(Vec2::new(
(view.src.loc.x / buf_size.w as f64) as f32, (view.src.loc.x / buf_size.w as f64) as f32,
(view.src.loc.y / buf_size.h as f64) as f32, (view.src.loc.y / buf_size.h as f64) as f32,
)); ));
let input_to_geo = let input_to_geo = Mat3::from(
transform_matrix * geo_scale * geo_translation * buf_scale * buf_translation; transform_matrix * geo_scale * geo_translation * buf_scale * buf_translation,
);
let uniforms = vec![ let uniforms = vec![
Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)), Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)),
@ -114,6 +112,7 @@ where
transpose: false, transpose: false,
}, },
), ),
Uniform::new("noise", UniformValue::_1f(0.0)),
]; ];
Self { Self {
@ -174,7 +173,8 @@ where
impl<R> Element for ClippedSurfaceRenderElement<R> impl<R> Element for ClippedSurfaceRenderElement<R>
where where
R: Renderer + ImportAll + ImportMem, R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: 'static,
{ {
fn id(&self) -> &Id { fn id(&self) -> &Id {
self.inner.id() self.inner.id()
@ -247,7 +247,7 @@ where
impl<R> RenderElement<R> for ClippedSurfaceRenderElement<R> impl<R> RenderElement<R> for ClippedSurfaceRenderElement<R>
where where
R: AsGlowRenderer + Renderer + ImportAll + ImportMem, R: AsGlowRenderer + Renderer + ImportAll,
R::TextureId: 'static, R::TextureId: 'static,
{ {
fn draw( fn draw(

View file

@ -0,0 +1,145 @@
use smithay::{
backend::renderer::{
ImportAll, Renderer,
element::surface::{KindEvaluation, WaylandSurfaceRenderElement},
utils::RendererSurfaceStateUserData,
},
reexports::wayland_server::protocol::wl_surface,
render_elements,
utils::{Logical, Physical, Point, Rectangle, Scale},
wayland::compositor::{self, TraversalAction},
};
use tracing::warn;
use crate::backend::render::{
element::AsGlowRenderer,
wayland::{blur_effect::BlurElement, clipped_surface::ClippedSurfaceRenderElement},
};
pub mod blur_effect;
pub mod clipped_surface;
render_elements! {
pub SurfaceRenderElement<R> where R: AsGlowRenderer + ImportAll, R::TextureId: Send;
Blur=BlurElement,
Clipped=ClippedSurfaceRenderElement<R>,
Wayland=WaylandSurfaceRenderElement<R>,
}
pub fn push_render_elements_from_surface_tree<R>(
renderer: &mut R,
main_surface: &wl_surface::WlSurface,
location: impl Into<Point<i32, Physical>>,
geometry: impl Into<Rectangle<f64, Logical>>,
scale: impl Into<Scale<f64>>,
alpha: f32,
should_clip: bool,
radii: [u8; 4],
blur_geometry: impl Into<Option<Rectangle<f64, Logical>>>,
blur_strength: usize,
kind: impl Into<KindEvaluation>,
push_above: &mut dyn FnMut(SurfaceRenderElement<R>),
mut push_below: Option<&mut dyn FnMut(SurfaceRenderElement<R>)>,
) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static,
{
let location = location.into().to_f64();
let geometry = geometry.into();
let blur_geometry = blur_geometry.into();
let scale = scale.into();
let kind = kind.into();
let mut passed_main = false;
compositor::with_surface_tree_downward(
main_surface,
location,
|_, states, location| {
let mut location = *location;
let data = states.data_map.get::<RendererSurfaceStateUserData>();
if let Some(data) = data {
if let Some(view) = data.lock().unwrap().view() {
location += view.offset.to_f64().to_physical(scale);
TraversalAction::DoChildren(location)
} else {
TraversalAction::SkipChildren
}
} else {
TraversalAction::SkipChildren
}
},
|surface, states, location| {
let mut location = *location;
let kind = kind.eval(states);
let data = states.data_map.get::<RendererSurfaceStateUserData>();
let mut blur = Ok(None);
if let Some(data) = data {
let has_view = if let Some(view) = data.lock().unwrap().view() {
location += view.offset.to_f64().to_physical(scale);
true
} else {
false
};
if has_view {
match WaylandSurfaceRenderElement::from_surface(
renderer, surface, states, location, alpha, kind,
) {
Ok(Some(surface)) => {
let blur_geo = blur_geometry.unwrap_or(geometry);
blur = BlurElement::from_surface(
renderer,
states,
blur_geo,
scale.x,
radii,
blur_strength,
);
let elem: SurfaceRenderElement<R> = if radii.iter().any(|r| *r != 0)
&& should_clip
&& ClippedSurfaceRenderElement::will_clip(
&surface, scale, geometry, radii,
) {
ClippedSurfaceRenderElement::new(
renderer, surface, scale, geometry, radii,
)
.into()
} else {
surface.into()
};
if let Some(push_below) = push_below.as_mut()
&& passed_main
{
push_below(elem);
} else {
push_above(elem);
}
}
Ok(None) => {} // surface is not mapped
Err(err) => {
warn!("Failed to import surface: {}", err);
}
};
}
}
if surface == main_surface {
passed_main = true;
}
if let Ok(Some(elem)) = blur {
if let Some(push_below) = push_below.as_mut()
&& passed_main
{
push_below(elem.into());
} else {
push_above(elem.into());
}
}
},
|_, _, _| true,
);
}

View file

@ -25,7 +25,8 @@ use crate::{
}, },
utils::{prelude::*, quirks::workspace_overview_is_open}, utils::{prelude::*, quirks::workspace_overview_is_open},
wayland::handlers::{ wayland::handlers::{
image_copy_capture::SessionHolder, xwayland_keyboard_grab::XWaylandGrabSeat, image_copy_capture::{SessionHolder, cursor_capture_constraints},
xwayland_keyboard_grab::XWaylandGrabSeat,
}, },
}; };
use calloop::{ use calloop::{
@ -35,14 +36,15 @@ use calloop::{
use cosmic_comp_config::{NumlockState, workspace::WorkspaceLayout}; use cosmic_comp_config::{NumlockState, workspace::WorkspaceLayout};
use cosmic_settings_config::shortcuts; use cosmic_settings_config::shortcuts;
use cosmic_settings_config::shortcuts::action::{Direction, ResizeDirection}; use cosmic_settings_config::shortcuts::action::{Direction, ResizeDirection};
#[cfg(feature = "logind")]
use smithay::backend::input::{Switch, SwitchState, SwitchToggleEvent};
use smithay::{ use smithay::{
backend::input::{ backend::input::{
AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, Device, DeviceCapability, AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, Device, DeviceCapability,
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _,
GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState, KeyboardKeyEvent, GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState, KeyboardKeyEvent,
PointerAxisEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent, PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
TabletToolTipState, TouchEvent,
}, },
desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree}, desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree},
input::{ input::{
@ -59,22 +61,19 @@ use smithay::{
output::Output, output::Output,
reexports::{ reexports::{
input::Device as InputDevice, input::Device as InputDevice,
wayland_server::{ wayland_server::{Resource as _, protocol::wl_surface::WlSurface},
Resource as _,
protocol::{wl_shm::Format as ShmFormat, wl_surface::WlSurface},
},
}, },
utils::{Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size}, utils::{Clock, Logical, Monotonic, Point, Rectangle, SERIAL_COUNTER, Serial},
wayland::{ wayland::{
compositor::CompositorHandler, compositor::CompositorHandler,
image_copy_capture::{BufferConstraints, CursorSessionRef}, image_copy_capture::CursorSessionRef,
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat,
pointer_constraints::{PointerConstraint, with_pointer_constraint}, pointer_constraints::{PointerConstraint, with_pointer_constraint},
seat::WaylandFocus, seat::WaylandFocus,
tablet_manager::{TabletDescriptor, TabletSeatTrait}, tablet_manager::{TabletDescriptor, TabletSeatTrait},
}, },
}; };
use tracing::{error, trace, warn}; use tracing::{error, trace};
use xkbcommon::xkb::{Keycode, Keysym}; use xkbcommon::xkb::{Keycode, Keysym};
use std::{ use std::{
@ -336,7 +335,7 @@ impl State {
// Constraint does not apply if not within region // Constraint does not apply if not within region
if !constraint.region().is_none_or(|x| { if !constraint.region().is_none_or(|x| {
x.contains( x.contains(
(ptr.current_location() - *surface_loc).to_i32_round(), (ptr.current_location() - *surface_loc).to_i32_floor(),
) )
}) { }) {
return; return;
@ -381,14 +380,25 @@ impl State {
.unwrap_or(current_output.clone()); .unwrap_or(current_output.clone());
drop(shell); drop(shell);
let output_geometry = output.geometry(); let output_geometry = output.geometry();
position.x = position.x.clamp(
output_geometry.loc.x as f64, let scale = output.current_scale().fractional_scale();
(output_geometry.loc.x + output_geometry.size.w - 1) as f64, let physical = output
); .current_mode()
position.y = position.y.clamp( .map(|mode| output.current_transform().transform_size(mode.size))
output_geometry.loc.y as f64, .unwrap_or_default();
(output_geometry.loc.y + output_geometry.size.h - 1) as f64, let logical = physical.to_f64().to_logical(scale);
); let output_geometry_loc = output_geometry.loc.to_f64();
// output_geometry.size is a rounded value and may undershoot/overshoot the accurate logical size
// We constrain the position with:
// - output_geometry.size so that we don't send leave events to a fullscreen app
// - logical size so that the position doesn't end up outside the actual size of the output
// See https://github.com/pop-os/cosmic-comp/pull/2568
let max_x = output_geometry_loc.x
+ logical.w.min(output_geometry.size.w as f64).next_down();
let max_y = output_geometry_loc.y
+ logical.h.min(output_geometry.size.h as f64).next_down();
position.x = position.x.clamp(output_geometry_loc.x, max_x);
position.y = position.y.clamp(output_geometry_loc.y, max_y);
if ptr.is_grabbed() { if ptr.is_grabbed() {
if seat if seat
@ -542,7 +552,7 @@ impl State {
if let Some(region) = &confine_region if let Some(region) = &confine_region
&& !region && !region
.contains((pos.as_logical() - *surface_loc).to_i32_round()) .contains((pos.as_logical() - *surface_loc).to_i32_floor())
{ {
return (false, None); return (false, None);
} }
@ -613,7 +623,7 @@ impl State {
PointerConstraint::Confined(confined) => confined.region(), PointerConstraint::Confined(confined) => confined.region(),
}; };
let point = let point =
(ptr.current_location() - surface_location).to_i32_round(); (ptr.current_location() - surface_location).to_i32_floor();
if region.is_none_or(|region| region.contains(point)) { if region.is_none_or(|region| region.contains(point)) {
constraint.activate(); constraint.activate();
} }
@ -638,37 +648,13 @@ impl State {
seat.set_active_output(&output); seat.set_active_output(&output);
} }
for session in cursor_sessions_for_output(&shell, &output) { update_output_image_copy_cursor_position(
if let Some((geometry, offset)) = seat.cursor_geometry( &shell,
(position - output_geometry.loc.to_f64()) &self.common.clock,
.as_logical() &output,
.to_buffer( &seat,
output.current_scale().fractional_scale(), position,
output.current_transform(), );
&output_geometry.size.to_f64().as_logical(),
),
self.common.clock.now(),
) {
if session
.current_constraints()
.map(|constraint| constraint.size != geometry.size)
.unwrap_or(true)
{
let mut cursor_size = geometry.size;
// Client shouldn't try to allocate 0x0 buffer
if cursor_size == Size::new(0, 0) {
cursor_size = Size::new(1, 1);
}
session.update_constraints(BufferConstraints {
size: cursor_size,
shm: vec![ShmFormat::Argb8888],
dma: None,
});
}
session.set_cursor_hotspot(offset);
session.set_cursor_pos(Some(geometry.loc));
}
}
} }
} }
InputEvent::PointerMotionAbsolute { event, .. } => { InputEvent::PointerMotionAbsolute { event, .. } => {
@ -707,37 +693,13 @@ impl State {
ptr.frame(self); ptr.frame(self);
let shell = self.common.shell.read(); let shell = self.common.shell.read();
for session in cursor_sessions_for_output(&shell, &output) { update_output_image_copy_cursor_position(
if let Some((geometry, offset)) = seat.cursor_geometry( &shell,
(position - output_geometry.loc.to_f64()) &self.common.clock,
.as_logical() &output,
.to_buffer( &seat,
output.current_scale().fractional_scale(), position,
output.current_transform(), );
&output_geometry.size.to_f64().as_logical(),
),
self.common.clock.now(),
) {
if session
.current_constraints()
.map(|constraint| constraint.size != geometry.size)
.unwrap_or(true)
{
let mut cursor_size = geometry.size;
// Client shouldn't try to allocate 0x0 buffer
if cursor_size == Size::new(0, 0) {
cursor_size = Size::new(1, 1);
}
session.update_constraints(BufferConstraints {
size: cursor_size,
shm: vec![ShmFormat::Argb8888],
dma: None,
});
}
session.set_cursor_hotspot(offset);
session.set_cursor_pos(Some(geometry.loc));
}
}
} }
} }
InputEvent::PointerButton { event, .. } => { InputEvent::PointerButton { event, .. } => {
@ -787,7 +749,12 @@ impl State {
let shell = self.common.shell.read(); let shell = self.common.shell.read();
State::element_under(global_position, &output, &shell, &seat) State::element_under(global_position, &output, &shell, &seat)
}; };
if let Some(target) = under { // Grabbing a tiling resize handle (the gap between tiles) must not change keyboard focus
let on_resize_fork = matches!(
seat.get_pointer().unwrap().current_focus(),
Some(PointerFocusTarget::ResizeFork(_))
);
if let Some(target) = under.filter(|_| !on_resize_fork) {
if let Some(surface) = target.toplevel().map(Cow::into_owned) if let Some(surface) = target.toplevel().map(Cow::into_owned)
&& seat.get_keyboard().unwrap().modifier_state().logo && seat.get_keyboard().unwrap().modifier_state().logo
&& !shortcuts_inhibited && !shortcuts_inhibited
@ -1002,6 +969,10 @@ impl State {
if let Some(horizontal_amount) = event.amount(Axis::Horizontal) { if let Some(horizontal_amount) = event.amount(Axis::Horizontal) {
if horizontal_amount != 0.0 { if horizontal_amount != 0.0 {
frame = frame frame = frame
.relative_direction(
Axis::Horizontal,
event.relative_direction(Axis::Horizontal),
)
.value(Axis::Horizontal, scroll_factor * horizontal_amount); .value(Axis::Horizontal, scroll_factor * horizontal_amount);
if let Some(discrete) = event.amount_v120(Axis::Horizontal) { if let Some(discrete) = event.amount_v120(Axis::Horizontal) {
frame = frame.v120( frame = frame.v120(
@ -1015,8 +986,12 @@ impl State {
} }
if let Some(vertical_amount) = event.amount(Axis::Vertical) { if let Some(vertical_amount) = event.amount(Axis::Vertical) {
if vertical_amount != 0.0 { if vertical_amount != 0.0 {
frame = frame = frame
frame.value(Axis::Vertical, scroll_factor * vertical_amount); .relative_direction(
Axis::Vertical,
event.relative_direction(Axis::Vertical),
)
.value(Axis::Vertical, scroll_factor * vertical_amount);
if let Some(discrete) = event.amount_v120(Axis::Vertical) { if let Some(discrete) = event.amount_v120(Axis::Vertical) {
frame = frame.v120( frame = frame.v120(
Axis::Vertical, Axis::Vertical,
@ -1599,6 +1574,7 @@ impl State {
} }
} }
InputEvent::Special(_) => {} InputEvent::Special(_) => {}
#[allow(unused_variables)]
InputEvent::SwitchToggle { event } => { InputEvent::SwitchToggle { event } => {
#[cfg(feature = "logind")] #[cfg(feature = "logind")]
if event.switch() == Some(Switch::Lid) && self.common.inhibit_lid_fd.is_some() { if event.switch() == Some(Switch::Lid) && self.common.inhibit_lid_fd.is_some() {
@ -1620,7 +1596,7 @@ impl State {
if let Err(err) = self.refresh_output_config() { if let Err(err) = self.refresh_output_config() {
if !closed { if !closed {
warn!(?err, "Failed to re-enable internal connector"); tracing::warn!(?err, "Failed to re-enable internal connector");
if let Some(output) = output { if let Some(output) = output {
use cosmic_comp_config::output::comp::OutputState; use cosmic_comp_config::output::comp::OutputState;
@ -2132,6 +2108,7 @@ impl State {
layer, layer,
popup, popup,
location, location,
..
} => { } => {
if layer.can_receive_keyboard_focus() { if layer.can_receive_keyboard_focus() {
let surface = popup.wl_surface(); let surface = popup.wl_surface();
@ -2149,7 +2126,9 @@ impl State {
} }
} }
} }
Stage::LayerSurface { layer, location } => { Stage::LayerSurface {
layer, location, ..
} => {
if under_from_surface_tree( if under_from_surface_tree(
layer.wl_surface(), layer.wl_surface(),
global_pos.as_logical(), global_pos.as_logical(),
@ -2192,7 +2171,7 @@ impl State {
if Rectangle::new(offset.as_local(), output_geo.size) if Rectangle::new(offset.as_local(), output_geo.size)
.intersection(output_geo) .intersection(output_geo)
.is_some_and(|geometry| { .is_some_and(|geometry| {
geometry.contains(global_pos.to_local(output).to_i32_round()) geometry.contains(global_pos.to_local(output).to_i32_floor())
}) })
&& let Some(element) = workspace.popup_element_under(location, seat) && let Some(element) = workspace.popup_element_under(location, seat)
{ {
@ -2206,7 +2185,7 @@ impl State {
if Rectangle::new(offset.as_local(), output_geo.size) if Rectangle::new(offset.as_local(), output_geo.size)
.intersection(output_geo) .intersection(output_geo)
.is_some_and(|geometry| { .is_some_and(|geometry| {
geometry.contains(global_pos.to_local(output).to_i32_round()) geometry.contains(global_pos.to_local(output).to_i32_floor())
}) })
&& let Some(element) = workspace.toplevel_element_under(location, seat) && let Some(element) = workspace.toplevel_element_under(location, seat)
{ {
@ -2301,7 +2280,9 @@ impl State {
)))); ))));
} }
} }
Stage::LayerSurface { layer, location } => { Stage::LayerSurface {
layer, location, ..
} => {
let surface = layer.wl_surface(); let surface = layer.wl_surface();
if let Some((surface, surface_loc)) = under_from_surface_tree( if let Some((surface, surface_loc)) = under_from_surface_tree(
surface, surface,
@ -2407,7 +2388,7 @@ impl State {
if let Some(constraint) = constraint if let Some(constraint) = constraint
&& let Some(region) = constraint.region() && let Some(region) = constraint.region()
{ {
let point_in_surface = (p - surface_offset.to_f64()).to_i32_round(); let point_in_surface = (p - surface_offset.to_f64()).to_i32_floor();
return region.contains(point_in_surface); return region.contains(point_in_surface);
} }
true true
@ -2420,7 +2401,7 @@ impl State {
if is_legal(pos_in_element) { if is_legal(pos_in_element) {
let x = workspace_origin.x + origin.x + pos_in_element.x; let x = workspace_origin.x + origin.x + pos_in_element.x;
let y = workspace_origin.y + origin.y + pos_in_element.y; let y = workspace_origin.y + origin.y + pos_in_element.y;
Some((Point::new(x, y), output.clone())) Some((Point::<_, Global>::new(x, y), output.clone()))
} else { } else {
None None
} }
@ -2430,11 +2411,34 @@ impl State {
}; };
if let Some((point, output)) = point_and_output { if let Some((point, output)) = point_and_output {
// TODO: Replace with `wl_pointer.warp`
let original_position = pointer.current_location(); let original_position = pointer.current_location();
pointer.set_location(point); let serial = SERIAL_COUNTER.next_serial();
let under = State::surface_under(point, &output, &self.common.shell.write())
.map(|(target, pos)| (target, pos.as_logical()));
let time = self.common.clock.now();
pointer.relative_motion(
self,
under.clone(),
&RelativeMotionEvent {
delta: (0., 0.).into(),
delta_unaccel: (0., 0.).into(),
utime: time.as_micros(),
},
);
pointer.motion(
self,
under,
&MotionEvent {
location: point.as_logical(),
serial,
time: time.as_millis(),
},
);
pointer.frame(self);
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
shell.update_pointer_position(point.as_global().to_local(&output), &output); shell.update_pointer_position(point.to_local(&output), &output);
let seat = shell let seat = shell
.seats .seats
@ -2449,31 +2453,13 @@ impl State {
self.common.config.cosmic_conf.accessibility_zoom.view_moves, self.common.config.cosmic_conf.accessibility_zoom.view_moves,
); );
let output_geometry = output.geometry(); update_output_image_copy_cursor_position(
for session in cursor_sessions_for_output(&shell, &output) { &shell,
if let Some((geometry, offset)) = seat.cursor_geometry( &self.common.clock,
point.to_buffer( &output,
output.current_scale().fractional_scale(), &seat,
output.current_transform(), point,
&output_geometry.size.to_f64().as_logical(), );
),
self.common.clock.now(),
) {
if session
.current_constraints()
.map(|constraint| constraint.size != geometry.size)
.unwrap_or(true)
{
session.update_constraints(BufferConstraints {
size: geometry.size,
shm: vec![ShmFormat::Argb8888],
dma: None,
});
}
session.set_cursor_hotspot(offset);
session.set_cursor_pos(Some(geometry.loc));
}
}
} }
} }
} }
@ -2534,3 +2520,36 @@ fn mapped_output_for_device<'a, D: Device + 'static>(
}; };
map_to_output.or_else(|| shell.builtin_output()) map_to_output.or_else(|| shell.builtin_output())
} }
pub fn update_output_image_copy_cursor_position(
shell: &Shell,
clock: &Clock<Monotonic>,
output: &Output,
seat: &Seat<State>,
position: Point<f64, Global>,
) {
let output_geometry = output.geometry();
for session in cursor_sessions_for_output(&shell, &output) {
if let Some(cursor_geometry) = seat.cursor_geometry(
(position - output_geometry.loc.to_f64())
.as_logical()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output_geometry.size.to_f64().as_logical(),
),
clock.now(),
) {
let constraints = cursor_capture_constraints(Some(cursor_geometry));
if session
.current_constraints()
.map(|current_constraints| current_constraints.size != constraints.size)
.unwrap_or(true)
{
session.update_constraints(constraints);
}
session.set_cursor_hotspot(cursor_geometry.hotspot);
session.set_cursor_pos(Some(cursor_geometry.geometry.loc));
}
}
}

View file

@ -26,7 +26,9 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use wayland::protocols::overlap_notify::OverlapNotifyState; use wayland::protocols::{
keyboard_layout::KeyboardLayoutState, overlap_notify::OverlapNotifyState,
};
use crate::wayland::handlers::compositor::client_compositor_state; use crate::wayland::handlers::compositor::client_compositor_state;
@ -79,11 +81,8 @@ impl State {
warn!(?err, "Failed to setup cosmic-session communication"); warn!(?err, "Failed to setup cosmic-session communication");
} }
let mut args = env::args().skip(1); self.common.kiosk_child = if let Some(mut command) = self.kiosk_command.take() {
self.common.kiosk_child = if let Some(exec) = args.next() {
// Run command in kiosk mode // Run command in kiosk mode
let mut command = process::Command::new(&exec);
command.args(args);
command.envs( command.envs(
session::get_env(&self.common).expect("WAYLAND_DISPLAY should be valid UTF-8"), session::get_env(&self.common).expect("WAYLAND_DISPLAY should be valid UTF-8"),
); );
@ -94,7 +93,7 @@ impl State {
}) })
}; };
info!("Running {:?}", exec); info!("Running {:?}", command.get_program());
command command
.spawn() .spawn()
.map_err(|err| { .map_err(|err| {
@ -113,8 +112,10 @@ impl State {
pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> { pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
let raw_args = RawArgs::from_args(); let raw_args = RawArgs::from_args();
let mut cursor = raw_args.cursor(); let mut cursor = raw_args.cursor();
raw_args.next_os(&mut cursor);
let git_hash = option_env!("GIT_HASH").unwrap_or("unknown"); let git_hash = option_env!("GIT_HASH").unwrap_or("unknown");
let mut kiosk_command = None;
let mut with_xwayland = true; let mut with_xwayland = true;
// Parse the arguments // Parse the arguments
while let Some(arg) = raw_args.next_os(&mut cursor) { while let Some(arg) = raw_args.next_os(&mut cursor) {
@ -135,7 +136,11 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
); );
return Ok(()); return Ok(());
} }
_ => {} _ => {
let mut cmd = process::Command::new(arg);
cmd.args(raw_args.remaining(&mut cursor));
kiosk_command = Some(cmd);
}
} }
} }
@ -169,6 +174,7 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
event_loop.handle(), event_loop.handle(),
event_loop.get_signal(), event_loop.get_signal(),
with_xwayland, with_xwayland,
kiosk_command,
); );
// init backend // init backend
backend::init_backend_auto(&display, &mut event_loop, &mut state)?; backend::init_backend_auto(&display, &mut event_loop, &mut state)?;
@ -331,5 +337,6 @@ fn refresh(state: &mut State) {
state::Common::refresh_focus(state); state::Common::refresh_focus(state);
OverlapNotifyState::refresh(state); OverlapNotifyState::refresh(state);
state.common.update_x11_stacking_order(); state.common.update_x11_stacking_order();
KeyboardLayoutState::refresh(state);
state.last_refresh = LastRefresh::At(Instant::now()); state.last_refresh = LastRefresh::At(Instant::now());
} }

View file

@ -1,18 +1,21 @@
use crate::{ use crate::{
backend::render::element::{AsGlowRenderer, FromGlesError}, backend::render::element::AsGlowRenderer,
state::State, state::State,
utils::{iced::IcedElementInternal, prelude::*}, utils::{
iced::{IcedElementInternal, IcedRenderElement},
prelude::*,
},
}; };
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic_comp_config::AppearanceConfig; use cosmic_comp_config::AppearanceConfig;
use id_tree::NodeId; use id_tree::NodeId;
use smithay::{ use smithay::{
backend::{ backend::{
drm::DrmNode,
input::KeyState, input::KeyState,
renderer::{ renderer::{
element::{ element::{
Element, Kind, RenderElement, UnderlyingStorage, Element, Kind, RenderElement, UnderlyingStorage,
memory::MemoryRenderBufferRenderElement,
utils::{CropRenderElement, RelocateRenderElement, RescaleRenderElement}, utils::{CropRenderElement, RelocateRenderElement, RescaleRenderElement},
}, },
gles::element::PixelShaderElement, gles::element::PixelShaderElement,
@ -124,9 +127,9 @@ impl fmt::Debug for CosmicMapped {
} }
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct CosmicMappedKey(CosmicMappedKeyInner); pub struct CosmicMappedKey(CosmicMappedKeyInner);
#[derive(Clone)] #[derive(Clone, Debug)]
enum CosmicMappedKeyInner { enum CosmicMappedKeyInner {
Window(Weak<Mutex<IcedElementInternal<CosmicWindowInternal>>>), Window(Weak<Mutex<IcedElementInternal<CosmicWindowInternal>>>),
Stack(Weak<Mutex<IcedElementInternal<CosmicStackInternal>>>), Stack(Weak<Mutex<IcedElementInternal<CosmicStackInternal>>>),
@ -165,6 +168,20 @@ impl PartialEq for CosmicMappedKey {
} }
impl Eq for CosmicMappedKey {} impl Eq for CosmicMappedKey {}
impl PartialEq<CosmicMappedKey> for CosmicMapped {
fn eq(&self, other: &CosmicMappedKey) -> bool {
match (&self.element, &other.0) {
(CosmicMappedInternal::Window(window), CosmicMappedKeyInner::Window(weak)) => {
Arc::as_ptr(&window.0.0) == weak.as_ptr()
}
(CosmicMappedInternal::Stack(stack), CosmicMappedKeyInner::Stack(weak)) => {
Arc::as_ptr(&stack.0.0) == weak.as_ptr()
}
_ => false,
}
}
}
impl PartialEq for CosmicMapped { impl PartialEq for CosmicMapped {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.element == other.element self.element == other.element
@ -583,33 +600,38 @@ impl CosmicMapped {
} }
} }
pub fn popup_render_elements<R, C>( pub fn push_popup_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: smithay::utils::Point<i32, smithay::utils::Physical>, location: smithay::utils::Point<i32, smithay::utils::Physical>,
scale: smithay::utils::Scale<f64>, scale: smithay::utils::Scale<f64>,
alpha: f32, alpha: f32,
) -> Vec<C> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(CosmicMappedRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
C: From<CosmicMappedRenderElement<R>>,
{ {
match &self.element { match &self.element {
CosmicMappedInternal::Stack(s) => s CosmicMappedInternal::Stack(s) => s.push_popup_render_elements(
.popup_render_elements::<R, CosmicMappedRenderElement<R>>( renderer,
renderer, location, scale, alpha, location,
), scale,
CosmicMappedInternal::Window(w) => w alpha,
.popup_render_elements::<R, CosmicMappedRenderElement<R>>( scanout_node,
renderer, location, scale, alpha, &mut |elem| push(elem.into()),
), ),
CosmicMappedInternal::Window(w) => w.push_popup_render_elements(
renderer,
location,
scale,
alpha,
scanout_node,
&mut |elem| push(elem.into()),
),
_ => unreachable!(), _ => unreachable!(),
} }
.into_iter()
.map(C::from)
.collect()
} }
pub fn shadow_render_element<R, C>( pub fn shadow_render_element<R, C>(
@ -656,7 +678,7 @@ impl CosmicMapped {
} }
} }
pub fn render_elements<R, C>( pub fn push_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: smithay::utils::Point<i32, smithay::utils::Physical>, location: smithay::utils::Point<i32, smithay::utils::Physical>,
@ -664,15 +686,16 @@ impl CosmicMapped {
scale: smithay::utils::Scale<f64>, scale: smithay::utils::Scale<f64>,
alpha: f32, alpha: f32,
scanout_override: Option<bool>, scanout_override: Option<bool>,
) -> Vec<C> scanout_node: Option<DrmNode>,
where push_above: &mut dyn FnMut(CosmicMappedRenderElement<R>),
push_below: &mut dyn FnMut(CosmicMappedRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
C: From<CosmicMappedRenderElement<R>>,
{ {
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
let mut elements = if let Some(debug) = self.debug.lock().unwrap().as_mut() { if let Some(debug) = self.debug.lock().unwrap().as_mut() {
let window = self.active_window(); let window = self.active_window();
let window_geo = window.geometry(); let window_geo = window.geometry();
let (min_size, max_size, size) = ( let (min_size, max_size, size) = (
@ -833,41 +856,38 @@ impl CosmicMapped {
scale.x, scale.x,
0.8, 0.8,
) { ) {
Ok(element) => vec![CosmicMappedRenderElement::from(element)], Ok(element) => push_above(element.into()),
Err(err) => { Err(err) => {
debug!(?err, "Error rendering debug overlay."); debug!(?err, "Error rendering debug overlay.");
Vec::new()
} }
} }
} else {
Vec::new()
}; };
#[cfg(not(feature = "debug"))]
let mut elements = Vec::new();
#[cfg_attr(not(feature = "debug"), allow(unused_mut))] match &self.element {
elements.extend(match &self.element { CosmicMappedInternal::Stack(s) => s.push_render_elements(
CosmicMappedInternal::Stack(s) => s.render_elements::<R, CosmicMappedRenderElement<R>>(
renderer, renderer,
location, location,
max_size, max_size,
scale, scale,
alpha, alpha,
scanout_override, scanout_override,
scanout_node,
&mut |elem| push_above(elem.into()),
&mut |elem| push_below(elem.into()),
),
CosmicMappedInternal::Window(w) => w.push_render_elements(
renderer,
location,
max_size,
scale,
alpha,
scanout_override,
scanout_node,
&mut |elem| push_above(elem.into()),
&mut |elem| push_below(elem.into()),
), ),
CosmicMappedInternal::Window(w) => w
.render_elements::<R, CosmicMappedRenderElement<R>>(
renderer,
location,
max_size,
scale,
alpha,
scanout_override,
),
_ => unreachable!(), _ => unreachable!(),
}); }
elements.into_iter().map(C::from).collect()
} }
pub(crate) fn update_theme(&self, theme: cosmic::Theme) { pub(crate) fn update_theme(&self, theme: cosmic::Theme) {
@ -1109,7 +1129,7 @@ where
GrabbedWindow(RescaleRenderElement<self::window::CosmicWindowRenderElement<R>>), GrabbedWindow(RescaleRenderElement<self::window::CosmicWindowRenderElement<R>>),
FocusIndicator(PixelShaderElement), FocusIndicator(PixelShaderElement),
Overlay(PixelShaderElement), Overlay(PixelShaderElement),
StackHoverIndicator(MemoryRenderBufferRenderElement<R>), StackHoverIndicator(IcedRenderElement<R>),
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
Egui(TextureRenderElement<GlesTexture>), Egui(TextureRenderElement<GlesTexture>),
} }
@ -1117,7 +1137,7 @@ where
impl<R> Element for CosmicMappedRenderElement<R> impl<R> Element for CosmicMappedRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
{ {
fn id(&self) -> &smithay::backend::renderer::element::Id { fn id(&self) -> &smithay::backend::renderer::element::Id {
match self { match self {
@ -1338,8 +1358,7 @@ where
impl<R> RenderElement<R> for CosmicMappedRenderElement<R> impl<R> RenderElement<R> for CosmicMappedRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
R::Error: FromGlesError,
{ {
fn draw( fn draw(
&self, &self,
@ -1372,7 +1391,7 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error), .map_err(R::from_gles_error),
CosmicMappedRenderElement::MovingStack(elem) => { CosmicMappedRenderElement::MovingStack(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache) elem.draw(frame, src, dst, damage, opaque_regions, cache)
} }
@ -1394,7 +1413,7 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error), .map_err(R::from_gles_error),
CosmicMappedRenderElement::Overlay(elem) => RenderElement::<GlowRenderer>::draw( CosmicMappedRenderElement::Overlay(elem) => RenderElement::<GlowRenderer>::draw(
elem, elem,
R::glow_frame_mut(frame), R::glow_frame_mut(frame),
@ -1404,7 +1423,7 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error), .map_err(R::from_gles_error),
CosmicMappedRenderElement::StackHoverIndicator(elem) => { CosmicMappedRenderElement::StackHoverIndicator(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache) elem.draw(frame, src, dst, damage, opaque_regions, cache)
} }
@ -1420,7 +1439,7 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
} }
} }
@ -1483,7 +1502,7 @@ where
dst, dst,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicMappedRenderElement::MovingStack(elem) => { CosmicMappedRenderElement::MovingStack(elem) => {
elem.capture_framebuffer(frame, src, dst, cache) elem.capture_framebuffer(frame, src, dst, cache)
@ -1505,7 +1524,7 @@ where
dst, dst,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicMappedRenderElement::Overlay(elem) => { CosmicMappedRenderElement::Overlay(elem) => {
RenderElement::<GlowRenderer>::capture_framebuffer( RenderElement::<GlowRenderer>::capture_framebuffer(
@ -1515,7 +1534,7 @@ where
dst, dst,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicMappedRenderElement::StackHoverIndicator(elem) => { CosmicMappedRenderElement::StackHoverIndicator(elem) => {
elem.capture_framebuffer(frame, src, dst, cache) elem.capture_framebuffer(frame, src, dst, cache)
@ -1526,7 +1545,7 @@ where
RenderElement::<GlowRenderer>::capture_framebuffer( RenderElement::<GlowRenderer>::capture_framebuffer(
elem, glow_frame, src, dst, cache, elem, glow_frame, src, dst, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
} }
} }
@ -1564,13 +1583,13 @@ where
} }
} }
impl<R> From<MemoryRenderBufferRenderElement<R>> for CosmicMappedRenderElement<R> impl<R> From<IcedRenderElement<R>> for CosmicMappedRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: MemoryRenderBufferRenderElement<R>) -> Self { fn from(elem: IcedRenderElement<R>) -> Self {
CosmicMappedRenderElement::StackHoverIndicator(elem) CosmicMappedRenderElement::StackHoverIndicator(elem)
} }
} }

View file

@ -1,10 +1,11 @@
use std::sync::Mutex; use std::sync::{Arc, Mutex};
use crate::{ use crate::{
backend::render::element::AsGlowRenderer,
config::Config, config::Config,
fl, fl,
shell::grabs::ResizeEdge, shell::grabs::ResizeEdge,
utils::iced::{IcedElement, Program}, utils::iced::{IcedElement, IcedRenderElement, Program},
}; };
use calloop::LoopHandle; use calloop::LoopHandle;
@ -12,175 +13,330 @@ use cosmic::{
Apply, Apply,
iced::{ iced::{
Alignment, Alignment,
widget::{column, container, row, space}, core::{Background, Border, Color, Length},
widget::{container, row, space},
}, },
iced_core::{Background, Border, Color, Length},
theme, theme,
widget::{icon::from_name, text}, widget::{icon::from_name, text},
}; };
use cosmic_settings_config::shortcuts::action::{Action, ResizeDirection}; use cosmic_settings_config::shortcuts::action::{Action, ResizeDirection};
use smithay::utils::Size; use smithay::{
backend::renderer::ImportMem,
desktop::space::SpaceElement,
output::Output,
utils::{Logical, Physical, Point, Rectangle, Scale, Size},
};
pub type ResizeIndicator = IcedElement<ResizeIndicatorInternal>; #[derive(Debug, Clone)]
pub struct ResizeIndicator {
edges: ResizeEdge,
direction: Arc<Mutex<ResizeDirection>>,
size: Size<i32, Logical>,
pub fn resize_indicator( center_elem: IcedElement<ResizeIndicatorInternal>,
direction: ResizeDirection, left_elem: IcedElement<ResizeIndicatorArrow>,
config: &Config, top_elem: IcedElement<ResizeIndicatorArrow>,
evlh: LoopHandle<'static, crate::state::State>, right_elem: IcedElement<ResizeIndicatorArrow>,
theme: cosmic::Theme, down_elem: IcedElement<ResizeIndicatorArrow>,
) -> ResizeIndicator { }
ResizeIndicator::new(
ResizeIndicatorInternal { const ARROW_SIZE: i32 = 36;
edges: Mutex::new(ResizeEdge::all()),
direction, impl ResizeIndicator {
shortcut1: config pub fn new(
.shortcuts direction: ResizeDirection,
.iter() config: &Config,
.find_map(|(pattern, action)| { evlh: LoopHandle<'static, crate::state::State>,
(*action == Action::Resizing(ResizeDirection::Outwards)).then_some(pattern) mut theme: cosmic::Theme,
}) ) -> ResizeIndicator {
.map(|pattern| format!("{}: ", pattern.to_string())) theme.transparent = theme.cosmic().frosted_system_interface;
.unwrap_or_else(|| crate::fl!("unknown-keybinding")), let direction = Arc::new(Mutex::new(direction));
shortcut2: config
.shortcuts ResizeIndicator {
.iter() edges: ResizeEdge::all(),
.find_map(|(pattern, action)| { direction: direction.clone(),
(*action == Action::Resizing(ResizeDirection::Inwards)).then_some(pattern) size: Size::default(),
}) center_elem: IcedElement::new(
.map(|pattern| format!("{}: ", pattern.to_string())) ResizeIndicatorInternal {
.unwrap_or_else(|| crate::fl!("unknown-keybinding")), shortcut1: config
}, .shortcuts
Size::from((1, 1)), .iter()
evlh, .find_map(|(pattern, action)| {
theme, (*action == Action::Resizing(ResizeDirection::Outwards))
) .then_some(pattern)
})
.map(|pattern| format!("{}: ", pattern.to_string()))
.unwrap_or_else(|| crate::fl!("unknown-keybinding")),
shortcut2: config
.shortcuts
.iter()
.find_map(|(pattern, action)| {
(*action == Action::Resizing(ResizeDirection::Inwards))
.then_some(pattern)
})
.map(|pattern| format!("{}: ", pattern.to_string()))
.unwrap_or_else(|| crate::fl!("unknown-keybinding")),
},
Size::from((1, 1)),
evlh.clone(),
theme.clone(),
),
left_elem: IcedElement::new(
ResizeIndicatorArrow {
direction: direction.clone(),
icon_outwards: "go-previous-symbolic",
icon_inwards: "go-next-symbolic",
},
Size::from((ARROW_SIZE, ARROW_SIZE)),
evlh.clone(),
theme.clone(),
),
top_elem: IcedElement::new(
ResizeIndicatorArrow {
direction: direction.clone(),
icon_outwards: "go-up-symbolic",
icon_inwards: "go-down-symbolic",
},
Size::from((ARROW_SIZE, ARROW_SIZE)),
evlh.clone(),
theme.clone(),
),
right_elem: IcedElement::new(
ResizeIndicatorArrow {
direction: direction.clone(),
icon_outwards: "go-next-symbolic",
icon_inwards: "go-previous-symbolic",
},
Size::from((ARROW_SIZE, ARROW_SIZE)),
evlh.clone(),
theme.clone(),
),
down_elem: IcedElement::new(
ResizeIndicatorArrow {
direction,
icon_outwards: "go-down-symbolic",
icon_inwards: "go-up-symbolic",
},
Size::from((ARROW_SIZE, ARROW_SIZE)),
evlh.clone(),
theme.clone(),
),
}
}
pub fn resize(&mut self, size: Size<i32, Logical>) {
let minimum = self.center_elem.minimum_size();
let new_size = Size::<i32, Logical>::new(size.w.min(minimum.w), size.h.min(minimum.h));
self.center_elem.resize(new_size);
self.size = size;
}
pub fn push_render_elements<R>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
push_above: &mut dyn FnMut(IcedRenderElement<R>),
mut push_below: Option<&mut dyn FnMut(IcedRenderElement<R>)>,
) where
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
let elem_size = self.center_elem.current_size();
let center_location = Point::new(
self.size.w.saturating_sub(elem_size.w) / 2,
self.size.h.saturating_sub(elem_size.h) / 2,
);
let left_location = Point::new(0, self.size.h.saturating_sub(ARROW_SIZE) / 2);
let top_location = Point::new(self.size.w.saturating_sub(ARROW_SIZE) / 2, 0);
let right_location = Point::new(
self.size.w.saturating_sub(ARROW_SIZE),
self.size.h.saturating_sub(ARROW_SIZE) / 2,
);
let down_location = Point::new(
self.size.w.saturating_sub(ARROW_SIZE) / 2,
self.size.h.saturating_sub(ARROW_SIZE),
);
let radii = self
.center_elem
.with_theme(|theme| theme.cosmic().radius_s())
.map(|x| x.round() as u8);
self.center_elem.push_render_elements(
renderer,
location + center_location.to_physical_precise_round(scale),
scale,
alpha,
radii,
push_above,
if let Some(push_below) = push_below.as_mut() {
Some(&mut **push_below)
} else {
None
},
);
let mut render = move |elem: &IcedElement<ResizeIndicatorArrow>,
loc: Point<i32, Logical>| {
elem.push_render_elements(
renderer,
location + loc.to_physical_precise_round(scale),
scale,
alpha,
radii,
push_above,
if let Some(push_below) = push_below.as_mut() {
Some(&mut **push_below)
} else {
None
},
);
};
if self.edges.contains(ResizeEdge::LEFT) {
render(&self.left_elem, left_location);
}
if self.edges.contains(ResizeEdge::TOP) {
render(&self.top_elem, top_location);
}
if self.edges.contains(ResizeEdge::RIGHT) {
render(&self.right_elem, right_location);
}
if self.edges.contains(ResizeEdge::BOTTOM) {
render(&self.down_elem, down_location);
}
}
pub fn set_edges(&mut self, edges: ResizeEdge) {
self.edges = edges;
}
pub fn set_direction(&self, direction: ResizeDirection) {
let mut dir_ref = self.direction.lock().unwrap();
if *dir_ref != direction {
*dir_ref = direction;
self.left_elem.force_redraw();
self.top_elem.force_redraw();
self.right_elem.force_redraw();
self.down_elem.force_redraw();
}
}
pub fn output_enter(&self, output: &Output) {
self.center_elem
.output_enter(output, Rectangle::default() /*unused*/);
self.left_elem
.output_enter(output, Rectangle::default() /*unused*/);
self.top_elem
.output_enter(output, Rectangle::default() /*unused*/);
self.right_elem
.output_enter(output, Rectangle::default() /*unused*/);
self.down_elem
.output_enter(output, Rectangle::default() /*unused*/);
}
pub fn output_leave(&self, output: &Output) {
self.center_elem.output_leave(output);
self.left_elem.output_leave(output);
self.top_elem.output_leave(output);
self.right_elem.output_leave(output);
self.down_elem.output_leave(output);
}
} }
pub struct ResizeIndicatorInternal { pub struct ResizeIndicatorInternal {
pub edges: Mutex<ResizeEdge>, shortcut1: String,
pub direction: ResizeDirection, shortcut2: String,
pub shortcut1: String,
pub shortcut2: String,
} }
impl Program for ResizeIndicatorInternal { impl Program for ResizeIndicatorInternal {
type Message = (); type Message = ();
fn view(&self) -> cosmic::Element<'_, Self::Message> { fn view(&self) -> cosmic::Element<'_, Self::Message> {
let edges = self.edges.lock().unwrap(); row(vec![
let icon_container_style = || { text::heading(&self.shortcut1).into(),
theme::Container::custom(|theme| container::Style { text::body(fl!("grow-window")).into(),
space::horizontal().width(40).into(),
text::heading(&self.shortcut2).into(),
text::body(fl!("shrink-window")).into(),
])
.apply(container)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.padding(16)
.apply(container)
.class(theme::Container::custom(|theme| {
let mut background = theme.cosmic().accent_color();
if theme.transparent {
background.alpha = theme
.cosmic()
.alpha_map
.blurred_alpha(theme.cosmic().frosted);
}
container::Style {
snap: true, snap: true,
icon_color: Some(Color::from(theme.cosmic().accent.on)), icon_color: Some(Color::from(theme.cosmic().accent.on)),
text_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())), background: Some(Background::Color(background.into())),
border: Border { border: Border {
radius: 18.0.into(), radius: theme.cosmic().radius_s().into(),
width: 0.0, width: 0.0,
color: Color::TRANSPARENT, color: Color::TRANSPARENT,
}, },
shadow: Default::default(), shadow: Default::default(),
}) }
}; }))
.width(Length::Shrink)
column(vec![ .height(Length::Shrink)
if edges.contains(ResizeEdge::TOP) { .into()
from_name(if self.direction == ResizeDirection::Outwards { }
"go-up-symbolic" }
} else {
"go-down-symbolic" pub struct ResizeIndicatorArrow {
}) direction: Arc<Mutex<ResizeDirection>>,
.size(20) icon_outwards: &'static str,
.prefer_svg(true) icon_inwards: &'static str,
.apply(container) }
.padding(8)
.class(icon_container_style()) impl Program for ResizeIndicatorArrow {
.width(Length::Shrink) type Message = ();
.apply(container)
.center_x(Length::Fill) fn view(&self) -> cosmic::Element<'_, Self::Message> {
.into() from_name(
} else { if *self.direction.lock().unwrap() == ResizeDirection::Outwards {
space::vertical().height(36).into() self.icon_outwards
}, } else {
row(vec![ self.icon_inwards
if edges.contains(ResizeEdge::LEFT) { },
from_name(if self.direction == ResizeDirection::Outwards { )
"go-previous-symbolic" .size(20)
} else { .prefer_svg(true)
"go-next-symbolic" .apply(container)
}) .padding(8)
.size(20) .class(theme::Container::custom(|theme| {
.prefer_svg(true) let mut background = theme.cosmic().accent_color();
.apply(container) if theme.transparent {
.padding(8) background.alpha = theme
.class(icon_container_style()) .cosmic()
.width(Length::Shrink) .alpha_map
.apply(container) .blurred_alpha(theme.cosmic().frosted);
.center_y(Length::Fill) }
.into()
} else { container::Style {
space::horizontal().width(36).into() snap: true,
}, icon_color: Some(Color::from(theme.cosmic().accent.on)),
row(vec![ text_color: Some(Color::from(theme.cosmic().accent.on)),
text::heading(&self.shortcut1).into(), background: Some(Background::Color(background.into())),
text::body(fl!("grow-window")).into(), border: Border {
space::horizontal().width(40).into(), radius: theme.cosmic().radius_s().into(),
text::heading(&self.shortcut2).into(), width: 0.0,
text::body(fl!("shrink-window")).into(), color: Color::TRANSPARENT,
]) },
.apply(container) shadow: Default::default(),
.align_x(Alignment::Center) }
.align_y(Alignment::Center) }))
.padding(16) .width(Length::Shrink)
.apply(container)
.class(icon_container_style())
.width(Length::Shrink)
.height(Length::Shrink)
.apply(container)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into(),
if edges.contains(ResizeEdge::RIGHT) {
from_name(if self.direction == ResizeDirection::Outwards {
"go-next-symbolic"
} else {
"go-previous-symbolic"
})
.size(20)
.prefer_svg(true)
.apply(container)
.padding(8)
.class(icon_container_style())
.height(Length::Shrink)
.apply(container)
.center_y(Length::Fill)
.into()
} else {
space::horizontal().width(36).into()
},
])
.width(Length::Fill)
.height(Length::Fill)
.into(),
if edges.contains(ResizeEdge::BOTTOM) {
from_name(if self.direction == ResizeDirection::Outwards {
"go-down-symbolic"
} else {
"go-up-symbolic"
})
.size(20)
.prefer_svg(true)
.apply(container)
.padding(8)
.class(icon_container_style())
.width(Length::Shrink)
.apply(container)
.center_x(Length::Fill)
.into()
} else {
space::vertical().height(36).into()
},
])
.into() .into()
} }
} }

View file

@ -4,11 +4,8 @@ use super::{
}; };
use crate::{ use crate::{
backend::render::{ backend::render::{
IndicatorShader, Key, Usage, IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer,
clipped_surface::ClippedSurfaceRenderElement, shadow::ShadowShader, wayland::SurfaceRenderElement,
cursor::CursorState,
element::{AsGlowRenderer, FromGlesError},
shadow::ShadowShader,
}, },
hooks::{Decorations, HOOKS}, hooks::{Decorations, HOOKS},
shell::{ shell::{
@ -19,17 +16,20 @@ use crate::{
}, },
state::State, state::State,
utils::{ utils::{
iced::{IcedElement, Program}, iced::{IcedElement, IcedRenderElement, Program},
prelude::*, prelude::*,
}, },
}; };
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic::{ use cosmic::{
Apply, Element as CosmicElement, Theme, Apply, Element as CosmicElement, Theme,
iced::{Alignment, id::Id, widget as iced_widget}, iced::{
iced_core::{Background, Border, Color, Length, border::Radius}, Alignment,
iced_runtime::Task, core::{Background, Border, Color, Length, border::Radius},
iced_widget::scrollable::AbsoluteOffset, id::Id,
runtime::Task,
widget::{self as iced_widget, scrollable::AbsoluteOffset},
},
theme, widget as cosmic_widget, theme, widget as cosmic_widget,
}; };
use cosmic_comp_config::AppearanceConfig; use cosmic_comp_config::AppearanceConfig;
@ -37,14 +37,11 @@ use cosmic_settings_config::shortcuts;
use shortcuts::action::{Direction, FocusDirection}; use shortcuts::action::{Direction, FocusDirection};
use smithay::{ use smithay::{
backend::{ backend::{
drm::DrmNode,
input::KeyState, input::KeyState,
renderer::{ renderer::{
ImportAll, ImportMem, Renderer, ImportAll, ImportMem, Renderer,
element::{ element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
AsRenderElements, Element, Id as RendererId, Kind, RenderElement,
UnderlyingStorage, memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
},
gles::element::PixelShaderElement, gles::element::PixelShaderElement,
glow::GlowRenderer, glow::GlowRenderer,
utils::{CommitCounter, DamageSet, OpaqueRegions}, utils::{CommitCounter, DamageSet, OpaqueRegions},
@ -149,9 +146,10 @@ impl CosmicStack {
pub fn new<I: Into<CosmicSurface>>( pub fn new<I: Into<CosmicSurface>>(
windows: impl Iterator<Item = I>, windows: impl Iterator<Item = I>,
handle: LoopHandle<'static, crate::state::State>, handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme, mut theme: cosmic::Theme,
appearance: AppearanceConfig, appearance: AppearanceConfig,
) -> CosmicStack { ) -> CosmicStack {
theme.transparent = theme.cosmic().frosted_windows;
let windows = windows.map(Into::into).collect::<Vec<_>>(); let windows = windows.map(Into::into).collect::<Vec<_>>();
assert!(!windows.is_empty()); assert!(!windows.is_empty());
@ -532,7 +530,7 @@ impl CosmicStack {
let geo = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry(); let geo = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry();
if surface_type.contains(WindowSurfaceType::TOPLEVEL) { if surface_type.contains(WindowSurfaceType::TOPLEVEL) {
let point_i32 = relative_pos.to_i32_round::<i32>(); let point_i32 = relative_pos.to_i32_floor::<i32>();
if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0) if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0)
|| (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0) || (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0)
|| (point_i32.x - geo.loc.x >= geo.size.w || (point_i32.x - geo.loc.x >= geo.size.w
@ -648,30 +646,38 @@ impl CosmicStack {
self.0.loop_handle() self.0.loop_handle()
} }
pub fn popup_render_elements<R, C>( pub fn push_popup_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: Point<i32, Physical>, location: Point<i32, Physical>,
scale: Scale<f64>, scale: Scale<f64>,
alpha: f32, alpha: f32,
) -> Vec<C> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(CosmicStackRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
C: From<CosmicStackRenderElement<R>>,
{ {
let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32));
self.0.with_program(|p| { self.0.with_program(|p| {
let windows = p.windows.lock().unwrap(); let windows = p.windows.lock().unwrap();
let active = p.active.load(Ordering::SeqCst); let active = p.active.load(Ordering::SeqCst);
let theme = p.theme.lock().unwrap();
let frosted = if theme.cosmic().frosted_windows {
(theme.cosmic().frosted as u8 + 1) as usize
} else {
0
};
windows[active] windows[active].push_popup_render_elements(
.popup_render_elements::<R, CosmicStackRenderElement<R>>( renderer,
renderer, window_loc, scale, alpha, window_loc,
) scale,
.into_iter() alpha,
.map(C::from) scanout_node,
.collect() frosted,
&mut |elem| push(elem.into()),
)
}) })
} }
@ -742,7 +748,7 @@ impl CosmicStack {
}) })
} }
pub fn render_elements<R, C>( pub fn push_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: Point<i32, Physical>, location: Point<i32, Physical>,
@ -750,17 +756,18 @@ impl CosmicStack {
scale: Scale<f64>, scale: Scale<f64>,
alpha: f32, alpha: f32,
scanout_override: Option<bool>, scanout_override: Option<bool>,
) -> Vec<C> scanout_node: Option<DrmNode>,
where push_above: &mut dyn FnMut(CosmicStackRenderElement<R>),
push_below: &mut dyn FnMut(CosmicStackRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
C: From<CosmicStackRenderElement<R>>,
{ {
if !self if !self
.0 .0
.with_program(|p| p.override_alive.load(Ordering::Acquire)) .with_program(|p| p.override_alive.load(Ordering::Acquire))
{ {
return Vec::new(); return;
} }
let geometry = self let geometry = self
@ -769,27 +776,36 @@ impl CosmicStack {
.to_physical_precise_round(scale); .to_physical_precise_round(scale);
let stack_loc = location + geometry.loc; let stack_loc = location + geometry.loc;
let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32));
let frosted = self.0.with_program(|p| {
let mut elements = AsRenderElements::<R>::render_elements::<CosmicStackRenderElement<R>>( let theme = p.theme.lock().unwrap();
&self.0, renderer, stack_loc, scale, alpha, if theme.cosmic().frosted_windows {
); (theme.cosmic().frosted as u8 + 1) as usize
} else {
elements.extend(self.0.with_program(|p| { 0
}
});
let radii = self.0.with_program(|p| {
let windows = p.windows.lock().unwrap(); let windows = p.windows.lock().unwrap();
let active = p.active.load(Ordering::SeqCst); let active = p.active.load(Ordering::SeqCst);
let theme = p.theme.lock().unwrap();
let appearance = p.appearance_conf.lock().unwrap(); let appearance = p.appearance_conf.lock().unwrap();
let theme = p.theme.lock().unwrap();
let tiled = p.tiled.load(Ordering::Acquire); let tiled = p.tiled.load(Ordering::Acquire);
let maximized = windows[active].is_maximized(false); let maximized = windows[active].is_maximized(false);
let round = (appearance.clip_tiled_windows || !tiled) && !maximized; let round = (appearance.clip_tiled_windows || !tiled) && !maximized;
let radii = round.then(|| { round.then(|| {
theme theme
.cosmic() .cosmic()
.radius_s() .radius_s()
.map(|x| if x < 4.0 { x } else { x + 4.0 }) .map(|x| if x < 4.0 { x } else { x + 4.0 })
.map(|x| x.round() as u8) .map(|x| x.round() as u8)
}); })
});
self.0.with_program(|p| {
let windows = p.windows.lock().unwrap();
let active = p.active.load(Ordering::SeqCst);
let theme = p.theme.lock().unwrap();
let maximized = windows[active].is_maximized(false);
let mut geo = SpaceElement::geometry(&windows[active]).to_f64(); let mut geo = SpaceElement::geometry(&windows[active]).to_f64();
geo.loc += location.to_f64().to_logical(scale); geo.loc += location.to_f64().to_logical(scale);
@ -801,53 +817,49 @@ impl CosmicStack {
let window_key = let window_key =
CosmicMappedKey(CosmicMappedKeyInner::Stack(Arc::downgrade(&self.0.0))); CosmicMappedKey(CosmicMappedKeyInner::Stack(Arc::downgrade(&self.0.0)));
let border = (!maximized).then(|| { if !maximized {
let (r, g, b, a) = theme.cosmic().bg_divider().into_components(); let (r, g, b, a) = theme.cosmic().bg_divider().into_components();
CosmicStackRenderElement::Border(IndicatorShader::element( push_above(CosmicStackRenderElement::Border(IndicatorShader::element(
renderer, renderer,
Key::Window(Usage::Border, window_key.clone()), Key::Window(Usage::Border, window_key.clone()),
geo.to_i32_round().as_local(), geo.to_i32_round().as_local(),
1, 1,
radii.unwrap_or([0, 0, 0, 0]), radii.unwrap_or([0; 4]),
a * alpha, a * alpha,
scale.x, scale.x,
[r, g, b], [r, g, b],
)) )));
}); };
border.into_iter().chain( let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]);
windows[active] windows[active].push_render_elements(
.render_elements::<R, WaylandSurfaceRenderElement<R>>( renderer,
renderer, window_loc,
window_loc, scale,
scale, alpha,
alpha, scanout_override,
scanout_override, scanout_node,
) radii.is_some(),
.into_iter() radii.unwrap_or([0; 4]),
.map(move |elem| { frosted,
let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]); &mut |elem| push_above(elem.into()),
if radii.is_some_and(|radii| { Some(&mut |elem| push_below(elem.into())),
ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radii) );
}) { });
CosmicStackRenderElement::Clipped(ClippedSurfaceRenderElement::new(
renderer,
elem,
scale,
geo,
radii.unwrap(),
))
} else {
CosmicStackRenderElement::Window(elem)
}
}),
)
}));
elements.into_iter().map(C::from).collect() self.0.push_render_elements(
renderer,
stack_loc,
scale,
alpha,
radii.map(|[_, b, _, d]| [0, b, 0, d]).unwrap_or([0; 4]),
&mut |elem| push_above(elem.into()),
Some(&mut |elem| push_below(elem.into())),
);
} }
pub(crate) fn set_theme(&self, theme: cosmic::Theme) { pub(crate) fn set_theme(&self, mut theme: cosmic::Theme) {
theme.transparent = theme.cosmic().frosted_windows;
self.0.with_program(|p| { self.0.with_program(|p| {
*p.theme.lock().unwrap() = theme.clone(); *p.theme.lock().unwrap() = theme.clone();
}); });
@ -1356,11 +1368,14 @@ impl Decorations<CosmicStackInternal, Message> for DefaultDecorations {
.class(theme::Container::custom(move |theme| { .class(theme::Container::custom(move |theme| {
let cosmic_theme = theme.cosmic(); let cosmic_theme = theme.cosmic();
let background = if group_focused { let mut background = if group_focused {
cosmic_theme.accent_color() cosmic_theme.accent_color()
} else { } else {
cosmic_theme.primary_container_color() cosmic_theme.primary_container_color()
}; };
if cosmic_theme.frosted_windows {
background.alpha = cosmic_theme.alpha_map.blurred_alpha(cosmic_theme.frosted);
}
iced_widget::container::Style { iced_widget::container::Style {
snap: true, snap: true,
@ -1877,40 +1892,32 @@ impl TouchTarget<State> for CosmicStack {
} }
pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> { pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> {
Header(MemoryRenderBufferRenderElement<R>), Header(IcedRenderElement<R>),
Shadow(PixelShaderElement), Shadow(PixelShaderElement),
Border(PixelShaderElement), Border(PixelShaderElement),
Window(WaylandSurfaceRenderElement<R>), Window(SurfaceRenderElement<R>),
Clipped(ClippedSurfaceRenderElement<R>),
} }
impl<R: Renderer + ImportAll + ImportMem> From<MemoryRenderBufferRenderElement<R>> impl<R: Renderer + ImportAll + ImportMem> From<IcedRenderElement<R>>
for CosmicStackRenderElement<R> for CosmicStackRenderElement<R>
{ {
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self { fn from(value: IcedRenderElement<R>) -> Self {
Self::Header(value) Self::Header(value)
} }
} }
impl<R: Renderer + ImportAll + ImportMem> From<WaylandSurfaceRenderElement<R>> impl<R: Renderer + ImportAll + ImportMem> From<SurfaceRenderElement<R>>
for CosmicStackRenderElement<R> for CosmicStackRenderElement<R>
{ {
fn from(value: WaylandSurfaceRenderElement<R>) -> Self { fn from(value: SurfaceRenderElement<R>) -> Self {
Self::Window(value) Self::Window(value)
} }
} }
impl<R: Renderer + ImportAll + ImportMem> From<ClippedSurfaceRenderElement<R>>
for CosmicStackRenderElement<R>
{
fn from(value: ClippedSurfaceRenderElement<R>) -> Self {
Self::Clipped(value)
}
}
impl<R> Element for CosmicStackRenderElement<R> impl<R> Element for CosmicStackRenderElement<R>
where where
R: Renderer + ImportAll + ImportMem, R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
R::TextureId: Send + 'static,
{ {
fn id(&self) -> &RendererId { fn id(&self) -> &RendererId {
match self { match self {
@ -1918,7 +1925,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.id(), CosmicStackRenderElement::Shadow(elem) => elem.id(),
CosmicStackRenderElement::Border(elem) => elem.id(), CosmicStackRenderElement::Border(elem) => elem.id(),
CosmicStackRenderElement::Window(elem) => elem.id(), CosmicStackRenderElement::Window(elem) => elem.id(),
CosmicStackRenderElement::Clipped(elem) => elem.id(),
} }
} }
@ -1928,7 +1934,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.current_commit(), CosmicStackRenderElement::Shadow(elem) => elem.current_commit(),
CosmicStackRenderElement::Border(elem) => elem.current_commit(), CosmicStackRenderElement::Border(elem) => elem.current_commit(),
CosmicStackRenderElement::Window(elem) => elem.current_commit(), CosmicStackRenderElement::Window(elem) => elem.current_commit(),
CosmicStackRenderElement::Clipped(elem) => elem.current_commit(),
} }
} }
@ -1938,7 +1943,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.src(), CosmicStackRenderElement::Shadow(elem) => elem.src(),
CosmicStackRenderElement::Border(elem) => elem.src(), CosmicStackRenderElement::Border(elem) => elem.src(),
CosmicStackRenderElement::Window(elem) => elem.src(), CosmicStackRenderElement::Window(elem) => elem.src(),
CosmicStackRenderElement::Clipped(elem) => elem.src(),
} }
} }
@ -1948,7 +1952,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.geometry(scale), CosmicStackRenderElement::Shadow(elem) => elem.geometry(scale),
CosmicStackRenderElement::Border(elem) => elem.geometry(scale), CosmicStackRenderElement::Border(elem) => elem.geometry(scale),
CosmicStackRenderElement::Window(elem) => elem.geometry(scale), CosmicStackRenderElement::Window(elem) => elem.geometry(scale),
CosmicStackRenderElement::Clipped(elem) => elem.geometry(scale),
} }
} }
@ -1958,7 +1961,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.location(scale), CosmicStackRenderElement::Shadow(elem) => elem.location(scale),
CosmicStackRenderElement::Border(elem) => elem.location(scale), CosmicStackRenderElement::Border(elem) => elem.location(scale),
CosmicStackRenderElement::Window(elem) => elem.location(scale), CosmicStackRenderElement::Window(elem) => elem.location(scale),
CosmicStackRenderElement::Clipped(elem) => elem.location(scale),
} }
} }
@ -1968,7 +1970,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.transform(), CosmicStackRenderElement::Shadow(elem) => elem.transform(),
CosmicStackRenderElement::Border(elem) => elem.transform(), CosmicStackRenderElement::Border(elem) => elem.transform(),
CosmicStackRenderElement::Window(elem) => elem.transform(), CosmicStackRenderElement::Window(elem) => elem.transform(),
CosmicStackRenderElement::Clipped(elem) => elem.transform(),
} }
} }
@ -1982,7 +1983,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Shadow(elem) => elem.damage_since(scale, commit),
CosmicStackRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Border(elem) => elem.damage_since(scale, commit),
CosmicStackRenderElement::Window(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Window(elem) => elem.damage_since(scale, commit),
CosmicStackRenderElement::Clipped(elem) => elem.damage_since(scale, commit),
} }
} }
@ -1992,7 +1992,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Shadow(elem) => elem.opaque_regions(scale),
CosmicStackRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Border(elem) => elem.opaque_regions(scale),
CosmicStackRenderElement::Window(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Window(elem) => elem.opaque_regions(scale),
CosmicStackRenderElement::Clipped(elem) => elem.opaque_regions(scale),
} }
} }
@ -2002,7 +2001,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.alpha(), CosmicStackRenderElement::Shadow(elem) => elem.alpha(),
CosmicStackRenderElement::Border(elem) => elem.alpha(), CosmicStackRenderElement::Border(elem) => elem.alpha(),
CosmicStackRenderElement::Window(elem) => elem.alpha(), CosmicStackRenderElement::Window(elem) => elem.alpha(),
CosmicStackRenderElement::Clipped(elem) => elem.alpha(),
} }
} }
@ -2012,7 +2010,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.kind(), CosmicStackRenderElement::Shadow(elem) => elem.kind(),
CosmicStackRenderElement::Border(elem) => elem.kind(), CosmicStackRenderElement::Border(elem) => elem.kind(),
CosmicStackRenderElement::Window(elem) => elem.kind(), CosmicStackRenderElement::Window(elem) => elem.kind(),
CosmicStackRenderElement::Clipped(elem) => elem.kind(),
} }
} }
@ -2022,7 +2019,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Shadow(elem) => elem.is_framebuffer_effect(),
CosmicStackRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Border(elem) => elem.is_framebuffer_effect(),
CosmicStackRenderElement::Window(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Window(elem) => elem.is_framebuffer_effect(),
CosmicStackRenderElement::Clipped(elem) => elem.is_framebuffer_effect(),
} }
} }
} }
@ -2030,12 +2026,11 @@ where
impl<R> RenderElement<R> for CosmicStackRenderElement<R> impl<R> RenderElement<R> for CosmicStackRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
R::Error: FromGlesError,
{ {
fn draw( fn draw(
&self, &self,
frame: &mut <R>::Frame<'_, '_>, frame: &mut R::Frame<'_, '_>,
src: Rectangle<f64, Buffer>, src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>, dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>], damage: &[Rectangle<i32, Physical>],
@ -2056,14 +2051,11 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicStackRenderElement::Window(elem) => { CosmicStackRenderElement::Window(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache) elem.draw(frame, src, dst, damage, opaque_regions, cache)
} }
CosmicStackRenderElement::Clipped(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache)
}
} }
} }
@ -2074,7 +2066,6 @@ where
elem.underlying_storage(renderer.glow_renderer_mut()) elem.underlying_storage(renderer.glow_renderer_mut())
} }
CosmicStackRenderElement::Window(elem) => elem.underlying_storage(renderer), CosmicStackRenderElement::Window(elem) => elem.underlying_storage(renderer),
CosmicStackRenderElement::Clipped(elem) => elem.underlying_storage(renderer),
} }
} }
@ -2097,14 +2088,11 @@ where
dst, dst,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicStackRenderElement::Window(elem) => { CosmicStackRenderElement::Window(elem) => {
elem.capture_framebuffer(frame, src, dst, cache) elem.capture_framebuffer(frame, src, dst, cache)
} }
CosmicStackRenderElement::Clipped(elem) => {
elem.capture_framebuffer(frame, src, dst, cache)
}
} }
} }
} }

View file

@ -3,16 +3,15 @@ use cosmic::{
font::Font, font::Font,
iced::{ iced::{
Background, Background,
widget::{self, container::draw_background, rule::FillMode}, core::{
Border, Clipboard, Color, Length, Rectangle, Shell, Size, alignment, event,
layout::{Layout, Limits, Node},
mouse, overlay, renderer,
text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping},
widget::{Id, Widget, operation::Operation, tree::Tree},
},
widget::{self, container::draw_background, rule::FillMode, scrollable::AbsoluteOffset},
}, },
iced_core::{
Border, Clipboard, Color, Length, Rectangle, Shell, Size, alignment, event,
layout::{Layout, Limits, Node},
mouse, overlay, renderer,
text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping},
widget::{Id, Widget, operation::Operation, tree::Tree},
},
iced_widget::scrollable::AbsoluteOffset,
theme, theme,
widget::{Icon, icon::from_name, text}, widget::{Icon, icon::from_name, text},
}; };
@ -289,8 +288,8 @@ where
.min_height(size.height) .min_height(size.height)
.width(size.width) .width(size.width)
.height(size.height); .height(size.height);
cosmic::iced_core::layout::flex::resolve( cosmic::iced::core::layout::flex::resolve(
cosmic::iced_core::layout::flex::Axis::Horizontal, cosmic::iced::core::layout::flex::Axis::Horizontal,
renderer, renderer,
&limits, &limits,
Length::Fill, Length::Fill,

View file

@ -1,24 +1,30 @@
use super::tab::{MIN_ACTIVE_TAB_WIDTH, Tab, TabBackgroundTheme, TabMessage, TabRuleTheme}; use super::tab::{MIN_ACTIVE_TAB_WIDTH, Tab, TabBackgroundTheme, TabMessage, TabRuleTheme};
use cosmic::{ use cosmic::{
Apply, Apply,
iced::{Element, id::Id, widget}, iced::{
iced_core::{ Element,
Background, Border, Clipboard, Color, Length, Point, Rectangle, Renderer, Shell, Size, core::{
Vector, event, Background, Border, Clipboard, Color, Length, Point, Rectangle, Renderer, Shell, Size,
layout::{Layout, Limits, Node}, Vector, event,
mouse, overlay, renderer, layout::{Layout, Limits, Node},
widget::{ mouse, overlay, renderer,
Widget, widget::{
operation::{ Widget,
Operation, Scrollable, operation::{
scrollable::{AbsoluteOffset, RelativeOffset}, Operation, Scrollable,
scrollable::{AbsoluteOffset, RelativeOffset},
},
tree::{self, Tree},
}, },
tree::{self, Tree}, },
id::Id,
widget::{
self,
container::{Catalog, draw_background},
}, },
}, },
iced_widget::container::draw_background,
theme, theme,
widget::{container::Catalog, icon::from_name}, widget::icon::from_name,
}; };
use keyframe::{ use keyframe::{
ease, ease,
@ -379,8 +385,8 @@ where
>= MIN_ACTIVE_TAB_WIDTH >= MIN_ACTIVE_TAB_WIDTH
{ {
// just use a flex layout // just use a flex layout
cosmic::iced_core::layout::flex::resolve( cosmic::iced::core::layout::flex::resolve(
cosmic::iced_core::layout::flex::Axis::Horizontal, cosmic::iced::core::layout::flex::Axis::Horizontal,
renderer, renderer,
&limits, &limits,
self.width, self.width,
@ -513,13 +519,11 @@ where
let background_style = Catalog::style( let background_style = Catalog::style(
theme, theme,
&theme::Container::custom(|theme| widget::container::Style { &theme::Container::custom(|_theme| widget::container::Style {
snap: true, snap: true,
icon_color: None, icon_color: None,
text_color: None, text_color: None,
background: Some(Background::Color( background: Some(Background::Color(Color::TRANSPARENT)),
theme.cosmic().primary_container_color().into(),
)),
border: Border { border: Border {
radius: 0.0.into(), radius: 0.0.into(),
width: 0.0, width: 0.0,

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
backend::render::element::AsGlowRenderer,
fl, fl,
utils::iced::{IcedElement, Program}, utils::iced::{IcedElement, IcedRenderElement, Program},
}; };
use calloop::LoopHandle; use calloop::LoopHandle;
@ -8,22 +9,78 @@ use cosmic::{
Apply, Apply,
iced::{ iced::{
Alignment, Alignment,
core::{Background, Border, Color, Length},
widget::{container, row}, widget::{container, row},
}, },
iced_core::{Background, Border, Color, Length},
theme, theme,
widget::{icon::from_name, space, text}, widget::{icon::from_name, space, text},
}; };
use smithay::utils::{Logical, Size}; use smithay::{
backend::renderer::ImportMem,
desktop::space::SpaceElement,
output::Output,
utils::{Logical, Physical, Point, Rectangle, Scale, Size},
};
pub type StackHover = IcedElement<StackHoverInternal>; #[derive(Debug)]
pub struct StackHover {
location: Point<i32, Logical>,
elem: IcedElement<StackHoverInternal>,
}
pub fn stack_hover( impl StackHover {
evlh: LoopHandle<'static, crate::state::State>, pub fn new(
size: Size<i32, Logical>, evlh: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme, size: Size<i32, Logical>,
) -> StackHover { mut theme: cosmic::Theme,
StackHover::new(StackHoverInternal, size, evlh, theme) ) -> StackHover {
theme.transparent = theme.cosmic().frosted_system_interface;
let elem = IcedElement::new(StackHoverInternal, size, evlh, theme);
let minimum = elem.minimum_size();
let new_size = Size::<i32, Logical>::new(size.w.min(minimum.w), size.h.min(minimum.h));
let location = Point::new(
size.w.saturating_sub(new_size.w) / 2,
size.h.saturating_sub(new_size.h) / 2,
);
elem.resize(new_size);
StackHover { location, elem }
}
pub fn push_render_elements<R>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
push_above: &mut dyn FnMut(IcedRenderElement<R>),
push_below: Option<&mut dyn FnMut(IcedRenderElement<R>)>,
) where
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
self.elem.push_render_elements(
renderer,
location + self.location.to_physical_precise_round(scale),
scale,
alpha,
self.elem
.with_theme(|theme| theme.cosmic().radius_s())
.map(|x| x.round() as u8),
push_above,
push_below,
);
}
pub fn output_enter(&self, output: &Output) {
self.elem
.output_enter(output, Rectangle::default() /*unused*/);
}
pub fn output_leave(&self, output: &Output) {
self.elem.output_leave(output);
}
} }
pub struct StackHoverInternal; pub struct StackHoverInternal;
@ -47,23 +104,30 @@ impl Program for StackHoverInternal {
.align_y(Alignment::Center) .align_y(Alignment::Center)
.padding(16) .padding(16)
.apply(container) .apply(container)
.class(theme::Container::custom(|theme| container::Style { .class(theme::Container::custom(|theme| {
snap: true, let mut background = theme.cosmic().accent_color();
icon_color: Some(Color::from(theme.cosmic().accent.on)), if theme.transparent {
text_color: Some(Color::from(theme.cosmic().accent.on)), background.alpha = theme
background: Some(Background::Color(theme.cosmic().accent_color().into())), .cosmic()
border: Border { .alpha_map
radius: 18.0.into(), .blurred_alpha(theme.cosmic().frosted);
width: 0.0, }
color: Color::TRANSPARENT,
}, container::Style {
shadow: Default::default(), snap: true,
icon_color: Some(Color::from(theme.cosmic().accent.on)),
text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(background.into())),
border: Border {
radius: theme.cosmic().radius_s().into(),
width: 0.0,
color: Color::TRANSPARENT,
},
shadow: Default::default(),
}
})) }))
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Shrink) .height(Length::Shrink)
.apply(container)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into() .into()
} }
} }

View file

@ -1,7 +1,12 @@
use crate::{ use crate::{
backend::render::{
element::AsGlowRenderer,
wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree},
},
shell::focus::target::PointerFocusTarget, shell::focus::target::PointerFocusTarget,
wayland::{ wayland::handlers::{
handlers::compositor::frame_time_filter_fn, protocols::corner_radius::CacheableCorners, background_effect::ComputedBlurRegionCachedState, compositor::frame_time_filter_fn,
corner_radius::surface_corners,
}, },
}; };
use std::{ use std::{
@ -14,11 +19,12 @@ use std::{
}; };
use smithay::{ use smithay::{
backend::renderer::{ backend::{
ImportAll, Renderer, drm::DrmNode,
element::{ renderer::{
AsRenderElements, Kind, RenderElementStates, ImportAll, Renderer, buffer_has_alpha,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree}, element::{Kind, RenderElementStates, surface::KindEvaluation},
utils::RendererSurfaceStateUserData,
}, },
}, },
desktop::{ desktop::{
@ -45,10 +51,12 @@ use smithay::{
IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, user_data::UserDataMap, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, user_data::UserDataMap,
}, },
wayland::{ wayland::{
alpha_modifier::AlphaModifierSurfaceCachedState,
compositor::{ compositor::{
SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states, SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states,
with_surface_tree_downward, with_surface_tree_downward,
}, },
dmabuf::get_dmabuf,
seat::WaylandFocus, seat::WaylandFocus,
shell::xdg::{ shell::xdg::{
SurfaceCachedState, ToplevelCachedState, ToplevelSurface, XdgToplevelSurfaceData, SurfaceCachedState, ToplevelCachedState, ToplevelSurface, XdgToplevelSurfaceData,
@ -67,6 +75,83 @@ use crate::{
}, },
}; };
/// The [`DrmNode`] the surface's currently committed buffer was allocated on, if it is a dmabuf.
fn buffer_node(data: &SurfaceData) -> Option<DrmNode> {
let surface_state = data.data_map.get::<RendererSurfaceStateUserData>()?;
let surface_state = surface_state.lock().unwrap();
surface_state
.buffer()
.and_then(|buffer| get_dmabuf(buffer).ok())
.and_then(|dmabuf| dmabuf.node())
}
fn is_likely_translucent(alpha: f32, data: &SurfaceData) -> bool {
if alpha < 1.0 {
return true;
}
let mut alpha_modifier_state = data.cached_state.get::<AlphaModifierSurfaceCachedState>();
let alpha_multiplier = alpha_modifier_state
.current()
.multiplier_f32()
.unwrap_or(1.0);
if alpha_multiplier < 1.0 {
return true;
}
let Some(surface_state) = data.data_map.get::<RendererSurfaceStateUserData>() else {
return false;
};
let surface_state = surface_state.lock().unwrap();
if surface_state
.buffer()
.is_none_or(|buffer| !buffer_has_alpha(buffer).unwrap_or(true))
{
return false;
}
let mut blur_state = data.cached_state.get::<ComputedBlurRegionCachedState>();
blur_state
.current()
.blur_region
.as_ref()
.is_some_and(|region| !region.is_empty())
}
/// Build the [`KindEvaluation`] for a window's surface tree.
///
/// `scanout_node`, when set, is the scan-out target [`DrmNode`] of the output currently being
/// rendered: only buffers allocated on that node may be promoted to overlay scan-out candidates.
/// It is `None` for render passes that never scan out to a plane (e.g. screen-copy).
fn scanout_kind_eval(
scanout_override: Option<bool>,
scanout_node: Option<DrmNode>,
alpha: f32,
) -> KindEvaluation {
match (scanout_override, scanout_node) {
// Forced off.
(Some(false), _) => Kind::Unspecified.into(),
// No node restriction: preserve the previous behaviour exactly.
(Some(true), None) => Kind::ScanoutCandidate.into(),
(None, None) => FRAME_TIME_FILTER,
// Node restriction in effect: only buffers on the scan-out node may be candidates.
(Some(true), Some(node)) => KindEvaluation::Closure(Box::new(move |data| {
if buffer_node(data) == Some(node) && !is_likely_translucent(alpha, data) {
Kind::ScanoutCandidate
} else {
Kind::Unspecified
}
})),
(None, Some(node)) => KindEvaluation::Closure(Box::new(move |data| {
if buffer_node(data) == Some(node) && !is_likely_translucent(alpha, data) {
frame_time_filter_fn(data)
} else {
Kind::Unspecified
}
})),
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq)] #[derive(Debug, Clone, PartialEq, Hash, Eq)]
pub struct CosmicSurface(pub Window); pub struct CosmicSurface(pub Window);
@ -144,22 +229,7 @@ impl CosmicSurface {
pub fn corner_radius(&self, geometry_size: Size<i32, Logical>) -> Option<[u8; 4]> { pub fn corner_radius(&self, geometry_size: Size<i32, Logical>) -> Option<[u8; 4]> {
self.wl_surface().and_then(|surface| { self.wl_surface().and_then(|surface| {
with_states(&surface, |states| { with_states(&surface, |states| surface_corners(states, geometry_size))
let mut guard = states.cached_state.get::<CacheableCorners>();
// guard against corner radius being too large, potentially disconnecting the outline
let half_min_dim =
u8::try_from(geometry_size.w.min(geometry_size.h) / 2).unwrap_or(u8::MAX);
let corners = guard.current().0?;
Some([
corners.bottom_right.min(half_min_dim),
corners.top_right.min(half_min_dim),
corners.bottom_left.min(half_min_dim),
corners.top_left.min(half_min_dim),
])
})
}) })
} }
@ -810,97 +880,112 @@ impl CosmicSurface {
self.0.user_data() self.0.user_data()
} }
pub fn popup_render_elements<R, C>( pub fn push_popup_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: Point<i32, Physical>, location: Point<i32, Physical>,
scale: Scale<f64>, scale: Scale<f64>,
alpha: f32, alpha: f32,
) -> Vec<C> scanout_node: Option<DrmNode>,
where blur_strength: usize,
R: Renderer + ImportAll, push: &mut dyn FnMut(SurfaceRenderElement<R>),
) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static, R::TextureId: Clone + 'static,
C: From<WaylandSurfaceRenderElement<R>>,
{ {
match self.0.underlying_surface() { match self.0.underlying_surface() {
WindowSurface::Wayland(toplevel) => { WindowSurface::Wayland(toplevel) => {
let surface = toplevel.wl_surface(); let surface = toplevel.wl_surface();
PopupManager::popups_for_surface(surface) for (popup, popup_offset) in PopupManager::popups_for_surface(surface) {
.flat_map(move |(popup, popup_offset)| { let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc)
let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc) .to_physical_precise_round(scale);
.to_physical_precise_round(scale); let mut geometry = popup.geometry().to_f64();
geometry.loc += location.to_f64().to_logical(scale) + popup_offset.to_f64();
render_elements_from_surface_tree( let radii = with_states(popup.wl_surface(), |states| {
renderer, surface_corners(states, geometry.size.to_i32_round())
popup.wl_surface(),
location + offset,
scale,
alpha,
FRAME_TIME_FILTER,
)
}) })
.collect() .unwrap_or([0; 4]);
push_render_elements_from_surface_tree(
renderer,
popup.wl_surface(),
location + offset,
geometry,
scale,
alpha,
false,
radii,
None,
blur_strength,
scanout_kind_eval(None, scanout_node, alpha),
push,
None,
)
}
} }
WindowSurface::X11(_) => Vec::new(), WindowSurface::X11(_) => {}
} }
} }
pub fn render_elements<R, C>( pub fn push_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: Point<i32, Physical>, location: Point<i32, Physical>,
scale: Scale<f64>, scale: Scale<f64>,
alpha: f32, alpha: f32,
scanout_override: Option<bool>, scanout_override: Option<bool>,
) -> Vec<C> scanout_node: Option<DrmNode>,
where should_clip: bool,
R: Renderer + ImportAll, radii: [u8; 4],
blur_strength: usize,
push_above: &mut dyn FnMut(SurfaceRenderElement<R>),
push_below: Option<&mut dyn FnMut(SurfaceRenderElement<R>)>,
) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static, R::TextureId: Clone + 'static,
C: From<WaylandSurfaceRenderElement<R>>,
{ {
let mut geometry = self.0.geometry().to_f64();
geometry.loc += location.to_f64().to_logical(scale);
match self.0.underlying_surface() { match self.0.underlying_surface() {
WindowSurface::Wayland(toplevel) => { WindowSurface::Wayland(toplevel) => {
let surface = toplevel.wl_surface(); let surface = toplevel.wl_surface();
render_elements_from_surface_tree( push_render_elements_from_surface_tree(
renderer, renderer,
surface, surface,
location, location,
geometry,
scale, scale,
alpha, alpha,
scanout_override should_clip,
.map(|val| { radii,
if val { None,
Kind::ScanoutCandidate blur_strength,
} else { scanout_kind_eval(scanout_override, scanout_node, alpha),
Kind::Unspecified push_above,
} push_below,
.into()
})
.unwrap_or(FRAME_TIME_FILTER),
) )
} }
WindowSurface::X11(surface) => { WindowSurface::X11(surface) => {
let Some(surface) = surface.wl_surface() else { let Some(surface) = surface.wl_surface() else {
return Vec::new(); return;
}; };
render_elements_from_surface_tree( push_render_elements_from_surface_tree(
renderer, renderer,
&surface, &surface,
location, location,
geometry,
scale, scale,
alpha, alpha,
scanout_override should_clip,
.map(|val| { radii,
if val { None,
Kind::ScanoutCandidate blur_strength,
} else { scanout_kind_eval(scanout_override, scanout_node, alpha),
Kind::Unspecified push_above,
} push_below,
.into()
})
.unwrap_or(FRAME_TIME_FILTER),
) )
} }
} }
@ -1044,24 +1129,6 @@ impl X11Relatable for CosmicSurface {
} }
} }
impl<R> AsRenderElements<R> for CosmicSurface
where
R: Renderer + ImportAll,
R::TextureId: Clone + 'static,
{
type RenderElement = WaylandSurfaceRenderElement<R>;
fn render_elements<C: From<Self::RenderElement>>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
) -> Vec<C> {
self.0.render_elements(renderer, location, scale, alpha)
}
}
fn with_toplevel_state<T, F: FnOnce(Option<&smithay::wayland::shell::xdg::ToplevelState>) -> T>( fn with_toplevel_state<T, F: FnOnce(Option<&smithay::wayland::shell::xdg::ToplevelState>) -> T>(
toplevel: &ToplevelSurface, toplevel: &ToplevelSurface,
pending: bool, pending: bool,

View file

@ -1,25 +1,88 @@
use crate::{ use crate::{
backend::render::element::AsGlowRenderer,
fl, fl,
utils::iced::{IcedElement, Program}, utils::iced::{IcedElement, IcedRenderElement, Program},
}; };
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic::{ use cosmic::{
Apply, Apply,
iced::widget::{container, row, space}, iced::{
iced_core::{Alignment, Background, Border, Color, Length}, core::{Alignment, Background, Border, Color, Length},
widget::{container, row, space},
},
theme, theme,
widget::{icon::from_name, text}, widget::{icon::from_name, text},
}; };
use smithay::utils::Size; use smithay::{
backend::renderer::ImportMem,
desktop::space::SpaceElement,
output::Output,
utils::{Logical, Physical, Point, Rectangle, Scale, Size},
};
pub type SwapIndicator = IcedElement<SwapIndicatorInternal>; #[derive(Debug, Clone)]
pub struct SwapIndicator {
location: Point<i32, Logical>,
elem: IcedElement<SwapIndicatorInternal>,
}
pub fn swap_indicator( impl SwapIndicator {
evlh: LoopHandle<'static, crate::state::State>, pub fn new(
theme: cosmic::Theme, evlh: LoopHandle<'static, crate::state::State>,
) -> SwapIndicator { mut theme: cosmic::Theme,
SwapIndicator::new(SwapIndicatorInternal, Size::from((1, 1)), evlh, theme) ) -> SwapIndicator {
theme.transparent = theme.cosmic().frosted_system_interface;
SwapIndicator {
location: Point::default(),
elem: IcedElement::new(SwapIndicatorInternal, Size::from((1, 1)), evlh, theme),
}
}
pub fn resize(&mut self, size: Size<i32, Logical>) {
let minimum = self.elem.minimum_size();
let new_size = Size::<i32, Logical>::new(size.w.min(minimum.w), size.h.min(minimum.h));
let location = Point::new(
size.w.saturating_sub(new_size.w) / 2,
size.h.saturating_sub(new_size.h) / 2,
);
self.elem.resize(new_size);
self.location = location;
}
pub fn push_render_elements<R>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
push_above: &mut dyn FnMut(IcedRenderElement<R>),
push_below: Option<&mut dyn FnMut(IcedRenderElement<R>)>,
) where
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
self.elem.push_render_elements(
renderer,
location + self.location.to_physical_precise_round(scale),
scale,
alpha,
self.elem
.with_theme(|theme| theme.cosmic().radius_s())
.map(|x| x.round() as u8),
push_above,
push_below,
);
}
pub fn output_enter(&self, output: &Output) {
self.elem
.output_enter(output, Rectangle::default() /*unused*/);
}
pub fn output_leave(&self, output: &Output) {
self.elem.output_leave(output);
}
} }
pub struct SwapIndicatorInternal; pub struct SwapIndicatorInternal;
@ -43,23 +106,30 @@ impl Program for SwapIndicatorInternal {
.align_y(Alignment::Center) .align_y(Alignment::Center)
.padding(16) .padding(16)
.apply(container) .apply(container)
.class(theme::Container::custom(|theme| container::Style { .class(theme::Container::custom(|theme| {
snap: true, let mut background = theme.cosmic().accent_color();
icon_color: Some(Color::from(theme.cosmic().accent.on)), if theme.transparent {
text_color: Some(Color::from(theme.cosmic().accent.on)), background.alpha = theme
background: Some(Background::Color(theme.cosmic().accent_color().into())), .cosmic()
border: Border { .alpha_map
radius: 18.0.into(), .blurred_alpha(theme.cosmic().frosted);
width: 0.0, }
color: Color::TRANSPARENT,
}, container::Style {
shadow: Default::default(), snap: true,
icon_color: Some(Color::from(theme.cosmic().accent.on)),
text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(background.into())),
border: Border {
radius: theme.cosmic().radius_s().into(),
width: 0.0,
color: Color::TRANSPARENT,
},
shadow: Default::default(),
}
})) }))
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Shrink) .height(Length::Shrink)
.apply(container)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into() .into()
} }
} }

View file

@ -1,10 +1,7 @@
use crate::{ use crate::{
backend::render::{ backend::render::{
IndicatorShader, Key, Usage, IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer,
clipped_surface::ClippedSurfaceRenderElement, shadow::ShadowShader, wayland::SurfaceRenderElement,
cursor::CursorState,
element::{AsGlowRenderer, FromGlesError},
shadow::ShadowShader,
}, },
hooks::{Decorations, HOOKS}, hooks::{Decorations, HOOKS},
shell::{ shell::{
@ -14,7 +11,7 @@ use crate::{
}, },
state::State, state::State,
utils::{ utils::{
iced::{IcedElement, Program}, iced::{IcedElement, IcedRenderElement, Program},
prelude::*, prelude::*,
}, },
}; };
@ -23,14 +20,11 @@ use cosmic::iced::{Color, Task};
use cosmic_comp_config::AppearanceConfig; use cosmic_comp_config::AppearanceConfig;
use smithay::{ use smithay::{
backend::{ backend::{
drm::DrmNode,
input::KeyState, input::KeyState,
renderer::{ renderer::{
ImportAll, ImportMem, Renderer, ImportAll, ImportMem, Renderer,
element::{ element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
AsRenderElements, Element, Id as RendererId, Kind, RenderElement,
UnderlyingStorage, memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
},
gles::element::PixelShaderElement, gles::element::PixelShaderElement,
glow::GlowRenderer, glow::GlowRenderer,
utils::{CommitCounter, DamageSet, OpaqueRegions}, utils::{CommitCounter, DamageSet, OpaqueRegions},
@ -119,7 +113,7 @@ impl Focus {
location: Point<f64, Logical>, location: Point<f64, Logical>,
) -> Option<Focus> { ) -> Option<Focus> {
let geo = surface.geometry(); let geo = surface.geometry();
let loc = location.to_i32_round::<i32>() - geo.loc; let loc = location.to_i32_floor::<i32>() - geo.loc;
if loc.y < 0 && loc.x < 0 { if loc.y < 0 && loc.x < 0 {
Some(Focus::ResizeTopLeft) Some(Focus::ResizeTopLeft)
} else if loc.y < 0 && loc.x >= geo.size.w { } else if loc.y < 0 && loc.x >= geo.size.w {
@ -196,12 +190,13 @@ impl CosmicWindow {
pub fn new( pub fn new(
window: impl Into<CosmicSurface>, window: impl Into<CosmicSurface>,
handle: LoopHandle<'static, crate::state::State>, handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme, mut theme: cosmic::Theme,
appearance: AppearanceConfig, appearance: AppearanceConfig,
) -> CosmicWindow { ) -> CosmicWindow {
let window = window.into(); let window = window.into();
let width = window.geometry().size.w; let width = window.geometry().size.w;
let last_title = window.title(); let last_title = window.title();
theme.transparent = theme.cosmic().frosted_windows;
if appearance.clip_floating_windows { if appearance.clip_floating_windows {
window.set_tiled(true); window.set_tiled(true);
@ -297,7 +292,7 @@ impl CosmicWindow {
{ {
let geo = p.window.geometry(); let geo = p.window.geometry();
let point_i32 = relative_pos.to_i32_round::<i32>(); let point_i32 = relative_pos.to_i32_floor::<i32>();
let ssd_height = if has_ssd { SSD_HEIGHT } else { 0 }; let ssd_height = if has_ssd { SSD_HEIGHT } else { 0 };
if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0) if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0)
@ -351,17 +346,17 @@ impl CosmicWindow {
self.0.loop_handle() self.0.loop_handle()
} }
pub fn popup_render_elements<R, C>( pub fn push_popup_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: Point<i32, Physical>, location: Point<i32, Physical>,
scale: Scale<f64>, scale: Scale<f64>,
alpha: f32, alpha: f32,
) -> Vec<C> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(CosmicWindowRenderElement<R>),
R: Renderer + ImportAll + ImportMem, ) where
R: Renderer + AsGlowRenderer + ImportAll + ImportMem,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
C: From<CosmicWindowRenderElement<R>>,
{ {
let has_ssd = self.0.with_program(|p| p.has_ssd(false)); let has_ssd = self.0.with_program(|p| p.has_ssd(false));
@ -372,13 +367,22 @@ impl CosmicWindow {
}; };
self.0.with_program(|p| { self.0.with_program(|p| {
p.window let theme = p.theme.lock().unwrap();
.popup_render_elements::<R, CosmicWindowRenderElement<R>>( let frosted = if theme.cosmic().frosted_windows {
renderer, window_loc, scale, alpha, (theme.cosmic().frosted as u8 + 1) as usize
) } else {
.into_iter() 0
.map(C::from) };
.collect()
p.window.push_popup_render_elements(
renderer,
window_loc,
scale,
alpha,
scanout_node,
frosted,
&mut |elem| push(elem.into()),
)
}) })
} }
@ -462,7 +466,7 @@ impl CosmicWindow {
}) })
} }
pub fn render_elements<R, C>( pub fn push_render_elements<R>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: Point<i32, Physical>, location: Point<i32, Physical>,
@ -470,11 +474,12 @@ impl CosmicWindow {
scale: Scale<f64>, scale: Scale<f64>,
alpha: f32, alpha: f32,
scanout_override: Option<bool>, scanout_override: Option<bool>,
) -> Vec<C> scanout_node: Option<DrmNode>,
where push_above: &mut dyn FnMut(CosmicWindowRenderElement<R>),
push_below: &mut dyn FnMut(CosmicWindowRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
C: From<CosmicWindowRenderElement<R>>,
{ {
let (has_ssd, is_tiled, is_maximized, mut radii, appearance) = self.0.with_program(|p| { let (has_ssd, is_tiled, is_maximized, mut radii, appearance) = self.0.with_program(|p| {
( (
@ -511,8 +516,6 @@ impl CosmicWindow {
location location
}; };
let mut elements = Vec::new();
let (mut geo, bg_divider) = self.0.with_program(|p| { let (mut geo, bg_divider) = self.0.with_program(|p| {
( (
SpaceElement::geometry(&p.window).to_f64(), SpaceElement::geometry(&p.window).to_f64(),
@ -542,55 +545,57 @@ impl CosmicWindow {
scale.x, scale.x,
[r, g, b], [r, g, b],
)); ));
elements.push(elem); push_above(elem);
} }
let window_elements = self.0.with_program(|p| { self.0.with_program(|p| {
p.window let mut radii = radii;
.render_elements::<R, WaylandSurfaceRenderElement<R>>(
renderer,
window_loc,
scale,
alpha,
scanout_override,
)
});
if window_elements.is_empty() {
return Vec::new();
}
elements.extend(window_elements.into_iter().map(|elem| {
if has_ssd { if has_ssd {
radii[1] = 0; radii[1] = 0;
radii[3] = 0; radii[3] = 0;
} }
if radii.iter().any(|x| *x != 0) let theme = p.theme.lock().unwrap();
&& clip let frosted = if theme.cosmic().frosted_windows {
&& ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radii) (theme.cosmic().frosted as u8 + 1) as usize
{
CosmicWindowRenderElement::Clipped(ClippedSurfaceRenderElement::new(
renderer, elem, scale, geo, radii,
))
} else { } else {
CosmicWindowRenderElement::Window(elem) 0
} };
})); p.window.push_render_elements(
renderer,
window_loc,
scale,
alpha,
scanout_override,
scanout_node,
clip,
radii,
frosted,
&mut |elem| push_above(elem.into()),
Some(&mut |elem| push_below(elem.into())),
);
});
if has_ssd { if has_ssd {
radii[0] = 0;
radii[2] = 0;
let ssd_loc = location let ssd_loc = location
+ self + self
.0 .0
.with_program(|p| p.window.geometry().loc) .with_program(|p| p.window.geometry().loc.to_physical_precise_round(scale));
.to_physical_precise_round(scale); self.0.push_render_elements(
elements.extend(AsRenderElements::<R>::render_elements::< renderer,
CosmicWindowRenderElement<R>, ssd_loc,
>(&self.0, renderer, ssd_loc, scale, alpha)) scale,
alpha,
radii,
&mut |elem| push_above(elem.into()),
Some(&mut |elem| push_below(elem.into())),
);
} }
elements.into_iter().map(C::from).collect()
} }
pub(crate) fn set_theme(&self, theme: cosmic::Theme) { pub(crate) fn set_theme(&self, mut theme: cosmic::Theme) {
theme.transparent = theme.cosmic().frosted_windows;
self.0.with_program(|p| { self.0.with_program(|p| {
*p.theme.lock().unwrap() = theme.clone(); *p.theme.lock().unwrap() = theme.clone();
}); });
@ -1243,41 +1248,33 @@ impl WaylandFocus for CosmicWindow {
} }
} }
pub enum CosmicWindowRenderElement<R: Renderer + ImportAll + ImportMem> { pub enum CosmicWindowRenderElement<R: AsGlowRenderer + ImportAll + ImportMem> {
Header(MemoryRenderBufferRenderElement<R>), Header(IcedRenderElement<R>),
Shadow(PixelShaderElement), Shadow(PixelShaderElement),
Border(PixelShaderElement), Border(PixelShaderElement),
Window(WaylandSurfaceRenderElement<R>), Window(SurfaceRenderElement<R>),
Clipped(ClippedSurfaceRenderElement<R>),
} }
impl<R: Renderer + ImportAll + ImportMem> From<MemoryRenderBufferRenderElement<R>> impl<R: AsGlowRenderer + ImportAll + ImportMem> From<IcedRenderElement<R>>
for CosmicWindowRenderElement<R> for CosmicWindowRenderElement<R>
{ {
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self { fn from(value: IcedRenderElement<R>) -> Self {
Self::Header(value) Self::Header(value)
} }
} }
impl<R: Renderer + ImportAll + ImportMem> From<WaylandSurfaceRenderElement<R>> impl<R: AsGlowRenderer + ImportAll + ImportMem> From<SurfaceRenderElement<R>>
for CosmicWindowRenderElement<R> for CosmicWindowRenderElement<R>
{ {
fn from(value: WaylandSurfaceRenderElement<R>) -> Self { fn from(value: SurfaceRenderElement<R>) -> Self {
Self::Window(value) Self::Window(value)
} }
} }
impl<R: Renderer + ImportAll + ImportMem> From<ClippedSurfaceRenderElement<R>>
for CosmicWindowRenderElement<R>
{
fn from(value: ClippedSurfaceRenderElement<R>) -> Self {
Self::Clipped(value)
}
}
impl<R> Element for CosmicWindowRenderElement<R> impl<R> Element for CosmicWindowRenderElement<R>
where where
R: Renderer + ImportAll + ImportMem, R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
R::TextureId: Send + 'static,
{ {
fn id(&self) -> &RendererId { fn id(&self) -> &RendererId {
match self { match self {
@ -1285,7 +1282,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.id(), CosmicWindowRenderElement::Shadow(elem) => elem.id(),
CosmicWindowRenderElement::Border(elem) => elem.id(), CosmicWindowRenderElement::Border(elem) => elem.id(),
CosmicWindowRenderElement::Window(elem) => elem.id(), CosmicWindowRenderElement::Window(elem) => elem.id(),
CosmicWindowRenderElement::Clipped(elem) => elem.id(),
} }
} }
@ -1295,7 +1291,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.current_commit(), CosmicWindowRenderElement::Shadow(elem) => elem.current_commit(),
CosmicWindowRenderElement::Border(elem) => elem.current_commit(), CosmicWindowRenderElement::Border(elem) => elem.current_commit(),
CosmicWindowRenderElement::Window(elem) => elem.current_commit(), CosmicWindowRenderElement::Window(elem) => elem.current_commit(),
CosmicWindowRenderElement::Clipped(elem) => elem.current_commit(),
} }
} }
@ -1305,7 +1300,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.src(), CosmicWindowRenderElement::Shadow(elem) => elem.src(),
CosmicWindowRenderElement::Border(elem) => elem.src(), CosmicWindowRenderElement::Border(elem) => elem.src(),
CosmicWindowRenderElement::Window(elem) => elem.src(), CosmicWindowRenderElement::Window(elem) => elem.src(),
CosmicWindowRenderElement::Clipped(elem) => elem.src(),
} }
} }
@ -1315,7 +1309,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.geometry(scale), CosmicWindowRenderElement::Shadow(elem) => elem.geometry(scale),
CosmicWindowRenderElement::Border(elem) => elem.geometry(scale), CosmicWindowRenderElement::Border(elem) => elem.geometry(scale),
CosmicWindowRenderElement::Window(elem) => elem.geometry(scale), CosmicWindowRenderElement::Window(elem) => elem.geometry(scale),
CosmicWindowRenderElement::Clipped(elem) => elem.geometry(scale),
} }
} }
@ -1325,7 +1318,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.location(scale), CosmicWindowRenderElement::Shadow(elem) => elem.location(scale),
CosmicWindowRenderElement::Border(elem) => elem.location(scale), CosmicWindowRenderElement::Border(elem) => elem.location(scale),
CosmicWindowRenderElement::Window(elem) => elem.location(scale), CosmicWindowRenderElement::Window(elem) => elem.location(scale),
CosmicWindowRenderElement::Clipped(elem) => elem.location(scale),
} }
} }
@ -1335,7 +1327,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.transform(), CosmicWindowRenderElement::Shadow(elem) => elem.transform(),
CosmicWindowRenderElement::Border(elem) => elem.transform(), CosmicWindowRenderElement::Border(elem) => elem.transform(),
CosmicWindowRenderElement::Window(elem) => elem.transform(), CosmicWindowRenderElement::Window(elem) => elem.transform(),
CosmicWindowRenderElement::Clipped(elem) => elem.transform(),
} }
} }
@ -1349,7 +1340,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Shadow(elem) => elem.damage_since(scale, commit),
CosmicWindowRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Border(elem) => elem.damage_since(scale, commit),
CosmicWindowRenderElement::Window(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Window(elem) => elem.damage_since(scale, commit),
CosmicWindowRenderElement::Clipped(elem) => elem.damage_since(scale, commit),
} }
} }
@ -1359,7 +1349,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Shadow(elem) => elem.opaque_regions(scale),
CosmicWindowRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Border(elem) => elem.opaque_regions(scale),
CosmicWindowRenderElement::Window(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Window(elem) => elem.opaque_regions(scale),
CosmicWindowRenderElement::Clipped(elem) => elem.opaque_regions(scale),
} }
} }
@ -1369,7 +1358,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.alpha(), CosmicWindowRenderElement::Shadow(elem) => elem.alpha(),
CosmicWindowRenderElement::Border(elem) => elem.alpha(), CosmicWindowRenderElement::Border(elem) => elem.alpha(),
CosmicWindowRenderElement::Window(elem) => elem.alpha(), CosmicWindowRenderElement::Window(elem) => elem.alpha(),
CosmicWindowRenderElement::Clipped(elem) => elem.alpha(),
} }
} }
@ -1379,7 +1367,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.kind(), CosmicWindowRenderElement::Shadow(elem) => elem.kind(),
CosmicWindowRenderElement::Border(elem) => elem.kind(), CosmicWindowRenderElement::Border(elem) => elem.kind(),
CosmicWindowRenderElement::Window(elem) => elem.kind(), CosmicWindowRenderElement::Window(elem) => elem.kind(),
CosmicWindowRenderElement::Clipped(elem) => elem.kind(),
} }
} }
@ -1389,7 +1376,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Shadow(elem) => elem.is_framebuffer_effect(),
CosmicWindowRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Border(elem) => elem.is_framebuffer_effect(),
CosmicWindowRenderElement::Window(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Window(elem) => elem.is_framebuffer_effect(),
CosmicWindowRenderElement::Clipped(elem) => elem.is_framebuffer_effect(),
} }
} }
} }
@ -1397,8 +1383,7 @@ where
impl<R> RenderElement<R> for CosmicWindowRenderElement<R> impl<R> RenderElement<R> for CosmicWindowRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
R::Error: FromGlesError,
{ {
fn draw( fn draw(
&self, &self,
@ -1423,14 +1408,11 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicWindowRenderElement::Window(elem) => { CosmicWindowRenderElement::Window(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache) elem.draw(frame, src, dst, damage, opaque_regions, cache)
} }
CosmicWindowRenderElement::Clipped(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache)
}
} }
} }
@ -1441,7 +1423,6 @@ where
elem.underlying_storage(renderer.glow_renderer_mut()) elem.underlying_storage(renderer.glow_renderer_mut())
} }
CosmicWindowRenderElement::Window(elem) => elem.underlying_storage(renderer), CosmicWindowRenderElement::Window(elem) => elem.underlying_storage(renderer),
CosmicWindowRenderElement::Clipped(elem) => elem.underlying_storage(renderer),
} }
} }
@ -1464,14 +1445,11 @@ where
dst, dst,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
CosmicWindowRenderElement::Window(elem) => { CosmicWindowRenderElement::Window(elem) => {
elem.capture_framebuffer(frame, src, dst, cache) elem.capture_framebuffer(frame, src, dst, cache)
} }
CosmicWindowRenderElement::Clipped(elem) => {
elem.capture_framebuffer(frame, src, dst, cache)
}
} }
} }
} }

View file

@ -378,6 +378,14 @@ fn update_focus_state(
.cloned() .cloned()
.unwrap_or(seat.active_output()); .unwrap_or(seat.active_output());
crate::input::update_output_image_copy_cursor_position(
&shell,
&state.common.clock,
&output,
seat,
new_pos,
);
let focus = State::surface_under(new_pos, &output, &shell) let focus = State::surface_under(new_pos, &output, &shell)
.map(|(focus, loc)| (focus, loc.as_logical())); .map(|(focus, loc)| (focus, loc.as_logical()));
//drop here to avoid multiple borrows //drop here to avoid multiple borrows

View file

@ -32,10 +32,12 @@ pub enum Stage<'a> {
layer: LayerSurface, layer: LayerSurface,
popup: &'a PopupKind, popup: &'a PopupKind,
location: Point<i32, Global>, location: Point<i32, Global>,
workspace_idx: usize,
}, },
LayerSurface { LayerSurface {
layer: LayerSurface, layer: LayerSurface,
location: Point<i32, Global>, location: Point<i32, Global>,
workspace_idx: usize,
}, },
OverrideRedirect { OverrideRedirect {
surface: &'a X11Surface, surface: &'a X11Surface,
@ -95,10 +97,15 @@ fn render_input_order_internal<R: 'static>(
layer, layer,
popup: &popup, popup: &popup,
location, location,
workspace_idx: current.1,
})?; })?;
} }
for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) { for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) {
callback(Stage::LayerSurface { layer, location })?; callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
} }
// calculate a bunch of stuff for workspace transitions // calculate a bunch of stuff for workspace transitions
@ -187,7 +194,7 @@ fn render_input_order_internal<R: 'static>(
}); });
( (
Some((previous, has_fullscreen, offset)), Some((previous, previous_idx, has_fullscreen, offset)),
Point::<i32, Logical>::from(match (layout, forward) { Point::<i32, Logical>::from(match (layout, forward) {
(WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y),
(WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)),
@ -206,6 +213,7 @@ fn render_input_order_internal<R: 'static>(
layer, layer,
popup: &popup, popup: &popup,
location, location,
workspace_idx: current.1,
})?; })?;
} }
} }
@ -236,7 +244,7 @@ fn render_input_order_internal<R: 'static>(
if element_filter != ElementFilter::LayerShellOnly { if element_filter != ElementFilter::LayerShellOnly {
// previous workspace popups // previous workspace popups
if let Some((previous_handle, _, offset)) = previous.as_ref() { if let Some((previous_handle, _, _, offset)) = previous.as_ref() {
let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else {
return ControlFlow::Break(Err(OutputNoMode)); return ControlFlow::Break(Err(OutputNoMode));
}; };
@ -265,11 +273,12 @@ fn render_input_order_internal<R: 'static>(
layer, layer,
popup: &popup, popup: &popup,
location, location,
workspace_idx: current.1,
})?; })?;
} }
} }
if let Some((_, has_fullscreen, offset)) = previous.as_ref() if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref()
&& !has_fullscreen && !has_fullscreen
{ {
// previous bottom layer popups // previous bottom layer popups
@ -278,6 +287,7 @@ fn render_input_order_internal<R: 'static>(
layer, layer,
popup: &popup, popup: &popup,
location: location + offset.as_global(), location: location + offset.as_global(),
workspace_idx: **idx,
})?; })?;
} }
} }
@ -289,11 +299,12 @@ fn render_input_order_internal<R: 'static>(
layer, layer,
popup: &popup, popup: &popup,
location, location,
workspace_idx: current.1,
})?; })?;
} }
} }
if let Some((_, has_fullscreen, offset)) = previous.as_ref() if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref()
&& !has_fullscreen && !has_fullscreen
{ {
// previous background layer popups // previous background layer popups
@ -302,6 +313,7 @@ fn render_input_order_internal<R: 'static>(
layer, layer,
popup: &popup, popup: &popup,
location: location + offset.as_global(), location: location + offset.as_global(),
workspace_idx: **idx,
})?; })?;
} }
} }
@ -309,7 +321,11 @@ fn render_input_order_internal<R: 'static>(
if !has_focused_fullscreen { if !has_focused_fullscreen {
// top-layer shell // top-layer shell
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) { for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
callback(Stage::LayerSurface { layer, location })?; callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
} }
} }
@ -326,7 +342,7 @@ fn render_input_order_internal<R: 'static>(
})?; })?;
// previous workspace windows // previous workspace windows
if let Some((previous_handle, _, offset)) = previous.as_ref() { if let Some((previous_handle, _, _, offset)) = previous.as_ref() {
let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else {
return ControlFlow::Break(Err(OutputNoMode)); return ControlFlow::Break(Err(OutputNoMode));
}; };
@ -341,17 +357,25 @@ fn render_input_order_internal<R: 'static>(
// bottom layer // bottom layer
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
location += current_offset.as_global(); location += current_offset.as_global();
callback(Stage::LayerSurface { layer, location })?; callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
} }
} }
if let Some((_, has_fullscreen, offset)) = previous.as_ref() if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref()
&& !has_fullscreen && !has_fullscreen
{ {
// previous bottom layer // previous bottom layer
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
location += offset.as_global(); location += offset.as_global();
callback(Stage::LayerSurface { layer, location })?; callback(Stage::LayerSurface {
layer,
location,
workspace_idx: **idx,
})?;
} }
} }
@ -359,17 +383,25 @@ fn render_input_order_internal<R: 'static>(
// background layer // background layer
for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) {
location += current_offset.as_global(); location += current_offset.as_global();
callback(Stage::LayerSurface { layer, location })?; callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
} }
} }
if let Some((_, has_fullscreen, offset)) = previous.as_ref() if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref()
&& !has_fullscreen && !has_fullscreen
{ {
// previous background layer // previous background layer
for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) {
location += offset.as_global(); location += offset.as_global();
callback(Stage::LayerSurface { layer, location })?; callback(Stage::LayerSurface {
layer,
location,
workspace_idx: **idx,
})?;
} }
} }

View file

@ -6,7 +6,7 @@ use std::{
use crate::{ use crate::{
shell::{ shell::{
CosmicSurface, SeatExt, CosmicSurface, CursorGeometry, SeatExt,
element::{CosmicMapped, CosmicStack, CosmicWindow}, element::{CosmicMapped, CosmicStack, CosmicWindow},
layout::tiling::ResizeForkTarget, layout::tiling::ResizeForkTarget,
zoom::ZoomFocusTarget, zoom::ZoomFocusTarget,
@ -272,7 +272,7 @@ impl PointerFocusTarget {
None None
}; };
let cursor_hotspot = if let Some((_, hotspot)) = let cursor_hotspot = if let Some(CursorGeometry { hotspot, .. }) =
seat.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into()) seat.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into())
{ {
hotspot hotspot

View file

@ -1,10 +1,12 @@
use cosmic::{ use cosmic::{
iced::Element, iced::{
iced_core::{ Alignment, Element,
Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle, core::{
Renderer as IcedRenderer, Shell, Size, layout, mouse, overlay, Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle,
renderer::{Quad, Style}, Renderer as IcedRenderer, Shell, Size, layout, mouse, overlay,
widget::{Id, Tree, Widget, tree}, renderer::{Quad, Style},
widget::{Id, Tree, Widget, tree},
},
}, },
widget::button::Catalog, widget::button::Catalog,
}; };
@ -73,11 +75,26 @@ where
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let widget_state = state.state.downcast_ref::<State>(); let widget_state = state.state.downcast_ref::<State>();
let styling = if widget_state.cursor_over { let mut styling = if widget_state.cursor_over {
theme.hovered(true, false, &self.styling) theme.hovered(true, false, &self.styling)
} else { } else {
theme.active(true, false, &self.styling) theme.active(true, false, &self.styling)
}; };
if matches!(self.styling, cosmic::theme::Button::MenuItem) {
match theme.list_item_position {
Some((Alignment::Start, _)) => {
styling.border_radius =
styling.border_radius.bottom(theme.cosmic().radius_0()[3]);
}
Some((Alignment::End, _)) => {
styling.border_radius = styling.border_radius.top(theme.cosmic().radius_0()[0]);
}
Some((Alignment::Center, _)) => {}
None => {
styling.border_radius = theme.cosmic().radius_0().into();
}
};
}
renderer.fill_quad( renderer.fill_quad(
Quad { Quad {
@ -85,7 +102,7 @@ where
bounds: layout.bounds(), bounds: layout.bounds(),
border: Border { border: Border {
radius: styling.border_radius, radius: styling.border_radius,
width: styling.border_width, width: 0.,
color: styling.border_color, color: styling.border_color,
}, },
shadow: Default::default(), shadow: Default::default(),

View file

@ -9,19 +9,18 @@ use std::{
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic::{ use cosmic::{
Apply as _, Task, Apply as _, Task,
iced::{Alignment, Background}, iced::{
iced_core::{Border, Length, Rectangle as IcedRectangle, alignment::Horizontal}, Alignment, Background,
iced_widget::{self, Column, Row, text::Style as TextStyle}, core::{Border, Length, Rectangle as IcedRectangle, alignment::Horizontal},
widget::{self as iced_widget, Row, text::Style as TextStyle},
},
theme, theme,
widget::{button, divider, icon::from_name, space, text}, widget::{button, divider, icon::from_name, menu::menu_column::MenuColumn, space, text},
}; };
use smithay::{ use smithay::{
backend::{ backend::{
input::{ButtonState, TouchSlot}, input::{ButtonState, TouchSlot},
renderer::{ renderer::ImportMem,
ImportMem, Renderer,
element::{AsRenderElements, memory::MemoryRenderBufferRenderElement},
},
}, },
desktop::space::SpaceElement, desktop::space::SpaceElement,
input::{ input::{
@ -43,10 +42,11 @@ use smithay::{
}; };
use crate::{ use crate::{
backend::render::element::AsGlowRenderer,
shell::{SeatExt, focus::target::PointerFocusTarget}, shell::{SeatExt, focus::target::PointerFocusTarget},
state::State, state::State,
utils::{ utils::{
iced::{IcedElement, Program}, iced::{IcedElement, IcedRenderElement, Program},
prelude::*, prelude::*,
}, },
}; };
@ -65,29 +65,32 @@ pub struct MenuGrabState {
pub type SeatMenuGrabState = Mutex<Option<MenuGrabState>>; pub type SeatMenuGrabState = Mutex<Option<MenuGrabState>>;
impl MenuGrabState { impl MenuGrabState {
pub fn render<I, R>(&self, renderer: &mut R, output: &Output) -> Vec<I> pub fn render<R>(
where &self,
R: Renderer + ImportMem, renderer: &mut R,
output: &Output,
push: &mut dyn FnMut(IcedRenderElement<R>),
) where
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
I: From<MemoryRenderBufferRenderElement<R>>,
{ {
let scale = output.current_scale().fractional_scale(); let scale = output.current_scale().fractional_scale();
self.elements for elem in self.elements.lock().unwrap().iter() {
.lock() elem.iced.push_render_elements(
.unwrap() renderer,
.iter() elem.position
.flat_map(|elem| { .to_local(output)
elem.iced.render_elements( .as_logical()
renderer, .to_physical_precise_round(scale),
elem.position scale.into(),
.to_local(output) 1.0,
.as_logical() elem.iced
.to_physical_precise_round(scale), .with_theme(|theme| theme.cosmic().radius_s())
scale.into(), .map(|x| x.round() as u8),
1.0, push,
) None,
}) )
.collect() }
} }
pub fn is_in_screen_space(&self) -> bool { pub fn is_in_screen_space(&self) -> bool {
@ -276,11 +279,13 @@ impl Program for ContextMenu {
let mut elements = grab_state.elements.lock().unwrap(); let mut elements = grab_state.elements.lock().unwrap();
let position = elements.last().unwrap().position; let position = elements.last().unwrap().position;
let mut theme = state.common.theme.clone();
theme.transparent = theme.cosmic().frosted_system_interface;
let element = IcedElement::new( let element = IcedElement::new(
ContextMenu::new(items), ContextMenu::new(items),
Size::default(), Size::default(),
state.common.event_loop_handle.clone(), state.common.event_loop_handle.clone(),
state.common.theme.clone(), theme,
); );
let min_size = element.minimum_size(); let min_size = element.minimum_size();
@ -295,7 +300,7 @@ impl Program for ContextMenu {
Rectangle::new( Rectangle::new(
position position
+ Point::from(( + Point::from((
bounds.width.ceil() as i32, bounds.width.floor() as i32,
bounds.y.ceil() as i32, bounds.y.ceil() as i32,
)), )),
min_size.as_global(), min_size.as_global(),
@ -304,7 +309,7 @@ impl Program for ContextMenu {
Rectangle::new( Rectangle::new(
position position
+ Point::from(( + Point::from((
bounds.width.ceil() as i32, bounds.width.floor() as i32,
bounds.y.ceil() as i32 + bounds.height.ceil() as i32 bounds.y.ceil() as i32 + bounds.height.ceil() as i32
- min_size.h, - min_size.h,
)), )),
@ -312,14 +317,15 @@ impl Program for ContextMenu {
), ),
// to the left -> down // to the left -> down
Rectangle::new( Rectangle::new(
position + Point::from((-min_size.w, bounds.y.ceil() as i32)), position
+ Point::from((-min_size.w + 1, bounds.y.ceil() as i32)),
min_size.as_global(), min_size.as_global(),
), ),
// to the left -> up // to the left -> up
Rectangle::new( Rectangle::new(
position position
+ Point::from(( + Point::from((
-min_size.w, -min_size.w + 1,
bounds.y.ceil() as i32 + bounds.height.ceil() as i32 bounds.y.ceil() as i32 + bounds.height.ceil() as i32
- min_size.h, - min_size.h,
)), )),
@ -385,9 +391,11 @@ impl Program for ContextMenu {
_ => Length::Fill, _ => Length::Fill,
}; };
Column::with_children(self.items.iter().enumerate().map(|(idx, item)| { MenuColumn::with_children(self.items.iter().enumerate().map(|(idx, item)| {
match item { match item {
Item::Separator => divider::horizontal::light().into(), Item::Separator => divider::horizontal::light()
.class(theme::Rule::Default)
.into(),
Item::Submenu { title, .. } => Row::with_children(vec![ Item::Submenu { title, .. } => Row::with_children(vec![
space::horizontal().width(16).into(), space::horizontal().width(16).into(),
text::body(title).width(mode).into(), text::body(title).width(mode).into(),
@ -477,6 +485,7 @@ impl Program for ContextMenu {
.class(theme::Container::custom(|theme| { .class(theme::Container::custom(|theme| {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let component = &cosmic.background(false).component; let component = &cosmic.background(false).component;
let component = &cosmic.background(false).component;
iced_widget::container::Style { iced_widget::container::Style {
snap: true, snap: true,
icon_color: Some(cosmic.accent.base.into()), icon_color: Some(cosmic.accent.base.into()),
@ -540,7 +549,7 @@ impl PointerGrab<State> for MenuGrab {
let mut bbox = elem.iced.bbox(); let mut bbox = elem.iced.bbox();
bbox.loc = elem.position.as_logical(); bbox.loc = elem.position.as_logical();
bbox.contains(event_location.to_i32_round()) bbox.contains(event_location.to_i32_floor())
}) { }) {
let element = &mut elements[i]; let element = &mut elements[i];
@ -556,8 +565,11 @@ impl PointerGrab<State> for MenuGrab {
PointerTarget::motion(&element.iced, &self.seat, state, &new_event); PointerTarget::motion(&element.iced, &self.seat, state, &new_event);
} }
} else { } else {
elements.iter_mut().for_each(|element| { elements
if element.pointer_entered { .iter_mut()
.filter(|element| element.pointer_entered)
.skip(1)
.for_each(|element| {
PointerTarget::leave( PointerTarget::leave(
&element.iced, &element.iced,
&self.seat, &self.seat,
@ -566,8 +578,7 @@ impl PointerGrab<State> for MenuGrab {
event.time, event.time,
); );
element.pointer_entered = false; element.pointer_entered = false;
} })
})
} }
} }
handle.motion(state, None, event); handle.motion(state, None, event);
@ -743,7 +754,7 @@ impl TouchGrab<State> for MenuGrab {
let mut bbox = elem.iced.bbox(); let mut bbox = elem.iced.bbox();
bbox.loc = elem.position.as_logical(); bbox.loc = elem.position.as_logical();
bbox.contains(event_location.to_i32_round()) bbox.contains(event_location.to_i32_floor())
}) { }) {
let element = &mut elements[i]; let element = &mut elements[i];

View file

@ -6,10 +6,7 @@ use crate::{
}, },
shell::{ shell::{
CosmicMapped, CosmicSurface, Direction, ManagedLayer, CosmicMapped, CosmicSurface, Direction, ManagedLayer,
element::{ element::{CosmicMappedRenderElement, stack_hover::StackHover},
CosmicMappedRenderElement,
stack_hover::{StackHover, stack_hover},
},
focus::target::{KeyboardFocusTarget, PointerFocusTarget}, focus::target::{KeyboardFocusTarget, PointerFocusTarget},
layout::floating::TiledCorners, layout::floating::TiledCorners,
}, },
@ -19,12 +16,14 @@ use crate::{
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic::theme::CosmicTheme; use cosmic::theme::CosmicTheme;
use smallvec::SmallVec;
use smithay::{ use smithay::{
backend::{ backend::{
drm::DrmNode,
input::ButtonState, input::ButtonState,
renderer::{ renderer::{
ImportAll, ImportMem, Renderer, ImportAll, ImportMem,
element::{AsRenderElements, RenderElement, utils::RescaleRenderElement}, element::{RenderElement, utils::RescaleRenderElement},
}, },
}, },
desktop::{WindowSurfaceType, layer_map_for_output, space::SpaceElement}, desktop::{WindowSurfaceType, layer_map_for_output, space::SpaceElement},
@ -68,12 +67,17 @@ pub struct MoveGrabState {
impl MoveGrabState { impl MoveGrabState {
#[profiling::function] #[profiling::function]
pub fn render<I, R>(&self, renderer: &mut R, output: &Output, theme: &CosmicTheme) -> Vec<I> pub fn render<R>(
where &self,
R: Renderer + ImportAll + ImportMem + AsGlowRenderer, renderer: &mut R,
output: &Output,
theme: &CosmicTheme,
scanout_node: Option<DrmNode>,
push: &mut dyn FnMut(CosmicMappedRenderElement<R>),
) where
R: AsGlowRenderer + ImportAll + ImportMem,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
I: From<CosmicMappedRenderElement<R>>,
{ {
let scale = if self.previous == ManagedLayer::Tiling { let scale = if self.previous == ManagedLayer::Tiling {
0.6 + ((1.0 0.6 + ((1.0
@ -98,7 +102,7 @@ impl MoveGrabState {
.intersection(window_geo) .intersection(window_geo)
.is_none() .is_none()
{ {
return Vec::new(); return;
} }
let output_scale: Scale<f64> = output.current_scale().fractional_scale().into(); let output_scale: Scale<f64> = output.current_scale().fractional_scale().into();
@ -108,13 +112,33 @@ impl MoveGrabState {
+ self.window_offset + self.window_offset
- scaling_offset; - scaling_offset;
for (indicator, location) in self.stacking_indicator.iter() {
indicator.push_render_elements(
renderer,
location.to_physical_precise_round(output_scale),
output_scale,
1.0,
&mut |elem| push(elem.into()),
None,
);
}
self.window.push_popup_render_elements::<R>(
renderer,
(render_location - self.window.geometry().loc).to_physical_precise_round(output_scale),
output_scale,
alpha,
scanout_node,
push,
);
let active_window_hint = crate::theme::active_window_hint(theme); let active_window_hint = crate::theme::active_window_hint(theme);
let radius = self let radius = self
.element() .element()
.corner_radius(window_geo.size, self.indicator_thickness); .corner_radius(window_geo.size, self.indicator_thickness);
let focus_element = if self.indicator_thickness > 0 { if self.indicator_thickness > 0 {
Some(CosmicMappedRenderElement::from( push(
IndicatorShader::focus_element( IndicatorShader::focus_element(
renderer, renderer,
Key::Window(Usage::MoveGrabIndicator, self.window.key()), Key::Window(Usage::MoveGrabIndicator, self.window.key()),
@ -137,12 +161,57 @@ impl MoveGrabState {
active_window_hint.green, active_window_hint.green,
active_window_hint.blue, active_window_hint.blue,
], ],
), )
)) .into(),
} else { )
None }
let map_window_element = |elem| match elem {
CosmicMappedRenderElement::Stack(stack) => {
CosmicMappedRenderElement::GrabbedStack(RescaleRenderElement::from_element(
stack,
render_location
.to_physical_precise_round(output.current_scale().fractional_scale()),
scale,
))
}
CosmicMappedRenderElement::Window(window) => {
CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element(
window,
render_location
.to_physical_precise_round(output.current_scale().fractional_scale()),
scale,
))
}
x => x,
}; };
let mut lower_elements = SmallVec::<[CosmicMappedRenderElement<R>; 4]>::new_const();
self.window.push_render_elements(
renderer,
(render_location - self.window.geometry().loc).to_physical_precise_round(output_scale),
None,
output_scale,
alpha,
Some(false),
scanout_node,
&mut |elem| push(map_window_element(elem)),
&mut |elem| lower_elements.push(map_window_element(elem)),
);
if let Some(shadow_element) = self.window.shadow_render_element(
renderer,
(render_location - self.window.geometry().loc).to_physical_precise_round(output_scale),
None,
output_scale,
scale,
alpha,
) {
push(shadow_element);
}
for elem in lower_elements.into_iter() {
push(elem);
}
let non_exclusive_geometry = { let non_exclusive_geometry = {
let layers = layer_map_for_output(output); let layers = layer_map_for_output(output);
layers.non_exclusive_zone() layers.non_exclusive_zone()
@ -151,117 +220,46 @@ impl MoveGrabState {
let gaps = (theme.gaps.0 as i32, theme.gaps.1 as i32); let gaps = (theme.gaps.0 as i32, theme.gaps.1 as i32);
let thickness = self.indicator_thickness.max(1); let thickness = self.indicator_thickness.max(1);
let snapping_indicator = match &self.snapping_zone { if let Some(t) = &self.snapping_zone
Some(t) if &self.cursor_output == output => { && &self.cursor_output == output
let base_color = theme.palette.neutral_9; {
let overlay_geometry = t.overlay_geometry(non_exclusive_geometry, gaps); let base_color = theme.palette.neutral_9;
vec![ let overlay_geometry = t.overlay_geometry(non_exclusive_geometry, gaps);
CosmicMappedRenderElement::from(IndicatorShader::element(
renderer,
Key::Window(Usage::SnappingIndicator, self.window.key()),
overlay_geometry,
thickness,
[
theme.radius_s()[0] as u8,
theme.radius_s()[1] as u8,
theme.radius_s()[2] as u8,
theme.radius_s()[3] as u8,
],
1.0,
output_scale.x,
[
active_window_hint.red,
active_window_hint.green,
active_window_hint.blue,
],
)),
CosmicMappedRenderElement::from(BackdropShader::element(
renderer,
Key::Window(Usage::SnappingIndicator, self.window.key()),
t.overlay_geometry(non_exclusive_geometry, gaps),
theme.radius_s()[0], // TODO: Fix once shaders support 4 corner radii customization
0.4,
[base_color.red, base_color.green, base_color.blue],
)),
]
}
_ => vec![],
};
let w_elements = self push(
.window IndicatorShader::element(
.render_elements::<R, CosmicMappedRenderElement<R>>(
renderer,
(render_location - self.window.geometry().loc)
.to_physical_precise_round(output_scale),
None,
output_scale,
alpha,
Some(false),
);
let p_elements = self
.window
.popup_render_elements::<R, CosmicMappedRenderElement<R>>(
renderer,
(render_location - self.window.geometry().loc)
.to_physical_precise_round(output_scale),
output_scale,
alpha,
);
let shadow_element = self.window.shadow_render_element(
renderer,
(render_location - self.window.geometry().loc).to_physical_precise_round(output_scale),
None,
output_scale,
scale,
alpha,
);
self.stacking_indicator
.iter()
.flat_map(|(indicator, location)| {
indicator.render_elements(
renderer, renderer,
location.to_physical_precise_round(output_scale), Key::Window(Usage::SnappingIndicator, self.window.key()),
output_scale, overlay_geometry,
thickness,
[
theme.radius_s()[0] as u8,
theme.radius_s()[1] as u8,
theme.radius_s()[2] as u8,
theme.radius_s()[3] as u8,
],
1.0, 1.0,
output_scale.x,
[
active_window_hint.red,
active_window_hint.green,
active_window_hint.blue,
],
) )
}) .into(),
.chain(p_elements) );
.chain(focus_element) push(
.chain( BackdropShader::element(
w_elements renderer,
.into_iter() Key::Window(Usage::SnappingIndicator, self.window.key()),
.chain(shadow_element) t.overlay_geometry(non_exclusive_geometry, gaps),
.map(|elem| match elem { theme.radius_s()[0], // TODO: Fix once shaders support 4 corner radii customization
CosmicMappedRenderElement::Stack(stack) => { 0.4,
CosmicMappedRenderElement::GrabbedStack( [base_color.red, base_color.green, base_color.blue],
RescaleRenderElement::from_element( )
stack, .into(),
render_location.to_physical_precise_round(
output.current_scale().fractional_scale(),
),
scale,
),
)
}
CosmicMappedRenderElement::Window(window) => {
CosmicMappedRenderElement::GrabbedWindow(
RescaleRenderElement::from_element(
window,
render_location.to_physical_precise_round(
output.current_scale().fractional_scale(),
),
scale,
),
)
}
x => x,
}),
) )
.chain(snapping_indicator) }
.map(I::from)
.collect()
} }
pub fn element(&self) -> CosmicMapped { pub fn element(&self) -> CosmicMapped {
@ -441,7 +439,7 @@ impl MoveGrab {
if let Some(indicator) = if let Some(indicator) =
grab_state.stacking_indicator.as_ref().map(|x| &x.0) grab_state.stacking_indicator.as_ref().map(|x| &x.0)
{ {
indicator.output_enter(output, overlap); indicator.output_enter(output);
} }
} }
} else if self.window_outputs.remove(output) { } else if self.window_outputs.remove(output) {
@ -455,16 +453,14 @@ impl MoveGrab {
let indicator_location = shell.stacking_indicator(&current_output, self.previous); let indicator_location = shell.stacking_indicator(&current_output, self.previous);
if indicator_location.is_some() != grab_state.stacking_indicator.is_some() { if indicator_location.is_some() != grab_state.stacking_indicator.is_some() {
grab_state.stacking_indicator = indicator_location.map(|geo| { grab_state.stacking_indicator = indicator_location.map(|geo| {
let element = stack_hover( let size = geo.size.as_logical();
let element = StackHover::new(
state.common.event_loop_handle.clone(), state.common.event_loop_handle.clone(),
geo.size.as_logical(), size,
state.common.theme.clone(), state.common.theme.clone(),
); );
for output in &self.window_outputs { for output in &self.window_outputs {
element.output_enter( element.output_enter(output);
output,
Rectangle::from_size(output.geometry().size.as_logical()),
);
} }
(element, geo.loc.as_logical()) (element, geo.loc.as_logical())
}); });

View file

@ -9,10 +9,14 @@ use std::{
use cosmic_comp_config::AppearanceConfig; use cosmic_comp_config::AppearanceConfig;
use cosmic_settings_config::shortcuts::action::ResizeDirection; use cosmic_settings_config::shortcuts::action::ResizeDirection;
use keyframe::{ease, functions::EaseInOutCubic}; use keyframe::{ease, functions::EaseInOutCubic};
use smallvec::SmallVec;
use smithay::{ use smithay::{
backend::renderer::element::{ backend::{
AsRenderElements, RenderElement, drm::DrmNode,
utils::{Relocate, RelocateRenderElement, RescaleRenderElement}, renderer::element::{
RenderElement,
utils::{Relocate, RelocateRenderElement, RescaleRenderElement},
},
}, },
desktop::{PopupKind, Space, WindowSurfaceType, layer_map_for_output, space::SpaceElement}, desktop::{PopupKind, Space, WindowSurfaceType, layer_map_for_output, space::SpaceElement},
input::Seat, input::Seat,
@ -1346,7 +1350,7 @@ impl FloatingLayout {
mapped.set_bounds(geometry.size.as_logical()); mapped.set_bounds(geometry.size.as_logical());
let prev = self.space.element_geometry(&mapped).map(RectExt::as_local); let prev = self.space.element_geometry(&mapped).map(RectExt::as_local);
let window_geometry = if mapped.is_maximized(false) { let window_geometry = if mapped.is_maximized(true) {
geometry geometry
} else { } else {
prev.map(|mut rect| { prev.map(|mut rect| {
@ -1439,8 +1443,9 @@ impl FloatingLayout {
&self, &self,
renderer: &mut R, renderer: &mut R,
alpha: f32, alpha: f32,
) -> Vec<CosmicMappedRenderElement<R>> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(CosmicMappedRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
@ -1450,8 +1455,6 @@ impl FloatingLayout {
let output = self.space.outputs().next().unwrap(); let output = self.space.outputs().next().unwrap();
let output_scale = output.current_scale().fractional_scale(); let output_scale = output.current_scale().fractional_scale();
let mut elements = Vec::default();
for elem in self for elem in self
.animations .animations
.iter() .iter()
@ -1466,19 +1469,17 @@ impl FloatingLayout {
.unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha));
let render_location = geometry.loc - elem.geometry().loc.as_local(); let render_location = geometry.loc - elem.geometry().loc.as_local();
elements.extend( elem.push_popup_render_elements(
elem.popup_render_elements( renderer,
renderer, render_location
render_location .as_logical()
.as_logical() .to_physical_precise_round(output_scale),
.to_physical_precise_round(output_scale), output_scale.into(),
output_scale.into(), alpha,
alpha, scanout_node,
), push,
); );
} }
elements
} }
#[profiling::function] #[profiling::function]
@ -1490,8 +1491,9 @@ impl FloatingLayout {
indicator_thickness: u8, indicator_thickness: u8,
alpha: f32, alpha: f32,
theme: &cosmic::theme::CosmicTheme, theme: &cosmic::theme::CosmicTheme,
) -> Vec<CosmicMappedRenderElement<R>> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(CosmicMappedRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
@ -1504,8 +1506,7 @@ impl FloatingLayout {
layers.non_exclusive_zone() layers.non_exclusive_zone()
}; };
let output_scale = output.current_scale().fractional_scale(); let output_scale = output.current_scale().fractional_scale();
let mut lower_elements = SmallVec::<[_; 4]>::new_const();
let mut elements = Vec::default();
for elem in self for elem in self
.animations .animations
@ -1519,32 +1520,9 @@ impl FloatingLayout {
.get(elem) .get(elem)
.map(|anim| (*anim.previous_geometry(), alpha * anim.alpha())) .map(|anim| (*anim.previous_geometry(), alpha * anim.alpha()))
.unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha));
let render_location = geometry.loc - elem.geometry().loc.as_local(); let render_location = geometry.loc - elem.geometry().loc.as_local();
let mut window_elements = elem.render_elements(
renderer,
render_location
.as_logical()
.to_physical_precise_round(output_scale),
None,
output_scale.into(),
alpha,
None,
);
window_elements.extend(
elem.shadow_render_element(
renderer,
render_location
.as_logical()
.to_physical_precise_round(output_scale),
None,
output_scale.into(),
1.,
alpha,
),
);
if let Some(anim) = self.animations.get(elem) { let maybe_map = if let Some(anim) = self.animations.get(elem) {
let original_geo = anim.previous_geometry(); let original_geo = anim.previous_geometry();
geometry = anim.geometry( geometry = anim.geometry(
output_geometry, output_geometry,
@ -1562,80 +1540,77 @@ impl FloatingLayout {
y: geometry.size.h as f64 / buffer_size.h as f64, y: geometry.size.h as f64 / buffer_size.h as f64,
}; };
window_elements = window_elements Some(move |element| match element {
.into_iter() CosmicMappedRenderElement::Stack(elem) => {
.map(|element| match element { CosmicMappedRenderElement::MovingStack({
CosmicMappedRenderElement::Stack(elem) => { let rescaled = RescaleRenderElement::from_element(
CosmicMappedRenderElement::MovingStack({ elem,
let rescaled = RescaleRenderElement::from_element( original_geo
elem, .loc
original_geo .as_logical()
.loc .to_physical_precise_round(output_scale),
.as_logical() scale,
.to_physical_precise_round(output_scale), );
scale,
);
RelocateRenderElement::from_element( RelocateRenderElement::from_element(
rescaled, rescaled,
(geometry.loc - original_geo.loc) (geometry.loc - original_geo.loc)
.as_logical() .as_logical()
.to_physical_precise_round(output_scale), .to_physical_precise_round(output_scale),
Relocate::Relative, Relocate::Relative,
) )
}) })
} }
CosmicMappedRenderElement::Window(elem) => { CosmicMappedRenderElement::Window(elem) => {
CosmicMappedRenderElement::MovingWindow({ CosmicMappedRenderElement::MovingWindow({
let rescaled = RescaleRenderElement::from_element( let rescaled = RescaleRenderElement::from_element(
elem, elem,
original_geo original_geo
.loc .loc
.as_logical() .as_logical()
.to_physical_precise_round(output_scale), .to_physical_precise_round(output_scale),
scale, scale,
); );
RelocateRenderElement::from_element( RelocateRenderElement::from_element(
rescaled, rescaled,
(geometry.loc - original_geo.loc) (geometry.loc - original_geo.loc)
.as_logical() .as_logical()
.to_physical_precise_round(output_scale), .to_physical_precise_round(output_scale),
Relocate::Relative, Relocate::Relative,
) )
}) })
} }
x => x, x => x,
}) })
.collect(); } else {
} None
};
if focused == Some(elem) && !elem.is_maximized(false) { if focused == Some(elem) && !elem.is_maximized(false) {
let active_window_hint = crate::theme::active_window_hint(theme);
let radius = elem.corner_radius(geometry.size.as_logical(), indicator_thickness);
if let Some((mode, resize)) = resize_indicator.as_mut() { if let Some((mode, resize)) = resize_indicator.as_mut() {
let mut resize_geometry = geometry; let mut resize_geometry = geometry;
resize_geometry.loc -= (18, 18).into(); resize_geometry.loc -= (18, 18).into();
resize_geometry.size += (36, 36).into(); resize_geometry.size += (36, 36).into();
resize.resize(resize_geometry.size.as_logical()); resize.resize(resize_geometry.size.as_logical());
resize.output_enter(output, Rectangle::default() /* unused */); resize.output_enter(output);
window_elements = resize resize.push_render_elements(
.render_elements::<CosmicWindowRenderElement<R>>( renderer,
renderer, resize_geometry
resize_geometry .loc
.loc .as_logical()
.as_logical() .to_physical_precise_round(output_scale),
.to_physical_precise_round(output_scale), output_scale.into(),
output_scale.into(), alpha * mode.alpha().unwrap_or(1.0),
alpha * mode.alpha().unwrap_or(1.0), &mut |elem| push(CosmicMappedRenderElement::Window(elem.into())),
) None,
.into_iter() );
.map(CosmicMappedRenderElement::Window)
.chain(window_elements.into_iter())
.collect();
} }
let active_window_hint = crate::theme::active_window_hint(theme);
let radius = elem.corner_radius(geometry.size.as_logical(), indicator_thickness);
if indicator_thickness > 0 { if indicator_thickness > 0 {
let element = IndicatorShader::focus_element( let element = IndicatorShader::focus_element(
renderer, renderer,
@ -1651,14 +1626,47 @@ impl FloatingLayout {
active_window_hint.blue, active_window_hint.blue,
], ],
); );
window_elements.insert(0, element.into()); push(element.into());
} }
} }
elements.extend(window_elements); let map_anim = |elem| {
} if let Some(map) = maybe_map {
map(elem)
} else {
elem
}
};
elements elem.push_render_elements(
renderer,
render_location
.as_logical()
.to_physical_precise_round(output_scale),
None,
output_scale.into(),
alpha,
None,
scanout_node,
&mut |elem| push(map_anim(elem)),
&mut |elem| lower_elements.push(map_anim(elem)),
);
if let Some(shadow_element) = elem.shadow_render_element(
renderer,
render_location
.as_logical()
.to_physical_precise_round(output_scale),
None,
output_scale.into(),
1.,
alpha,
) {
push(map_anim(shadow_element));
}
for elem in lower_elements.drain(..) {
push(elem);
}
}
} }
pub fn snap_to_corner(&self, mapped: &CosmicMapped, corners: &TiledCorners) { pub fn snap_to_corner(&self, mapped: &CosmicMapped, corners: &TiledCorners) {

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,11 @@ use std::{
use wayland_backend::server::ClientId; use wayland_backend::server::ClientId;
use crate::{ use crate::{
shell::{focus::FocusTarget, grabs::fullscreen_items, layout::tiling::PlaceholderType}, shell::{
element::CosmicStack, focus::FocusTarget, grabs::fullscreen_items,
layout::tiling::PlaceholderType,
},
utils,
wayland::{ wayland::{
handlers::data_device::{self, get_dnd_icon}, handlers::data_device::{self, get_dnd_icon},
protocols::workspace::{State as WState, WorkspaceCapabilities}, protocols::workspace::{State as WState, WorkspaceCapabilities},
@ -99,9 +103,8 @@ use self::zoom::{OutputZoomState, ZoomState};
use self::{ use self::{
element::{ element::{
CosmicWindow, MaximizedState, CosmicWindow, MaximizedState, resize_indicator::ResizeIndicator,
resize_indicator::{ResizeIndicator, resize_indicator}, swap_indicator::SwapIndicator,
swap_indicator::{SwapIndicator, swap_indicator},
}, },
focus::target::{KeyboardFocusTarget, PointerFocusTarget}, focus::target::{KeyboardFocusTarget, PointerFocusTarget},
grabs::{ grabs::{
@ -1508,6 +1511,21 @@ impl Workspaces {
#[derive(Debug)] #[derive(Debug)]
pub struct InvalidWorkspaceIndex; pub struct InvalidWorkspaceIndex;
utils::id_gen!(next_output_id, OUTPUT_ID, OUTPUT_IDS);
pub struct OutputId(usize);
impl OutputId {
pub fn namespace_for_workspace(&self, idx: usize) -> usize {
self.0 | (idx << 32)
}
}
impl Drop for OutputId {
fn drop(&mut self) {
OUTPUT_IDS.lock().unwrap().remove(&self.0);
}
}
impl Common { impl Common {
pub fn add_output(&mut self, output: &Output) { pub fn add_output(&mut self, output: &Output) {
let mut shell = self.shell.write(); let mut shell = self.shell.write();
@ -1515,6 +1533,10 @@ impl Common {
.workspaces .workspaces
.add_output(output, &mut self.workspace_state.update()); .add_output(output, &mut self.workspace_state.update());
output
.user_data()
.insert_if_missing_threadsafe(|| OutputId(next_output_id()));
if let Some(state) = shell.zoom_state.as_ref() { if let Some(state) = shell.zoom_state.as_ref() {
output.user_data().insert_if_missing_threadsafe(|| { output.user_data().insert_if_missing_threadsafe(|| {
Mutex::new(OutputZoomState::new( Mutex::new(OutputZoomState::new(
@ -2288,7 +2310,7 @@ impl Shell {
OverviewMode::Started(_, _) | OverviewMode::Active(_) OverviewMode::Started(_, _) | OverviewMode::Active(_)
) { ) {
if matches!(trigger, Trigger::KeyboardSwap(_, _)) { if matches!(trigger, Trigger::KeyboardSwap(_, _)) {
self.swap_indicator = Some(swap_indicator(evlh, self.theme.clone())); self.swap_indicator = Some(SwapIndicator::new(evlh, self.theme.clone()));
} }
self.overview_mode = OverviewMode::Started(trigger, Instant::now()); self.overview_mode = OverviewMode::Started(trigger, Instant::now());
} }
@ -2341,7 +2363,7 @@ impl Shell {
} else { } else {
self.resize_mode = ResizeMode::Started(pattern, Instant::now(), direction); self.resize_mode = ResizeMode::Started(pattern, Instant::now(), direction);
} }
self.resize_indicator = Some(resize_indicator( self.resize_indicator = Some(ResizeIndicator::new(
direction, direction,
config, config,
evlh, evlh,
@ -2468,6 +2490,12 @@ impl Shell {
increment: zoom_config.increment, increment: zoom_config.increment,
movement: zoom_config.view_moves, movement: zoom_config.view_moves,
}); });
self.update_focal_point(
seat,
seat.get_pointer().unwrap().current_location().as_global(),
zoom_config.view_moves,
);
} }
pub fn update_focal_point( pub fn update_focal_point(
@ -2592,15 +2620,36 @@ impl Shell {
pub fn remap_unfullscreened_window( pub fn remap_unfullscreened_window(
&mut self, &mut self,
surface: CosmicSurface, surface: CosmicSurface,
state: Option<FullscreenRestoreState>, mut state: Option<FullscreenRestoreState>,
loop_handle: &LoopHandle<'static, State>, loop_handle: &LoopHandle<'static, State>,
) -> CosmicMapped { ) -> CosmicMapped {
let window = CosmicMapped::from(CosmicWindow::new( if let Some(FullscreenRestoreState::Stack { state: stack_state }) = &state {
surface, if let Some(mapped) = self.mapped().find(|m| **m == stack_state.stack)
loop_handle.clone(), && let Some(stack) = mapped.stack_ref()
self.theme.clone(), {
self.appearance_conf, let idx = stack_state.idx.min(stack.len());
)); stack.add_window(surface, Some(idx), None);
return mapped.clone();
} else {
state = None;
}
}
let window = if state.as_ref().is_some_and(|s| s.was_stack()) {
CosmicMapped::from(CosmicStack::new(
std::iter::once(surface),
loop_handle.clone(),
self.theme.clone(),
self.appearance_conf,
))
} else {
CosmicMapped::from(CosmicWindow::new(
surface,
loop_handle.clone(),
self.theme.clone(),
self.appearance_conf,
))
};
if let Some(FullscreenRestoreState::Sticky { output, state, .. }) = &state { if let Some(FullscreenRestoreState::Sticky { output, state, .. }) = &state {
let output = output let output = output
@ -2637,7 +2686,9 @@ impl Shell {
workspace workspace
} }
None => self.workspaces.active_mut(&seat.active_output()).unwrap(), None => self.workspaces.active_mut(&seat.active_output()).unwrap(),
Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(), Some(FullscreenRestoreState::Sticky { .. } | FullscreenRestoreState::Stack { .. }) => {
unreachable!()
}
}; };
let fullscreen_geometry = workspace.output.geometry().to_local(&workspace.output); let fullscreen_geometry = workspace.output.geometry().to_local(&workspace.output);
@ -2752,7 +2803,9 @@ impl Shell {
} }
} }
} }
Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(), Some(FullscreenRestoreState::Sticky { .. } | FullscreenRestoreState::Stack { .. }) => {
unreachable!()
}
} }
window window
@ -2856,10 +2909,12 @@ impl Shell {
let maybe_focused = workspace.focus_stack.get(&seat).iter().next().cloned(); let maybe_focused = workspace.focus_stack.get(&seat).iter().next().cloned();
if let Some(FocusTarget::Window(focused)) = maybe_focused if let Some(FocusTarget::Window(focused)) = maybe_focused
&& (focused.is_stack() && !is_dialog && !should_be_maximized) && let Some(stack) = focused.stack_ref()
&& !is_dialog
&& !should_be_maximized
&& !(workspace.is_tiled(&focused.active_window()) && floating_exception) && !(workspace.is_tiled(&focused.active_window()) && floating_exception)
{ {
focused.stack_ref().unwrap().add_window(window, None, None); stack.add_window(window, None, None);
if was_activated { if was_activated {
workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); workspace_state.add_workspace_state(&workspace_handle, WState::Urgent);
} }
@ -2985,8 +3040,8 @@ impl Shell {
.map(|idx| (idx, m.clone())) .map(|idx| (idx, m.clone()))
}); });
let surface = if let Some((idx, mapped)) = sticky_res { let surface = if let Some((idx, mapped)) = sticky_res {
if mapped.is_stack() { if let Some(stack) = mapped.stack_ref() {
mapped.stack_ref().unwrap().remove_idx(idx) stack.remove_idx(idx)
} else { } else {
set.sticky_layer.unmap(&mapped, None); set.sticky_layer.unmap(&mapped, None);
Some(mapped.active_window()) Some(mapped.active_window())
@ -2996,20 +3051,13 @@ impl Shell {
.iter() .iter()
.position(|w| w.windows().any(|s| &s == surface)) .position(|w| w.windows().any(|s| &s == surface))
{ {
if set if let Some(stack) = set
.minimized_windows .minimized_windows
.get(idx) .get_mut(idx)
.unwrap() .unwrap()
.mapped() .mapped_mut()
.is_some_and(CosmicMapped::is_stack) .and_then(|m| m.stack_ref())
{ {
let window = set
.minimized_windows
.get_mut(idx)
.unwrap()
.mapped_mut()
.unwrap();
let stack = window.stack_ref().unwrap();
let idx = stack.surfaces().position(|s| &s == surface); let idx = stack.surfaces().position(|s| &s == surface);
idx.and_then(|idx| stack.remove_idx(idx)) idx.and_then(|idx| stack.remove_idx(idx))
} else { } else {
@ -3240,7 +3288,7 @@ impl Shell {
toplevel_enter_workspace(window, to); toplevel_enter_workspace(window, to);
// we can't restore to a given position // we can't restore to a given position
if let WorkspaceRestoreData::Tiling(Some(state)) = &mut window_state { if let WorkspaceRestoreData::Tiling(state) = &mut window_state {
state.state.take(); state.state.take();
} }
// update fullscreen state to restore to the new workspace // update fullscreen state to restore to the new workspace
@ -3256,6 +3304,7 @@ impl Shell {
state: None, state: None,
was_maximized: previous.was_maximized(), was_maximized: previous.was_maximized(),
}, },
was_stack: previous.was_stack(),
}; };
} else if let FullscreenRestoreState::Tiling { workspace, .. } } else if let FullscreenRestoreState::Tiling { workspace, .. }
| FullscreenRestoreState::Floating { workspace, .. } = previous | FullscreenRestoreState::Floating { workspace, .. } = previous
@ -3267,7 +3316,7 @@ impl Shell {
if is_minimized { if is_minimized {
let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above
let minimized_window = match window_state { let minimized_window = match window_state {
WorkspaceRestoreData::Floating(Some(previous)) => { WorkspaceRestoreData::Floating(previous) => {
let window = CosmicMapped::from(CosmicWindow::new( let window = CosmicMapped::from(CosmicWindow::new(
window.clone(), window.clone(),
evlh.clone(), evlh.clone(),
@ -3277,7 +3326,7 @@ impl Shell {
window.set_minimized(true); window.set_minimized(true);
MinimizedWindow::Floating { window, previous } MinimizedWindow::Floating { window, previous }
} }
WorkspaceRestoreData::Tiling(Some(previous)) => { WorkspaceRestoreData::Tiling(previous) => {
let window = CosmicMapped::from(CosmicWindow::new( let window = CosmicMapped::from(CosmicWindow::new(
window.clone(), window.clone(),
evlh.clone(), evlh.clone(),
@ -3344,7 +3393,7 @@ impl Shell {
self.appearance_conf, self.appearance_conf,
)); ));
let position = match window_state { let position = match window_state {
WorkspaceRestoreData::Floating(Some(data)) => Some( WorkspaceRestoreData::Floating(data) => Some(
data.position_relative(to_workspace.output.geometry().size.as_logical()), data.position_relative(to_workspace.output.geometry().size.as_logical()),
), ),
_ => None, _ => None,
@ -3444,7 +3493,7 @@ impl Shell {
let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above
if !to_workspace.tiling_enabled { if !to_workspace.tiling_enabled {
let (position, was_maximized, was_snapped) = match &window_state { let (position, was_maximized, was_snapped) = match &window_state {
WorkspaceRestoreData::Floating(Some(data)) => ( WorkspaceRestoreData::Floating(data) => (
Some(data.position_relative(to_workspace.output.geometry().size.as_logical())), Some(data.position_relative(to_workspace.output.geometry().size.as_logical())),
data.was_maximized, data.was_maximized,
data.was_snapped, data.was_snapped,
@ -3644,6 +3693,8 @@ impl Shell {
return None; return None;
}; };
let mut theme = self.theme.clone();
theme.transparent = theme.cosmic().frosted_windows;
let grab = MenuGrab::new( let grab = MenuGrab::new(
GrabStartData::Pointer(start_data), GrabStartData::Pointer(start_data),
seat, seat,
@ -3652,7 +3703,7 @@ impl Shell {
MenuAlignment::CORNER, MenuAlignment::CORNER,
None, None,
evlh.clone(), evlh.clone(),
self.theme.clone(), theme,
); );
Some((grab, Focus::Keep)) Some((grab, Focus::Keep))
@ -3827,13 +3878,13 @@ impl Shell {
} }
(initial_window_location, layer, workspace.handle) (initial_window_location, layer, workspace.handle)
} else if let Some(sticky_layer) = self } else {
.workspaces let sticky_layer = self
.sets .workspaces
.get_mut(&cursor_output) .sets
.filter(|set| set.sticky_layer.mapped().any(|m| m == &old_mapped)) .get_mut(&cursor_output)
.map(|set| &mut set.sticky_layer) .filter(|set| set.sticky_layer.mapped().any(|m| m == &old_mapped))
{ .map(|set| &mut set.sticky_layer)?;
let elem_geo = sticky_layer.element_geometry(&old_mapped).unwrap(); let elem_geo = sticky_layer.element_geometry(&old_mapped).unwrap();
let mut initial_window_location = elem_geo.loc.to_global(&cursor_output); let mut initial_window_location = elem_geo.loc.to_global(&cursor_output);
@ -3876,8 +3927,6 @@ impl Shell {
ManagedLayer::Sticky, ManagedLayer::Sticky,
self.active_space(&cursor_output).unwrap().handle, self.active_space(&cursor_output).unwrap().handle,
) )
} else {
return None;
}; };
toplevel_leave_workspace(&window, &workspace_handle); toplevel_leave_workspace(&window, &workspace_handle);
@ -4171,14 +4220,13 @@ impl Shell {
.unwrap() .unwrap()
.to_global(&set.output); .to_global(&set.output);
(&mut set.sticky_layer, geometry) (&mut set.sticky_layer, geometry)
} else if let Some(workspace) = self.space_for_mut(mapped) { } else {
let workspace = self.space_for_mut(mapped)?;
let geometry = workspace let geometry = workspace
.element_geometry(mapped) .element_geometry(mapped)
.unwrap() .unwrap()
.to_global(workspace.output()); .to_global(workspace.output());
(&mut workspace.floating_layer, geometry) (&mut workspace.floating_layer, geometry)
} else {
return None;
}; };
let new_loc = if edge.contains(ResizeEdge::LEFT) { let new_loc = if edge.contains(ResizeEdge::LEFT) {
@ -4215,7 +4263,8 @@ impl Shell {
ReleaseMode::Click, ReleaseMode::Click,
) { ) {
grab.into() grab.into()
} else if let Some(ws) = self.space_for_mut(mapped) { } else {
let ws = self.space_for_mut(mapped)?;
let node_id = mapped.tiling_node_id.lock().unwrap().clone()?; let node_id = mapped.tiling_node_id.lock().unwrap().clone()?;
let (node, left_up_idx, orientation) = ws.tiling_layer.resize_request(node_id, edge)?; let (node, left_up_idx, orientation) = ws.tiling_layer.resize_request(node_id, edge)?;
ResizeForkGrab::new( ResizeForkGrab::new(
@ -4228,8 +4277,6 @@ impl Shell {
ReleaseMode::Click, ReleaseMode::Click,
) )
.into() .into()
} else {
return None;
}; };
Some(((focus, new_loc), (grab, Focus::Keep))) Some(((focus, new_loc), (grab, Focus::Keep)))
@ -4466,10 +4513,9 @@ impl Shell {
.find(|set| set.sticky_layer.mapped().any(|m| m == &mapped)) .find(|set| set.sticky_layer.mapped().any(|m| m == &mapped))
{ {
&mut set.sticky_layer &mut set.sticky_layer
} else if let Some(workspace) = self.space_for_mut(&mapped) {
&mut workspace.floating_layer
} else { } else {
return None; let workspace = self.space_for_mut(&mapped)?;
&mut workspace.floating_layer
}; };
let grab: ResizeGrab = if let Some(grab) = floating_layer.resize_request( let grab: ResizeGrab = if let Some(grab) = floating_layer.resize_request(
@ -4481,7 +4527,8 @@ impl Shell {
ReleaseMode::NoMouseButtons, ReleaseMode::NoMouseButtons,
) { ) {
grab.into() grab.into()
} else if let Some(ws) = self.space_for_mut(&mapped) { } else {
let ws = self.space_for_mut(&mapped)?;
let node_id = mapped.tiling_node_id.lock().unwrap().clone()?; let node_id = mapped.tiling_node_id.lock().unwrap().clone()?;
let (node, left_up_idx, orientation) = let (node, left_up_idx, orientation) =
ws.tiling_layer.resize_request(node_id, edges)?; ws.tiling_layer.resize_request(node_id, edges)?;
@ -4495,8 +4542,6 @@ impl Shell {
ReleaseMode::NoMouseButtons, ReleaseMode::NoMouseButtons,
) )
.into() .into()
} else {
return None;
}; };
Some((grab, Focus::Clear)) Some((grab, Focus::Clear))
@ -4796,14 +4841,19 @@ impl Shell {
{ {
let mut from = set.sticky_layer.element_geometry(&mapped).unwrap(); let mut from = set.sticky_layer.element_geometry(&mapped).unwrap();
let mut was_maximized = false; let mut was_maximized = false;
window = if mapped let mut restore_state = None;
.stack_ref() let was_stack = mapped.is_stack();
.map(|stack| stack.len() > 1) window = if let Some(stack) = mapped.stack_ref()
.unwrap_or(false) && stack.len() > 1
{ {
let stack = mapped.stack_ref().unwrap(); let idx = stack.surfaces().position(|s| &s == surface)?;
let surface = stack.surfaces().find(|s| s == surface).unwrap(); let surface = stack.remove_idx(idx)?;
stack.remove_window(&surface); restore_state = Some(FullscreenRestoreState::Stack {
state: StackRestoreData {
stack: mapped.key(),
idx,
},
});
surface surface
} else { } else {
// Must be set before `map_internal`/`unmap` below, as both may call // Must be set before `map_internal`/`unmap` below, as both may call
@ -4836,7 +4886,7 @@ impl Shell {
workspace.map_fullscreen( workspace.map_fullscreen(
&window, &window,
&seat, &seat,
Some(FullscreenRestoreState::Sticky { Some(restore_state.unwrap_or(FullscreenRestoreState::Sticky {
output: old_output, output: old_output,
state: FloatingRestoreData { state: FloatingRestoreData {
geometry: from, geometry: from,
@ -4844,10 +4894,12 @@ impl Shell {
was_maximized, was_maximized,
was_snapped: None, was_snapped: None,
}, },
}), was_stack,
})),
Some(from), Some(from),
); );
} else if let Some(workspace) = self.space_for_mut(&mapped) { } else {
let workspace = self.space_for_mut(&mapped)?;
if mapped.is_minimized() { if mapped.is_minimized() {
// TODO: Rewrite the `MinimizedWindow` to restore to fullscreen // TODO: Rewrite the `MinimizedWindow` to restore to fullscreen
return None; return None;
@ -4875,23 +4927,26 @@ impl Shell {
&seat, &seat,
match state { match state {
WorkspaceRestoreData::Floating(floating_state) => { WorkspaceRestoreData::Floating(floating_state) => {
floating_state.map(|state| FullscreenRestoreState::Floating { Some(FullscreenRestoreState::Floating {
workspace: handle, workspace: handle,
state, state: floating_state,
was_stack: mapped.is_stack(),
}) })
} }
WorkspaceRestoreData::Tiling(tiling_state) => { WorkspaceRestoreData::Tiling(tiling_state) => {
tiling_state.map(|state| FullscreenRestoreState::Tiling { Some(FullscreenRestoreState::Tiling {
workspace: handle, workspace: handle,
state, state: tiling_state,
was_stack: mapped.is_stack(),
}) })
} }
WorkspaceRestoreData::Stack(stack_state) => {
Some(FullscreenRestoreState::Stack { state: stack_state })
}
WorkspaceRestoreData::Fullscreen(_) => unreachable!(), WorkspaceRestoreData::Fullscreen(_) => unreachable!(),
}, },
Some(from), Some(from),
); );
} else {
return None;
}; };
Some(KeyboardFocusTarget::Fullscreen(window)) Some(KeyboardFocusTarget::Fullscreen(window))
@ -5003,13 +5058,14 @@ impl Shell {
let map = smithay::desktop::layer_map_for_output(output); let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() { for layer_surface in map.layers() {
let namespace = self.workspaces.active_num(output).1;
layer_surface.take_presentation_feedback( layer_surface.take_presentation_feedback(
&mut output_presentation_feedback, &mut output_presentation_feedback,
surface_primary_scanout_output, surface_primary_scanout_output,
|surface, _| { |surface, _| {
surface_presentation_feedback_flags_from_states( surface_presentation_feedback_flags_from_states(
surface, surface,
None, Some(namespace),
render_element_states, render_element_states,
) )
}, },

View file

@ -243,6 +243,12 @@ pub fn create_seat(
seat seat
} }
#[derive(Debug, Copy, Clone)]
pub struct CursorGeometry {
pub geometry: Rectangle<i32, Buffer>,
pub hotspot: Point<i32, Buffer>,
}
pub trait SeatExt { pub trait SeatExt {
fn id(&self) -> usize; fn id(&self) -> usize;
@ -266,7 +272,7 @@ pub trait SeatExt {
&self, &self,
loc: impl Into<Point<f64, Buffer>>, loc: impl Into<Point<f64, Buffer>>,
time: Time<Monotonic>, time: Time<Monotonic>,
) -> Option<(Rectangle<i32, Buffer>, Point<i32, Buffer>)>; ) -> Option<CursorGeometry>;
fn cursor_image_status(&self) -> CursorImageStatus; fn cursor_image_status(&self) -> CursorImageStatus;
fn set_cursor_image_status(&self, status: CursorImageStatus); fn set_cursor_image_status(&self, status: CursorImageStatus);
} }
@ -367,7 +373,7 @@ impl SeatExt for Seat<State> {
&self, &self,
loc: impl Into<Point<f64, Buffer>>, loc: impl Into<Point<f64, Buffer>>,
time: Time<Monotonic>, time: Time<Monotonic>,
) -> Option<(Rectangle<i32, Buffer>, Point<i32, Buffer>)> { ) -> Option<CursorGeometry> {
let location = loc.into().to_i32_round(); let location = loc.into().to_i32_round();
match self.cursor_image_status() { match self.cursor_image_status() {
@ -386,7 +392,10 @@ impl SeatExt for Seat<State> {
(geo.loc.x, geo.loc.y).into(), (geo.loc.x, geo.loc.y).into(),
geo.size.to_buffer(1, Transform::Normal), geo.size.to_buffer(1, Transform::Normal),
); );
Some((buffer_geo, (hotspot.x, hotspot.y).into())) Some(CursorGeometry {
geometry: buffer_geo,
hotspot: (hotspot.x, hotspot.y).into(),
})
} }
CursorImageStatus::Named(cursor_icon) => { CursorImageStatus::Named(cursor_icon) => {
let seat_userdata = self.user_data(); let seat_userdata = self.user_data();
@ -398,10 +407,13 @@ impl SeatExt for Seat<State> {
.get_named_cursor(cursor_icon) .get_named_cursor(cursor_icon)
.get_image(1, time.as_millis()); .get_image(1, time.as_millis());
Some(( Some(CursorGeometry {
Rectangle::new(location, (frame.width as i32, frame.height as i32).into()), geometry: Rectangle::new(
(frame.xhot as i32, frame.yhot as i32).into(), location,
)) (frame.width as i32, frame.height as i32).into(),
),
hotspot: (frame.xhot as i32, frame.yhot as i32).into(),
})
} }
CursorImageStatus::Hidden => None, CursorImageStatus::Hidden => None,
} }

View file

@ -1,11 +1,9 @@
use crate::backend::render::wayland::SurfaceRenderElement;
use crate::shell::focus::FocusTarget; use crate::shell::focus::FocusTarget;
use crate::shell::layout::tiling::RestoreTilingState; use crate::shell::layout::tiling::RestoreTilingState;
use crate::wayland::handlers::xdg_activation::ActivationContext; use crate::wayland::handlers::xdg_activation::ActivationContext;
use crate::{ use crate::{
backend::render::{ backend::render::{BackdropShader, element::AsGlowRenderer},
BackdropShader,
element::{AsGlowRenderer, FromGlesError},
},
shell::{ shell::{
ANIMATION_DURATION, OverviewMode, SeatMoveGrabState, ANIMATION_DURATION, OverviewMode, SeatMoveGrabState,
layout::{ layout::{
@ -31,14 +29,15 @@ use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::Tiling
use id_tree::Tree; use id_tree::Tree;
use indexmap::IndexSet; use indexmap::IndexSet;
use keyframe::{ease, functions::EaseInOutCubic}; use keyframe::{ease, functions::EaseInOutCubic};
use smithay::backend::renderer::element::Kind; use smallvec::SmallVec;
use smithay::backend::drm::DrmNode;
use smithay::backend::renderer::element::{Kind, NamespacedElement};
use smithay::output::WeakOutput; use smithay::output::WeakOutput;
use smithay::utils::user_data::UserDataMap; use smithay::utils::user_data::UserDataMap;
use smithay::{ use smithay::{
backend::renderer::{ backend::renderer::{
element::{ element::{
Element, Id, RenderElement, surface::WaylandSurfaceRenderElement, Element, Id, RenderElement, texture::TextureRenderElement, utils::RescaleRenderElement,
texture::TextureRenderElement, utils::RescaleRenderElement,
}, },
gles::GlesTexture, gles::GlesTexture,
glow::GlowRenderer, glow::GlowRenderer,
@ -61,7 +60,7 @@ use wayland_backend::server::ClientId;
use super::{ use super::{
CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode, CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode,
element::{ element::{
CosmicMapped, MaximizedState, resize_indicator::ResizeIndicator, CosmicMapped, CosmicMappedKey, MaximizedState, resize_indicator::ResizeIndicator,
stack::CosmicStackRenderElement, swap_indicator::SwapIndicator, stack::CosmicStackRenderElement, swap_indicator::SwapIndicator,
window::CosmicWindowRenderElement, window::CosmicWindowRenderElement,
}, },
@ -253,14 +252,20 @@ pub enum FullscreenRestoreState {
Tiling { Tiling {
workspace: WorkspaceHandle, workspace: WorkspaceHandle,
state: TilingRestoreData, state: TilingRestoreData,
was_stack: bool,
}, },
Floating { Floating {
workspace: WorkspaceHandle, workspace: WorkspaceHandle,
state: FloatingRestoreData, state: FloatingRestoreData,
was_stack: bool,
}, },
Sticky { Sticky {
output: WeakOutput, output: WeakOutput,
state: FloatingRestoreData, state: FloatingRestoreData,
was_stack: bool,
},
Stack {
state: StackRestoreData,
}, },
} }
@ -270,6 +275,18 @@ impl FullscreenRestoreState {
FullscreenRestoreState::Floating { state, .. } FullscreenRestoreState::Floating { state, .. }
| FullscreenRestoreState::Sticky { state, .. } => state.was_maximized, | FullscreenRestoreState::Sticky { state, .. } => state.was_maximized,
FullscreenRestoreState::Tiling { state, .. } => state.was_maximized, FullscreenRestoreState::Tiling { state, .. } => state.was_maximized,
FullscreenRestoreState::Stack { .. } => false,
}
}
// Surface was previously a single-window stack
pub fn was_stack(&self) -> bool {
match self {
FullscreenRestoreState::Floating { was_stack, .. }
| FullscreenRestoreState::Sticky { was_stack, .. } => *was_stack,
FullscreenRestoreState::Tiling { was_stack, .. } => *was_stack,
// Stack wasn't removed; surface was removed from the stack
FullscreenRestoreState::Stack { .. } => false,
} }
} }
} }
@ -277,18 +294,9 @@ impl FullscreenRestoreState {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum WorkspaceRestoreData { pub enum WorkspaceRestoreData {
Fullscreen(Option<FullscreenRestoreData>), Fullscreen(Option<FullscreenRestoreData>),
Tiling(Option<TilingRestoreData>), Tiling(TilingRestoreData),
Floating(Option<FloatingRestoreData>), Floating(FloatingRestoreData),
} Stack(StackRestoreData),
impl From<ManagedLayer> for WorkspaceRestoreData {
fn from(value: ManagedLayer) -> Self {
match value {
ManagedLayer::Floating | ManagedLayer::Sticky => WorkspaceRestoreData::Floating(None),
ManagedLayer::Tiling => WorkspaceRestoreData::Tiling(None),
ManagedLayer::Fullscreen => WorkspaceRestoreData::Fullscreen(None),
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -320,6 +328,12 @@ pub struct TilingRestoreData {
pub was_maximized: bool, pub was_maximized: bool,
} }
#[derive(Debug, Clone)]
pub struct StackRestoreData {
pub stack: CosmicMappedKey,
pub idx: usize,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FullscreenRestoreData { pub struct FullscreenRestoreData {
pub previous_state: FullscreenRestoreState, pub previous_state: FullscreenRestoreState,
@ -641,32 +655,30 @@ impl Workspace {
mapped.set_minimized(false); mapped.set_minimized(false);
return Some(match state { return Some(match state {
MinimizedWindow::Floating { previous, .. } => { MinimizedWindow::Floating { previous, .. } => {
WorkspaceRestoreData::Floating(Some(previous)) WorkspaceRestoreData::Floating(previous)
}
MinimizedWindow::Tiling { previous, .. } => {
WorkspaceRestoreData::Tiling(Some(previous))
} }
MinimizedWindow::Tiling { previous, .. } => WorkspaceRestoreData::Tiling(previous),
MinimizedWindow::Fullscreen { .. } => unreachable!(), MinimizedWindow::Fullscreen { .. } => unreachable!(),
}); });
} }
if let Ok(state) = self.tiling_layer.unmap(mapped, None) { if let Ok(state) = self.tiling_layer.unmap(mapped, None) {
return Some(WorkspaceRestoreData::Tiling(Some(TilingRestoreData { return Some(WorkspaceRestoreData::Tiling(TilingRestoreData {
state, state,
was_maximized: was_maximized.is_some(), was_maximized: was_maximized.is_some(),
}))); }));
} }
let was_snapped = *mapped.floating_tiled.lock().unwrap(); let was_snapped = *mapped.floating_tiled.lock().unwrap();
// unmaximize_request might have triggered a `floating_layer.refresh()`, // unmaximize_request might have triggered a `floating_layer.refresh()`,
// which may have already removed a non-alive surface. // which may have already removed a non-alive surface.
if let Some(floating_geometry) = self.floating_layer.unmap(mapped, None).or(was_maximized) { if let Some(floating_geometry) = self.floating_layer.unmap(mapped, None).or(was_maximized) {
return Some(WorkspaceRestoreData::Floating(Some(FloatingRestoreData { return Some(WorkspaceRestoreData::Floating(FloatingRestoreData {
geometry: floating_geometry, geometry: floating_geometry,
output_size: self.output.geometry().size.as_logical(), output_size: self.output.geometry().size.as_logical(),
was_maximized: was_maximized.is_some(), was_maximized: was_maximized.is_some(),
was_snapped, was_snapped,
}))); }));
}; };
None None
@ -712,19 +724,17 @@ impl Workspace {
} }
let mapped = self.element_for_surface(surface)?; let mapped = self.element_for_surface(surface)?;
let maybe_stack = mapped.stack_ref().filter(|s| s.len() > 1); if let Some(stack) = mapped.stack_ref()
if let Some(stack) = maybe_stack
&& stack.len() > 1 && stack.len() > 1
{ {
let idx = stack.surfaces().position(|s| &s == surface); let idx = stack.surfaces().position(|s| &s == surface)?;
let layer = if self.is_tiled(surface) { return Some((
ManagedLayer::Tiling stack.remove_idx(idx)?,
} else { WorkspaceRestoreData::Stack(StackRestoreData {
ManagedLayer::Floating stack: mapped.key(),
}; idx,
return idx }),
.and_then(|idx| stack.remove_idx(idx)) ));
.map(|s| (s, layer.into()));
} }
// we know mapped is no stack with more than one element now, // we know mapped is no stack with more than one element now,
@ -775,7 +785,7 @@ impl Workspace {
location: Point<f64, Global>, location: Point<f64, Global>,
seat: &Seat<State>, seat: &Seat<State>,
) -> Option<KeyboardFocusTarget> { ) -> Option<KeyboardFocusTarget> {
if !self.output.geometry().contains(location.to_i32_round()) { if !self.output.geometry().contains(location.to_i32_floor()) {
return None; return None;
} }
let location = location.to_local(&self.output); let location = location.to_local(&self.output);
@ -824,7 +834,7 @@ impl Workspace {
location: Point<f64, Global>, location: Point<f64, Global>,
seat: &Seat<State>, seat: &Seat<State>,
) -> Option<KeyboardFocusTarget> { ) -> Option<KeyboardFocusTarget> {
if !self.output.geometry().contains(location.to_i32_round()) { if !self.output.geometry().contains(location.to_i32_floor()) {
return None; return None;
} }
let location = location.to_local(&self.output); let location = location.to_local(&self.output);
@ -874,7 +884,7 @@ impl Workspace {
overview: OverviewMode, overview: OverviewMode,
seat: &Seat<State>, seat: &Seat<State>,
) -> Option<(PointerFocusTarget, Point<f64, Global>)> { ) -> Option<(PointerFocusTarget, Point<f64, Global>)> {
if !self.output.geometry().contains(location.to_i32_round()) { if !self.output.geometry().contains(location.to_i32_floor()) {
return None; return None;
} }
let location = location.to_local(&self.output); let location = location.to_local(&self.output);
@ -924,7 +934,7 @@ impl Workspace {
overview: OverviewMode, overview: OverviewMode,
seat: &Seat<State>, seat: &Seat<State>,
) -> Option<(PointerFocusTarget, Point<f64, Global>)> { ) -> Option<(PointerFocusTarget, Point<f64, Global>)> {
if !self.output.geometry().contains(location.to_i32_round()) { if !self.output.geometry().contains(location.to_i32_floor()) {
return None; return None;
} }
let location = location.to_local(&self.output); let location = location.to_local(&self.output);
@ -1612,8 +1622,9 @@ impl Workspace {
resize_indicator: Option<(ResizeMode, ResizeIndicator)>, resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
indicator_thickness: u8, indicator_thickness: u8,
theme: &CosmicTheme, theme: &CosmicTheme,
) -> Result<Vec<WorkspaceRenderElement<R>>, OutputNotMapped> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(WorkspaceRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
@ -1621,108 +1632,109 @@ impl Workspace {
CosmicStackRenderElement<R>: RenderElement<R>, CosmicStackRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>, WorkspaceRenderElement<R>: RenderElement<R>,
{ {
let mut elements = Vec::default();
let output_scale = self.output.current_scale().fractional_scale(); let output_scale = self.output.current_scale().fractional_scale();
let zone = { let zone = {
let layer_map = layer_map_for_output(&self.output); let layer_map = layer_map_for_output(&self.output);
layer_map.non_exclusive_zone().as_local() layer_map.non_exclusive_zone().as_local()
}; };
let focused = self.focus_stack.get(last_active_seat).last().cloned(); let focused = self.focus_stack.get(last_active_seat).last().cloned();
let fullscreen_focused = matches!(focused, Some(FocusTarget::Fullscreen(_)));
let render_fullscreen = |fullscreen: &FullscreenSurface, let mut fullscreen_elements = SmallVec::<[WorkspaceRenderElement<R>; 2]>::new_const();
renderer: &mut R, let mut render_fullscreen =
output_scale: f64| |fullscreen: &FullscreenSurface, renderer: &mut R, output_scale: f64| {
-> Vec<WorkspaceRenderElement<R>> { let fullscreen_geo = self.fullscreen_geometry_for(fullscreen);
let fullscreen_geo = self.fullscreen_geometry_for(fullscreen); let previous_geo = fullscreen
let previous_geo = fullscreen .previous_geometry
.previous_geometry .as_ref()
.as_ref() .unwrap_or(&fullscreen_geo);
.unwrap_or(&fullscreen_geo);
let (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) { let (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) {
(Some(started), _) => { (Some(started), _) => {
let duration = Instant::now().duration_since(started).as_secs_f64() let duration = Instant::now().duration_since(started).as_secs_f64()
/ FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); / FULLSCREEN_ANIMATION_DURATION.as_secs_f64();
( (
ease( ease(
EaseInOutCubic, EaseInOutCubic,
EaseRectangle(*previous_geo), EaseRectangle(*previous_geo),
EaseRectangle(fullscreen_geo), EaseRectangle(fullscreen_geo),
duration, duration,
)
.0,
ease(EaseInOutCubic, 0.0, 1.0, duration),
) )
.0, }
ease(EaseInOutCubic, 0.0, 1.0, duration), (_, Some(ended)) => {
) let duration = Instant::now().duration_since(ended).as_secs_f64()
} / FULLSCREEN_ANIMATION_DURATION.as_secs_f64();
(_, Some(ended)) => { (
let duration = Instant::now().duration_since(ended).as_secs_f64() ease(
/ FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); EaseInOutCubic,
( EaseRectangle(fullscreen_geo),
ease( EaseRectangle(*previous_geo),
EaseInOutCubic, duration,
EaseRectangle(fullscreen_geo), )
EaseRectangle(*previous_geo), .0,
duration, ease(EaseInOutCubic, 1.0, 0.0, duration),
) )
.0, }
ease(EaseInOutCubic, 1.0, 0.0, duration), (None, None) => (fullscreen_geo, 1.0),
) };
}
(None, None) => (fullscreen_geo, 1.0),
};
let render_loc = target_geo let render_loc = target_geo
.loc .loc
.as_logical() .as_logical()
.to_physical_precise_round(output_scale); .to_physical_precise_round(output_scale);
// Only rescale geometry when animating let fullscreen_geo = fullscreen.surface.0.geometry();
let animation_rescale = |elem| { let scale = Scale {
if fullscreen.is_animating() { x: target_geo.size.w as f64 / fullscreen_geo.size.w as f64,
let fullscreen_geo = fullscreen.surface.0.geometry(); y: target_geo.size.h as f64 / fullscreen_geo.size.h as f64,
let scale = Scale { };
x: target_geo.size.w as f64 / fullscreen_geo.size.w as f64,
y: target_geo.size.h as f64 / fullscreen_geo.size.h as f64,
};
RescaleRenderElement::from_element(elem, render_loc, scale).into() // Only rescale geometry when animating
} else { let animation_rescale = |elem| {
Into::<WorkspaceRenderElement<_>>::into(elem) if fullscreen.is_animating() {
} RescaleRenderElement::from_element(elem, render_loc, scale).into()
}; } else {
Into::<WorkspaceRenderElement<_>>::into(elem)
}
};
fullscreen let mut fullscreen_push = |elem: SurfaceRenderElement<R>| {
.surface if fullscreen_focused {
.render_elements::<R, CosmicWindowRenderElement<R>>( push(animation_rescale(elem.into()))
} else {
fullscreen_elements.push(animation_rescale(elem.into()))
}
};
fullscreen.surface.push_render_elements(
renderer, renderer,
render_loc, render_loc,
output_scale.into(), output_scale.into(),
alpha, alpha,
Some(true), Some(true),
) scanout_node,
.into_iter() false,
.map(animation_rescale) [0, 0, 0, 0],
.collect::<Vec<_>>() 0,
}; &mut fullscreen_push,
None,
);
};
let top_fullscreen = self.get_fullscreen(last_active_seat); let top_fullscreen = self.get_fullscreen(last_active_seat);
let mut fullscreen_elements: Vec<WorkspaceRenderElement<R>> = Vec::new();
if let Some(fs) = top_fullscreen { if let Some(fs) = top_fullscreen {
fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale)); render_fullscreen(fs, renderer, output_scale)
} }
// Also render any animating (entering/exiting) fullscreens // Also render any animating (entering/exiting) fullscreens
for fs in self.fullscreen_surfaces.iter().filter(|f| f.is_animating()) { for fs in self.fullscreen_surfaces.iter().filter(|f| f.is_animating()) {
if top_fullscreen.is_none_or(|top| top.surface != fs.surface) { if top_fullscreen.is_none_or(|top| top.surface != fs.surface) {
fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale)); render_fullscreen(fs, renderer, output_scale);
}; };
} }
if matches!(focused, Some(FocusTarget::Fullscreen(_))) {
elements.append(&mut fullscreen_elements);
}
let any_fullscreen_animating = self let any_fullscreen_animating = self
.fullscreen_surfaces .fullscreen_surfaces
.iter() .iter()
@ -1753,28 +1765,21 @@ impl Workspace {
OverviewMode::None => 1.0, OverviewMode::None => 1.0,
}; };
elements.extend( self.floating_layer.render(
self.floating_layer renderer,
.render::<R>( focused.as_ref().and_then(|target| {
renderer, if let FocusTarget::Window(mapped) = target {
render_focus Some(mapped)
.then(|| { } else {
focused.as_ref().and_then(|target| { None
if let FocusTarget::Window(mapped) = target { }
Some(mapped) }),
} else { resize_indicator.clone(),
None indicator_thickness,
} alpha,
}) theme,
}) scanout_node,
.flatten(), &mut |elem| push(elem.into()),
resize_indicator.clone(),
indicator_thickness,
alpha,
theme,
)
.into_iter()
.map(WorkspaceRenderElement::from),
); );
let alpha = match &overview.0 { let alpha = match &overview.0 {
@ -1791,23 +1796,31 @@ impl Workspace {
}; };
//tiling surfaces //tiling surfaces
elements.extend( self.tiling_layer.render(
self.tiling_layer renderer,
.render::<R>( render_focus.then_some(last_active_seat),
renderer, render_focus
render_focus.then_some(last_active_seat), .then(|| {
zone, focused.as_ref().and_then(|target| {
overview, if let FocusTarget::Window(mapped) = target {
resize_indicator, Some(mapped)
indicator_thickness, } else {
theme, None
)? }
.into_iter() })
.map(WorkspaceRenderElement::from), })
.flatten(),
zone,
overview,
resize_indicator,
indicator_thickness,
theme,
scanout_node,
&mut |elem| push(elem.into()),
); );
if let Some(alpha) = alpha { if let Some(alpha) = alpha {
elements.push( push(
Into::<CosmicMappedRenderElement<R>>::into(BackdropShader::element( Into::<CosmicMappedRenderElement<R>>::into(BackdropShader::element(
renderer, renderer,
self.backdrop_id.clone(), self.backdrop_id.clone(),
@ -1821,11 +1834,9 @@ impl Workspace {
} }
} }
if !matches!(focused, Some(FocusTarget::Fullscreen(_))) { for elem in fullscreen_elements {
elements.extend(fullscreen_elements.into_iter()); push(elem);
} }
Ok(elements)
} }
#[profiling::function] #[profiling::function]
@ -1836,8 +1847,9 @@ impl Workspace {
render_focus: bool, render_focus: bool,
overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree<Data>>)>), overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree<Data>>)>),
theme: &CosmicTheme, theme: &CosmicTheme,
) -> Result<Vec<WorkspaceRenderElement<R>>, OutputNotMapped> scanout_node: Option<DrmNode>,
where push: &mut dyn FnMut(WorkspaceRenderElement<R>),
) where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
@ -1845,8 +1857,6 @@ impl Workspace {
CosmicStackRenderElement<R>: RenderElement<R>, CosmicStackRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>, WorkspaceRenderElement<R>: RenderElement<R>,
{ {
let mut elements = Vec::default();
let output_scale = self.output.current_scale().fractional_scale(); let output_scale = self.output.current_scale().fractional_scale();
let zone = { let zone = {
let layer_map = layer_map_for_output(&self.output); let layer_map = layer_map_for_output(&self.output);
@ -1901,17 +1911,14 @@ impl Workspace {
.as_logical() .as_logical()
.to_physical_precise_round(output_scale); .to_physical_precise_round(output_scale);
elements.extend( fullscreen.surface.push_popup_render_elements(
fullscreen renderer,
.surface render_loc,
.popup_render_elements::<R, CosmicWindowRenderElement<R>>( output_scale.into(),
renderer, alpha,
render_loc, scanout_node,
output_scale.into(), 0,
alpha, &mut |elem| push(WorkspaceRenderElement::FullscreenPopup(elem.into())),
)
.into_iter()
.map(Into::into),
); );
} }
@ -1945,29 +1952,20 @@ impl Workspace {
OverviewMode::None => 1.0, OverviewMode::None => 1.0,
}; };
elements.extend( self.floating_layer
self.floating_layer .render_popups(renderer, alpha, scanout_node, &mut |elem| push(elem.into()));
.render_popups::<R>(renderer, alpha)
.into_iter()
.map(WorkspaceRenderElement::from),
);
//tiling surfaces //tiling surfaces
elements.extend( self.tiling_layer.render_popups(
self.tiling_layer renderer,
.render_popups::<R>( render_focus.then_some(last_active_seat),
renderer, zone,
render_focus.then_some(last_active_seat), overview,
zone, theme,
overview, scanout_node,
theme, &mut |elem| push(elem.into()),
)?
.into_iter()
.map(WorkspaceRenderElement::from),
); );
} }
Ok(elements)
} }
} }
@ -1986,9 +1984,10 @@ pub struct OutputNotMapped;
pub enum WorkspaceRenderElement<R> pub enum WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
{ {
OverrideRedirect(WaylandSurfaceRenderElement<R>), OverrideRedirect(SurfaceRenderElement<R>),
LowerLayerShell(NamespacedElement<SurfaceRenderElement<R>>),
Fullscreen(RescaleRenderElement<CosmicWindowRenderElement<R>>), Fullscreen(RescaleRenderElement<CosmicWindowRenderElement<R>>),
FullscreenPopup(CosmicWindowRenderElement<R>), FullscreenPopup(CosmicWindowRenderElement<R>),
Window(CosmicMappedRenderElement<R>), Window(CosmicMappedRenderElement<R>),
@ -1998,11 +1997,12 @@ where
impl<R> Element for WorkspaceRenderElement<R> impl<R> Element for WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
{ {
fn id(&self) -> &smithay::backend::renderer::element::Id { fn id(&self) -> &smithay::backend::renderer::element::Id {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.id(), WorkspaceRenderElement::OverrideRedirect(elem) => elem.id(),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.id(),
WorkspaceRenderElement::Fullscreen(elem) => elem.id(), WorkspaceRenderElement::Fullscreen(elem) => elem.id(),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.id(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.id(),
WorkspaceRenderElement::Window(elem) => elem.id(), WorkspaceRenderElement::Window(elem) => elem.id(),
@ -2013,6 +2013,7 @@ where
fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.current_commit(), WorkspaceRenderElement::OverrideRedirect(elem) => elem.current_commit(),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.current_commit(),
WorkspaceRenderElement::Fullscreen(elem) => elem.current_commit(), WorkspaceRenderElement::Fullscreen(elem) => elem.current_commit(),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.current_commit(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.current_commit(),
WorkspaceRenderElement::Window(elem) => elem.current_commit(), WorkspaceRenderElement::Window(elem) => elem.current_commit(),
@ -2023,6 +2024,7 @@ where
fn src(&self) -> Rectangle<f64, smithay::utils::Buffer> { fn src(&self) -> Rectangle<f64, smithay::utils::Buffer> {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.src(), WorkspaceRenderElement::OverrideRedirect(elem) => elem.src(),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.src(),
WorkspaceRenderElement::Fullscreen(elem) => elem.src(), WorkspaceRenderElement::Fullscreen(elem) => elem.src(),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.src(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.src(),
WorkspaceRenderElement::Window(elem) => elem.src(), WorkspaceRenderElement::Window(elem) => elem.src(),
@ -2033,6 +2035,7 @@ where
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, smithay::utils::Physical> { fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, smithay::utils::Physical> {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.geometry(scale), WorkspaceRenderElement::OverrideRedirect(elem) => elem.geometry(scale),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.geometry(scale),
WorkspaceRenderElement::Fullscreen(elem) => elem.geometry(scale), WorkspaceRenderElement::Fullscreen(elem) => elem.geometry(scale),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.geometry(scale), WorkspaceRenderElement::FullscreenPopup(elem) => elem.geometry(scale),
WorkspaceRenderElement::Window(elem) => elem.geometry(scale), WorkspaceRenderElement::Window(elem) => elem.geometry(scale),
@ -2043,6 +2046,7 @@ where
fn location(&self, scale: Scale<f64>) -> Point<i32, smithay::utils::Physical> { fn location(&self, scale: Scale<f64>) -> Point<i32, smithay::utils::Physical> {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.location(scale), WorkspaceRenderElement::OverrideRedirect(elem) => elem.location(scale),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.location(scale),
WorkspaceRenderElement::Fullscreen(elem) => elem.location(scale), WorkspaceRenderElement::Fullscreen(elem) => elem.location(scale),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.location(scale), WorkspaceRenderElement::FullscreenPopup(elem) => elem.location(scale),
WorkspaceRenderElement::Window(elem) => elem.location(scale), WorkspaceRenderElement::Window(elem) => elem.location(scale),
@ -2053,6 +2057,7 @@ where
fn transform(&self) -> smithay::utils::Transform { fn transform(&self) -> smithay::utils::Transform {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.transform(), WorkspaceRenderElement::OverrideRedirect(elem) => elem.transform(),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.transform(),
WorkspaceRenderElement::Fullscreen(elem) => elem.transform(), WorkspaceRenderElement::Fullscreen(elem) => elem.transform(),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.transform(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.transform(),
WorkspaceRenderElement::Window(elem) => elem.transform(), WorkspaceRenderElement::Window(elem) => elem.transform(),
@ -2067,6 +2072,7 @@ where
) -> DamageSet<i32, smithay::utils::Physical> { ) -> DamageSet<i32, smithay::utils::Physical> {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.damage_since(scale, commit), WorkspaceRenderElement::OverrideRedirect(elem) => elem.damage_since(scale, commit),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.damage_since(scale, commit),
WorkspaceRenderElement::Fullscreen(elem) => elem.damage_since(scale, commit), WorkspaceRenderElement::Fullscreen(elem) => elem.damage_since(scale, commit),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.damage_since(scale, commit), WorkspaceRenderElement::FullscreenPopup(elem) => elem.damage_since(scale, commit),
WorkspaceRenderElement::Window(elem) => elem.damage_since(scale, commit), WorkspaceRenderElement::Window(elem) => elem.damage_since(scale, commit),
@ -2077,6 +2083,7 @@ where
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, smithay::utils::Physical> { fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, smithay::utils::Physical> {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.opaque_regions(scale), WorkspaceRenderElement::OverrideRedirect(elem) => elem.opaque_regions(scale),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.opaque_regions(scale),
WorkspaceRenderElement::Fullscreen(elem) => elem.opaque_regions(scale), WorkspaceRenderElement::Fullscreen(elem) => elem.opaque_regions(scale),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.opaque_regions(scale), WorkspaceRenderElement::FullscreenPopup(elem) => elem.opaque_regions(scale),
WorkspaceRenderElement::Window(elem) => elem.opaque_regions(scale), WorkspaceRenderElement::Window(elem) => elem.opaque_regions(scale),
@ -2087,6 +2094,7 @@ where
fn alpha(&self) -> f32 { fn alpha(&self) -> f32 {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.alpha(), WorkspaceRenderElement::OverrideRedirect(elem) => elem.alpha(),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.alpha(),
WorkspaceRenderElement::Fullscreen(elem) => elem.alpha(), WorkspaceRenderElement::Fullscreen(elem) => elem.alpha(),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.alpha(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.alpha(),
WorkspaceRenderElement::Window(elem) => elem.alpha(), WorkspaceRenderElement::Window(elem) => elem.alpha(),
@ -2097,6 +2105,7 @@ where
fn kind(&self) -> Kind { fn kind(&self) -> Kind {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.kind(), WorkspaceRenderElement::OverrideRedirect(elem) => elem.kind(),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.kind(),
WorkspaceRenderElement::Fullscreen(elem) => elem.kind(), WorkspaceRenderElement::Fullscreen(elem) => elem.kind(),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.kind(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.kind(),
WorkspaceRenderElement::Window(elem) => elem.kind(), WorkspaceRenderElement::Window(elem) => elem.kind(),
@ -2107,6 +2116,7 @@ where
fn is_framebuffer_effect(&self) -> bool { fn is_framebuffer_effect(&self) -> bool {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.is_framebuffer_effect(), WorkspaceRenderElement::OverrideRedirect(elem) => elem.is_framebuffer_effect(),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.is_framebuffer_effect(),
WorkspaceRenderElement::Fullscreen(elem) => elem.is_framebuffer_effect(), WorkspaceRenderElement::Fullscreen(elem) => elem.is_framebuffer_effect(),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.is_framebuffer_effect(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.is_framebuffer_effect(),
WorkspaceRenderElement::Window(elem) => elem.is_framebuffer_effect(), WorkspaceRenderElement::Window(elem) => elem.is_framebuffer_effect(),
@ -2118,8 +2128,7 @@ where
impl<R> RenderElement<R> for WorkspaceRenderElement<R> impl<R> RenderElement<R> for WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
R::Error: FromGlesError,
{ {
fn draw( fn draw(
&self, &self,
@ -2134,6 +2143,9 @@ where
WorkspaceRenderElement::OverrideRedirect(elem) => { WorkspaceRenderElement::OverrideRedirect(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache) elem.draw(frame, src, dst, damage, opaque_regions, cache)
} }
WorkspaceRenderElement::LowerLayerShell(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache)
}
WorkspaceRenderElement::Fullscreen(elem) => { WorkspaceRenderElement::Fullscreen(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions, cache) elem.draw(frame, src, dst, damage, opaque_regions, cache)
} }
@ -2152,7 +2164,7 @@ where
opaque_regions, opaque_regions,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error), .map_err(R::from_gles_error),
} }
} }
@ -2162,6 +2174,7 @@ where
) -> Option<smithay::backend::renderer::element::UnderlyingStorage<'_>> { ) -> Option<smithay::backend::renderer::element::UnderlyingStorage<'_>> {
match self { match self {
WorkspaceRenderElement::OverrideRedirect(elem) => elem.underlying_storage(renderer), WorkspaceRenderElement::OverrideRedirect(elem) => elem.underlying_storage(renderer),
WorkspaceRenderElement::LowerLayerShell(elem) => elem.underlying_storage(renderer),
WorkspaceRenderElement::Fullscreen(elem) => elem.underlying_storage(renderer), WorkspaceRenderElement::Fullscreen(elem) => elem.underlying_storage(renderer),
WorkspaceRenderElement::FullscreenPopup(elem) => elem.underlying_storage(renderer), WorkspaceRenderElement::FullscreenPopup(elem) => elem.underlying_storage(renderer),
WorkspaceRenderElement::Window(elem) => elem.underlying_storage(renderer), WorkspaceRenderElement::Window(elem) => elem.underlying_storage(renderer),
@ -2182,6 +2195,9 @@ where
WorkspaceRenderElement::OverrideRedirect(elem) => { WorkspaceRenderElement::OverrideRedirect(elem) => {
elem.capture_framebuffer(frame, src, dst, cache) elem.capture_framebuffer(frame, src, dst, cache)
} }
WorkspaceRenderElement::LowerLayerShell(elem) => {
elem.capture_framebuffer(frame, src, dst, cache)
}
WorkspaceRenderElement::Fullscreen(elem) => { WorkspaceRenderElement::Fullscreen(elem) => {
elem.capture_framebuffer(frame, src, dst, cache) elem.capture_framebuffer(frame, src, dst, cache)
} }
@ -2199,7 +2215,7 @@ where
dst, dst,
cache, cache,
) )
.map_err(FromGlesError::from_gles_error) .map_err(R::from_gles_error)
} }
} }
} }
@ -2208,7 +2224,7 @@ where
impl<R> From<RescaleRenderElement<CosmicWindowRenderElement<R>>> for WorkspaceRenderElement<R> impl<R> From<RescaleRenderElement<CosmicWindowRenderElement<R>>> for WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: RescaleRenderElement<CosmicWindowRenderElement<R>>) -> Self { fn from(elem: RescaleRenderElement<CosmicWindowRenderElement<R>>) -> Self {
@ -2219,7 +2235,7 @@ where
impl<R> From<CosmicWindowRenderElement<R>> for WorkspaceRenderElement<R> impl<R> From<CosmicWindowRenderElement<R>> for WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: CosmicWindowRenderElement<R>) -> Self { fn from(elem: CosmicWindowRenderElement<R>) -> Self {
@ -2227,21 +2243,32 @@ where
} }
} }
impl<R> From<WaylandSurfaceRenderElement<R>> for WorkspaceRenderElement<R> impl<R> From<SurfaceRenderElement<R>> for WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: WaylandSurfaceRenderElement<R>) -> Self { fn from(elem: SurfaceRenderElement<R>) -> Self {
WorkspaceRenderElement::OverrideRedirect(elem) WorkspaceRenderElement::OverrideRedirect(elem)
} }
} }
impl<R> From<NamespacedElement<SurfaceRenderElement<R>>> for WorkspaceRenderElement<R>
where
R: AsGlowRenderer,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: NamespacedElement<SurfaceRenderElement<R>>) -> Self {
WorkspaceRenderElement::LowerLayerShell(elem)
}
}
impl<R> From<CosmicMappedRenderElement<R>> for WorkspaceRenderElement<R> impl<R> From<CosmicMappedRenderElement<R>> for WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: CosmicMappedRenderElement<R>) -> Self { fn from(elem: CosmicMappedRenderElement<R>) -> Self {
@ -2252,7 +2279,7 @@ where
impl<R> From<TextureRenderElement<GlesTexture>> for WorkspaceRenderElement<R> impl<R> From<TextureRenderElement<GlesTexture>> for WorkspaceRenderElement<R>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: 'static, R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
fn from(elem: TextureRenderElement<GlesTexture>) -> Self { fn from(elem: TextureRenderElement<GlesTexture>) -> Self {

View file

@ -3,15 +3,15 @@ use std::{sync::Mutex, time::Instant};
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic::{ use cosmic::{
Apply, Apply,
iced::{Alignment, Background, Border, Length, alignment::Vertical}, iced::{Alignment, Background, Border, Length, alignment::Vertical, widget as iced_widget},
iced_widget, theme, theme,
widget::{self, icon::Named}, widget::{self, icon::Named},
}; };
use cosmic_comp_config::ZoomMovement; use cosmic_comp_config::ZoomMovement;
use cosmic_config::ConfigSet; use cosmic_config::ConfigSet;
use keyframe::{ease, functions::Linear}; use keyframe::{ease, functions::Linear};
use smithay::{ use smithay::{
backend::renderer::{ImportMem, Renderer, element::AsRenderElements}, backend::renderer::ImportMem,
desktop::space::SpaceElement, desktop::space::SpaceElement,
input::{ input::{
Seat, Seat,
@ -27,14 +27,15 @@ use smithay::{
}, },
}, },
output::Output, output::Output,
utils::{IsAlive, Point, Rectangle, Serial, Size}, utils::{FrameExtents, IsAlive, Point, Rectangle, Serial, Size},
}; };
use tracing::error; use tracing::error;
use crate::{ use crate::{
backend::render::element::AsGlowRenderer,
state::State, state::State,
utils::{ utils::{
iced::{IcedElement, Program}, iced::{IcedElement, IcedRenderElement, Program},
prelude::*, prelude::*,
tween::EasePoint, tween::EasePoint,
}, },
@ -71,45 +72,17 @@ impl OutputZoomState {
increment: u32, increment: u32,
movement: ZoomMovement, movement: ZoomMovement,
loop_handle: LoopHandle<'static, State>, loop_handle: LoopHandle<'static, State>,
theme: cosmic::Theme, mut theme: cosmic::Theme,
) -> OutputZoomState { ) -> OutputZoomState {
theme.transparent = theme.cosmic().frosted_system_interface;
let cursor_position = seat.get_pointer().unwrap().current_location().as_global(); let cursor_position = seat.get_pointer().unwrap().current_location().as_global();
let focal_point = cursor_position.to_local(output);
let output_geometry = output.geometry().to_f64(); let output_geometry = output.geometry().to_f64();
let focal_point = if output_geometry.contains(cursor_position) {
match movement {
ZoomMovement::Continuously | ZoomMovement::OnEdge => {
cursor_position.to_local(output)
}
ZoomMovement::Centered => {
let mut zoomed_output_geometry = output.geometry().to_f64().downscale(level);
zoomed_output_geometry.loc =
cursor_position - zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(output)
.upscale(level)
.to_global(output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x,
(output_geometry.loc.x + output_geometry.size.w).next_down(),
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y,
(output_geometry.loc.y + output_geometry.size.h).next_down(),
);
focal_point.to_local(output)
}
}
} else {
(output_geometry.size.w / 2., output_geometry.size.h / 2.).into()
};
let program = ZoomProgram::new(level, movement, increment); let program = ZoomProgram::new(level, movement, increment);
let element = IcedElement::new(program, Size::default(), loop_handle, theme); let element = IcedElement::new(program, Size::default(), loop_handle, theme);
let mut size = element.minimum_size(); let mut size = element.minimum_size();
size.w = (size.w + 32/*TODO: figure out why iced is calculating too little*/) size.w = size.w.min(output_geometry.size.w.round() as i32);
.min(output_geometry.size.w.round() as i32);
element.set_activate(true); element.set_activate(true);
element.resize(size); element.resize(size);
element.output_enter(output, Rectangle::new(Point::from((0, 0)), size)); element.output_enter(output, Rectangle::new(Point::from((0, 0)), size));
@ -192,10 +165,13 @@ impl OutputZoomState {
}); });
} }
fn render<R, C>(&mut self, renderer: &mut R, output: &Output) -> Vec<C> fn render<R>(
where &mut self,
C: From<<IcedElement<ZoomProgram> as AsRenderElements<R>>::RenderElement>, renderer: &mut R,
R: Renderer + ImportMem, output: &Output,
push: &mut dyn FnMut(IcedRenderElement<R>),
) where
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
{ {
let size = self.element.current_size().to_f64(); let size = self.element.current_size().to_f64();
@ -208,8 +184,17 @@ impl OutputZoomState {
.to_physical(scale.fractional_scale()) .to_physical(scale.fractional_scale())
.to_i32_round(); .to_i32_round();
self.element self.element.push_render_elements(
.render_elements(renderer, location, scale.fractional_scale().into(), 1.0) renderer,
location,
scale.fractional_scale().into(),
1.0,
self.element
.with_theme(|theme| theme.cosmic().radius_s())
.map(|x| x.round() as u8),
push,
None,
)
} }
} }
@ -256,86 +241,101 @@ impl ZoomState {
&mut self, &mut self,
output: &Output, output: &Output,
cursor_position: Point<f64, Global>, cursor_position: Point<f64, Global>,
original_position: Point<f64, Global>, _original_position: Point<f64, Global>,
movement: ZoomMovement, movement: ZoomMovement,
) { ) {
let output_geometry = output.geometry().to_f64(); let output_geometry = output.geometry().to_f64().to_local(output);
let mut zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64(); let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64().to_local(output);
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap(); let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
let mut output_state_ref = output_state.lock().unwrap(); let mut output_state_ref = output_state.lock().unwrap();
// animate movement type changes let level = output_state_ref.current_level();
if self.movement != movement {
let is_level_change = output_state_ref
.previous_level
.is_some_and(|prev| prev.0 != level);
// animate level and movement type changes
if is_level_change || self.movement != movement {
output_state_ref.previous_point = Some((output_state_ref.focal_point, Instant::now())); output_state_ref.previous_point = Some((output_state_ref.focal_point, Instant::now()));
self.movement = movement; self.movement = movement;
} }
let cursor_position = cursor_position.to_local(output); let cursor_position = cursor_position.to_local(output);
match movement { match movement {
ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position, ZoomMovement::Continuously => {
ZoomMovement::OnEdge => { // Focal point is the cursor position
if !zoomed_output_geometry output_state_ref.focal_point = cursor_position;
.overlaps_or_touches(Rectangle::new(original_position, Size::from((16., 16.))))
{
zoomed_output_geometry.loc = cursor_position.to_global(output)
- zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(output)
.upscale(output_state_ref.level)
.to_global(output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x,
output_geometry.loc.x + output_geometry.size.w - 1.,
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y,
output_geometry.loc.y + output_geometry.size.h - 1.,
);
output_state_ref.previous_point =
Some((output_state_ref.focal_point, Instant::now()));
output_state_ref.focal_point = focal_point.to_local(output);
} else if !zoomed_output_geometry.contains(cursor_position.to_global(output)) {
let mut diff = output_state_ref.focal_point.to_global(output)
+ (cursor_position.to_global(output) - original_position)
.upscale(output_state_ref.level);
diff.x = diff.x.clamp(
output_geometry.loc.x,
(output_geometry.loc.x + output_geometry.size.w).next_down(),
);
diff.y = diff.y.clamp(
output_geometry.loc.y,
(output_geometry.loc.y + output_geometry.size.h).next_down(),
);
diff -= output_state_ref.focal_point.to_global(output);
output_state_ref.focal_point += diff.as_logical().as_local();
}
} }
ZoomMovement::Centered => { ZoomMovement::OnEdge => {
zoomed_output_geometry.loc = cursor_position.to_global(output) if is_level_change {
- zoomed_output_geometry.size.downscale(2.).to_point(); // Ensure the cursor doesn't disappear off screen
output_state_ref.focal_point = cursor_position;
return;
}
let mut focal_point = zoomed_output_geometry // Compute small margin relative to zoomed output to keep cursor within
.loc // (can be user-configurable in the future)
.to_local(output) let margin_size = zoomed_output_geometry.size.h * 0.02;
.upscale( let margins = FrameExtents::new(margin_size, margin_size, margin_size, margin_size);
(output_geometry.size.w let inner_rect = zoomed_output_geometry - margins;
/ (output_geometry.size.w - zoomed_output_geometry.size.w)
.max(f64::EPSILON)) if inner_rect.contains(cursor_position) {
.max(1.), // Do not move if cursor within margins
) return;
.to_global(output); }
// Compute dx and dy to move the zoomed output based on cursor distance outside margin(s)
let dx = if cursor_position.x < inner_rect.loc.x {
cursor_position.x - inner_rect.loc.x
} else if cursor_position.x > inner_rect.loc.x + inner_rect.size.w {
cursor_position.x - (inner_rect.loc.x + inner_rect.size.w)
} else {
0.0
};
let dy = if cursor_position.y < inner_rect.loc.y {
cursor_position.y - inner_rect.loc.y
} else if cursor_position.y > inner_rect.loc.y + inner_rect.size.h {
cursor_position.y - (inner_rect.loc.y + inner_rect.size.h)
} else {
0.0
};
let mut focal_point = output_state_ref.focal_point + Point::new(dx, dy);
// Clamp the final focal point to output geometry
focal_point.x = focal_point.x.clamp( focal_point.x = focal_point.x.clamp(
output_geometry.loc.x, output_geometry.loc.x,
output_geometry.loc.x + output_geometry.size.w - 1., output_geometry.loc.x + output_geometry.size.w - 1.0,
); );
focal_point.y = focal_point.y.clamp( focal_point.y = focal_point.y.clamp(
output_geometry.loc.y, output_geometry.loc.y,
output_geometry.loc.y + output_geometry.size.h - 1., output_geometry.loc.y + output_geometry.size.h - 1.0,
); );
output_state_ref.focal_point = focal_point.to_local(output);
output_state_ref.focal_point = focal_point;
}
ZoomMovement::Centered => {
let center = (output_geometry.size / 2.).to_point();
if level == 1.0 {
// Prevent focal point jumping to top-left corner (0, 0) on zoom out
output_state_ref.focal_point = center;
return;
}
// Compute translation to keep cursor at center of screen
let mut tx = center.x - cursor_position.x * level;
let mut ty = center.y - cursor_position.y * level;
// Clamp translation to keep viewport within screen bounds
tx = tx.clamp(output_geometry.size.w * (1.0 - level), 0.0);
ty = ty.clamp(output_geometry.size.h * (1.0 - level), 0.0);
// Convert translation back to focal point: T = F * (1 - level)
output_state_ref.focal_point =
Point::from((tx / (1.0 - level), ty / (1.0 - level)));
} }
} }
} }
@ -382,14 +382,13 @@ impl ZoomState {
None None
} }
pub fn render<R, C>(renderer: &mut R, output: &Output) -> Vec<C> pub fn render<R>(renderer: &mut R, output: &Output, push: &mut dyn FnMut(IcedRenderElement<R>))
where where
C: From<<IcedElement<ZoomProgram> as AsRenderElements<R>>::RenderElement>, R: AsGlowRenderer + ImportMem,
R: Renderer + ImportMem,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
{ {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap(); let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().render(renderer, output) output_state.lock().unwrap().render(renderer, output, push)
} }
} }
@ -506,6 +505,7 @@ impl Program for ZoomProgram {
.class(theme::Container::custom(|theme| { .class(theme::Container::custom(|theme| {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let component = &cosmic.background(false).component; let component = &cosmic.background(false).component;
let component = &cosmic.background(false).component;
iced_widget::container::Style { iced_widget::container::Style {
snap: true, snap: true,
icon_color: Some(component.on.into()), icon_color: Some(component.on.into()),
@ -581,6 +581,8 @@ impl Program for ZoomProgram {
let level = output_state_ref.level; let level = output_state_ref.level;
std::mem::drop(output_state_ref); std::mem::drop(output_state_ref);
let mut theme = state.common.theme.clone();
theme.transparent = theme.cosmic().frosted_system_interface;
let grab = MenuGrab::new( let grab = MenuGrab::new(
start_data, start_data,
&seat, &seat,
@ -693,7 +695,7 @@ impl Program for ZoomProgram {
), ),
Some(level.min(4.)), Some(level.min(4.)),
state.common.event_loop_handle.clone(), state.common.event_loop_handle.clone(),
state.common.theme.clone(), theme,
); );
std::mem::drop(shell); std::mem::drop(shell);
@ -746,6 +748,8 @@ impl Program for ZoomProgram {
let level = output_state_ref.level; let level = output_state_ref.level;
std::mem::drop(output_state_ref); std::mem::drop(output_state_ref);
let mut theme = state.common.theme.clone();
theme.transparent = theme.cosmic().frosted_system_interface;
let grab = MenuGrab::new( let grab = MenuGrab::new(
start_data, start_data,
&seat, &seat,
@ -778,7 +782,7 @@ impl Program for ZoomProgram {
MenuAlignment::PREFER_CENTERED, MenuAlignment::PREFER_CENTERED,
Some(level.min(4.)), Some(level.min(4.)),
state.common.event_loop_handle.clone(), state.common.event_loop_handle.clone(),
state.common.theme.clone(), theme,
); );
std::mem::drop(shell); std::mem::drop(shell);

View file

@ -19,6 +19,7 @@ use crate::{
corner_radius::CornerRadiusState, corner_radius::CornerRadiusState,
drm::WlDrmState, drm::WlDrmState,
image_capture_source::CosmicImageCaptureSourceState, image_capture_source::CosmicImageCaptureSourceState,
keyboard_layout::KeyboardLayoutState,
output_configuration::OutputConfigurationState, output_configuration::OutputConfigurationState,
output_power::OutputPowerState, output_power::OutputPowerState,
overlap_notify::OverlapNotifyState, overlap_notify::OverlapNotifyState,
@ -72,6 +73,7 @@ use smithay::{
utils::{Clock, Monotonic, Point}, utils::{Clock, Monotonic, Point},
wayland::{ wayland::{
alpha_modifier::AlphaModifierState, alpha_modifier::AlphaModifierState,
background_effect::BackgroundEffectState,
compositor::{CompositorClientState, CompositorState, SurfaceData}, compositor::{CompositorClientState, CompositorState, SurfaceData},
cursor_shape::CursorShapeManagerState, cursor_shape::CursorShapeManagerState,
dmabuf::{DmabufFeedback, DmabufGlobal, DmabufState}, dmabuf::{DmabufFeedback, DmabufGlobal, DmabufState},
@ -125,7 +127,7 @@ use std::{
cmp::min, cmp::min,
collections::HashSet, collections::HashSet,
ffi::OsString, ffi::OsString,
process::Child, process::{Child, Command},
sync::{Arc, LazyLock, Once, atomic::AtomicBool}, sync::{Arc, LazyLock, Once, atomic::AtomicBool},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -223,6 +225,7 @@ pub struct State {
pub common: Common, pub common: Common,
pub ready: Once, pub ready: Once,
pub last_refresh: LastRefresh, pub last_refresh: LastRefresh,
pub kiosk_command: Option<Command>,
} }
smithay::delegate_dispatch2!(State); smithay::delegate_dispatch2!(State);
@ -279,6 +282,8 @@ pub struct Common {
pub overlap_notify_state: OverlapNotifyState, pub overlap_notify_state: OverlapNotifyState,
pub a11y_state: A11yState, pub a11y_state: A11yState,
pub dbus_state: DBusState, pub dbus_state: DBusState,
pub keyboard_layout_state: KeyboardLayoutState,
pub background_effect_state: BackgroundEffectState,
// shell-related wayland state // shell-related wayland state
pub xdg_shell_state: XdgShellState, pub xdg_shell_state: XdgShellState,
@ -629,6 +634,7 @@ impl State {
handle: LoopHandle<'static, State>, handle: LoopHandle<'static, State>,
signal: LoopSignal, signal: LoopSignal,
with_xwayland: bool, with_xwayland: bool,
kiosk_command: Option<Command>,
) -> State { ) -> State {
let requested_languages = DesktopLanguageRequester::requested_languages(); let requested_languages = DesktopLanguageRequester::requested_languages();
i18n_embed::select(&*LANG_LOADER, &Localizations, &requested_languages) i18n_embed::select(&*LANG_LOADER, &Localizations, &requested_languages)
@ -682,6 +688,9 @@ impl State {
AlphaModifierState::new::<Self>(dh); AlphaModifierState::new::<Self>(dh);
SinglePixelBufferState::new::<Self>(dh); SinglePixelBufferState::new::<Self>(dh);
FixesState::new::<Self>(dh); FixesState::new::<Self>(dh);
let keyboard_layout_state = KeyboardLayoutState::new::<State, _>(dh, client_not_sandboxed);
let background_effect_state = BackgroundEffectState::new::<Self>(dh);
let idle_notifier_state = IdleNotifierState::<Self>::new(dh, handle.clone()); let idle_notifier_state = IdleNotifierState::<Self>::new(dh, handle.clone());
let idle_inhibit_manager_state = IdleInhibitManagerState::new::<State>(dh); let idle_inhibit_manager_state = IdleInhibitManagerState::new::<State>(dh);
@ -786,12 +795,14 @@ impl State {
xdg_activation_state, xdg_activation_state,
xdg_foreign_state, xdg_foreign_state,
workspace_state, workspace_state,
background_effect_state,
a11y_state, a11y_state,
xwayland_scale: None, xwayland_scale: None,
xwayland_state: None, xwayland_state: None,
xwayland_shell_state, xwayland_shell_state,
pointer_focus_state: None, pointer_focus_state: None,
dbus_state, dbus_state,
keyboard_layout_state,
#[cfg(feature = "logind")] #[cfg(feature = "logind")]
inhibit_lid_fd: None, inhibit_lid_fd: None,
@ -801,6 +812,7 @@ impl State {
backend: BackendData::Unset, backend: BackendData::Unset,
ready: Once::new(), ready: Once::new(),
last_refresh: LastRefresh::None, last_refresh: LastRefresh::None,
kiosk_command,
} }
} }
@ -930,19 +942,22 @@ impl Common {
render_element_states: &RenderElementStates, render_element_states: &RenderElementStates,
) { ) {
let shell = self.shell.read(); let shell = self.shell.read();
let processor = |surface: &WlSurface, states: &SurfaceData| { let processor = |namespace: Option<usize>| {
let primary_scanout_output = update_surface_primary_scanout_output( move |surface: &WlSurface, states: &SurfaceData| {
surface, let primary_scanout_output = update_surface_primary_scanout_output(
output, surface,
states, output,
None, states,
render_element_states, namespace,
primary_scanout_output_compare, render_element_states,
); primary_scanout_output_compare,
if let Some(output) = primary_scanout_output { );
with_fractional_scale(states, |fraction_scale| { if let Some(output) = primary_scanout_output {
fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); with_fractional_scale(states, |fraction_scale| {
}); fraction_scale
.set_preferred_scale(output.current_scale().fractional_scale());
});
}
} }
}; };
@ -950,7 +965,7 @@ impl Common {
if let Some(session_lock) = shell.session_lock.as_ref() if let Some(session_lock) = shell.session_lock.as_ref()
&& let Some(lock_surface) = session_lock.surfaces.get(output) && let Some(lock_surface) = session_lock.surfaces.get(output)
{ {
with_surfaces_surface_tree(lock_surface.wl_surface(), processor) with_surfaces_surface_tree(lock_surface.wl_surface(), processor(None))
} }
for seat in shell for seat in shell
@ -962,7 +977,7 @@ impl Common {
// cursor ... // cursor ...
if let CursorImageStatus::Surface(wl_surface) = cursor_status { if let CursorImageStatus::Surface(wl_surface) = cursor_status {
with_surfaces_surface_tree(&wl_surface, processor); with_surfaces_surface_tree(&wl_surface, processor(None));
} }
// grabs // grabs
@ -970,12 +985,12 @@ impl Common {
&& let Some(grab_state) = move_grab.lock().unwrap().as_ref() && let Some(grab_state) = move_grab.lock().unwrap().as_ref()
{ {
for (window, _) in grab_state.element().windows() { for (window, _) in grab_state.element().windows() {
window.with_surfaces(processor); window.with_surfaces(processor(None));
} }
} }
if let Some(icon) = get_dnd_icon(seat) { if let Some(icon) = get_dnd_icon(seat) {
with_surfaces_surface_tree(&icon.surface, processor); with_surfaces_surface_tree(&icon.surface, processor(None));
} }
} }
@ -983,7 +998,7 @@ impl Common {
for set in shell.workspaces.sets.values() { for set in shell.workspaces.sets.values() {
set.sticky_layer.mapped().for_each(|mapped| { set.sticky_layer.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
window.with_surfaces(processor); window.with_surfaces(processor(None));
} }
}); });
} }
@ -991,16 +1006,16 @@ impl Common {
// normal windows // normal windows
for space in shell.workspaces.spaces() { for space in shell.workspaces.spaces() {
if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) { if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
fs.surface.with_surfaces(processor); fs.surface.with_surfaces(processor(None));
} }
space.mapped().for_each(|mapped| { space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
window.with_surfaces(processor); window.with_surfaces(processor(None));
} }
}); });
space.minimized_windows.iter().for_each(|m| { space.minimized_windows.iter().for_each(|m| {
for window in m.windows() { for window in m.windows() {
window.with_surfaces(processor); window.with_surfaces(processor(None));
} }
}) })
} }
@ -1008,15 +1023,16 @@ impl Common {
// OR windows // OR windows
shell.override_redirect_windows.iter().for_each(|or| { shell.override_redirect_windows.iter().for_each(|or| {
if let Some(wl_surface) = or.wl_surface() { if let Some(wl_surface) = or.wl_surface() {
with_surfaces_surface_tree(&wl_surface, processor); with_surfaces_surface_tree(&wl_surface, processor(None));
} }
}); });
// layer surfaces // layer surfaces
for o in shell.outputs() { for o in shell.outputs() {
let namespace = shell.workspaces.active_num(o).1;
let map = smithay::desktop::layer_map_for_output(o); let map = smithay::desktop::layer_map_for_output(o);
for layer_surface in map.layers() { for layer_surface in map.layers() {
layer_surface.with_surfaces(processor); layer_surface.with_surfaces(processor(Some(namespace)));
} }
} }
} }

View file

@ -27,9 +27,9 @@ pub fn ready(common: &Common) {
), ),
Err(err) => error!(?err, "Failed to run systemctl although booted with systemd",), Err(err) => error!(?err, "Failed to run systemctl although booted with systemd",),
}; };
}
if let Err(err) = notify(false, &[NotifyState::Ready]) { if let Err(err) = notify(false, &[NotifyState::Ready]) {
error!(?err, "Failed to notify systemd"); error!(?err, "Failed to notify systemd");
}
} }
} }

View file

@ -12,15 +12,15 @@ use cosmic::{
iced::{ iced::{
Limits, Point as IcedPoint, Size as IcedSize, Task, Limits, Point as IcedPoint, Size as IcedSize, Task,
advanced::{graphics::text::font_system, widget::Tree}, advanced::{graphics::text::font_system, widget::Tree},
core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style},
event::Event, event::Event,
futures::{FutureExt, StreamExt}, futures::{FutureExt, StreamExt},
keyboard::{Event as KeyboardEvent, Modifiers as IcedModifiers}, keyboard::{Event as KeyboardEvent, Modifiers as IcedModifiers},
mouse::{Button as MouseButton, Cursor, Event as MouseEvent, ScrollDelta}, mouse::{Button as MouseButton, Cursor, Event as MouseEvent, ScrollDelta},
runtime::{Action, task::into_stream},
touch::{Event as TouchEvent, Finger}, touch::{Event as TouchEvent, Finger},
window::Event as WindowEvent, window::Event as WindowEvent,
}, },
iced_core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style},
iced_runtime::{Action, task::into_stream},
}; };
use iced_tiny_skia::{ use iced_tiny_skia::{
Layer, Layer,
@ -33,9 +33,9 @@ use smithay::{
allocator::Fourcc, allocator::Fourcc,
input::{ButtonState, KeyState}, input::{ButtonState, KeyState},
renderer::{ renderer::{
ImportMem, Renderer, ImportMem,
element::{ element::{
AsRenderElements, Kind, Kind,
memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement}, memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
}, },
}, },
@ -57,13 +57,20 @@ use smithay::{
}, },
output::Output, output::Output,
reexports::calloop::{self, LoopHandle, RegistrationToken, futures::Scheduler}, reexports::calloop::{self, LoopHandle, RegistrationToken, futures::Scheduler},
render_elements,
utils::{ utils::{
Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size,
Transform, Transform,
}, },
}; };
use crate::utils::iced::state::State; use crate::{
backend::render::{
element::AsGlowRenderer,
wayland::blur_effect::{BlurElement, BlurState},
},
utils::iced::state::State,
};
static ID: LazyLock<Id> = LazyLock::new(|| Id::new("Program")); static ID: LazyLock<Id> = LazyLock::new(|| Id::new("Program"));
@ -173,6 +180,7 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
outputs: HashSet<Output>, outputs: HashSet<Output>,
buffers: HashMap<OrderedFloat<f64>, (MemoryRenderBuffer, Option<(Vec<Layer>, Color)>)>, buffers: HashMap<OrderedFloat<f64>, (MemoryRenderBuffer, Option<(Vec<Layer>, Color)>)>,
pending_realloc: bool, pending_realloc: bool,
blur: BlurState,
// state // state
size: Size<i32, Logical>, size: Size<i32, Logical>,
@ -225,6 +233,7 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
outputs: self.outputs.clone(), outputs: self.outputs.clone(),
buffers: self.buffers.clone(), buffers: self.buffers.clone(),
pending_realloc: self.pending_realloc, pending_realloc: self.pending_realloc,
blur: BlurState::default(),
size: self.size, size: self.size,
last_seat: self.last_seat.clone(), last_seat: self.last_seat.clone(),
cursor_pos: self.cursor_pos, cursor_pos: self.cursor_pos,
@ -310,6 +319,7 @@ impl<P: Program + Send + 'static> IcedElement<P> {
outputs: HashSet::new(), outputs: HashSet::new(),
buffers: HashMap::new(), buffers: HashMap::new(),
pending_realloc: false, pending_realloc: false,
blur: BlurState::default(),
size, size,
cursor_pos: None, cursor_pos: None,
last_seat, last_seat,
@ -342,7 +352,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
let node = element let node = element
.as_widget_mut() .as_widget_mut()
.layout( .layout(
// TODO Avoid creating a new tree here?
&mut tree, &mut tree,
&internal.renderer, &internal.renderer,
&Limits::new(IcedSize::ZERO, IcedSize::INFINITE) &Limits::new(IcedSize::ZERO, IcedSize::INFINITE)
@ -386,6 +395,11 @@ impl<P: Program + Send + 'static> IcedElement<P> {
self.0.lock().unwrap().update(true); self.0.lock().unwrap().update(true);
} }
pub fn with_theme<R: 'static>(&self, f: impl FnOnce(&Theme) -> R) -> R {
let guard = self.0.lock().unwrap();
f(&guard.theme)
}
pub fn set_theme(&self, theme: cosmic::Theme) { pub fn set_theme(&self, theme: cosmic::Theme) {
let mut guard = self.0.lock().unwrap(); let mut guard = self.0.lock().unwrap();
guard.theme = theme.clone(); guard.theme = theme.clone();
@ -910,21 +924,20 @@ impl<P: Program + Send + 'static> SpaceElement for IcedElement<P> {
} }
} }
impl<P, R> AsRenderElements<R> for IcedElement<P> impl<P: Program + Send + 'static> IcedElement<P> {
where pub fn push_render_elements<R>(
P: Program + Send + 'static,
R: Renderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
type RenderElement = MemoryRenderBufferRenderElement<R>;
fn render_elements<C: From<Self::RenderElement>>(
&self, &self,
renderer: &mut R, renderer: &mut R,
location: Point<i32, Physical>, location: Point<i32, Physical>,
mut scale: Scale<f64>, mut scale: Scale<f64>,
alpha: f32, alpha: f32,
) -> Vec<C> { mut radii: [u8; 4],
push_above: &mut dyn FnMut(IcedRenderElement<R>),
push_below: Option<&mut dyn FnMut(IcedRenderElement<R>)>,
) where
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
let mut internal = self.0.lock().unwrap(); let mut internal = self.0.lock().unwrap();
// makes partial borrows easier // makes partial borrows easier
let internal_ref = &mut *internal; let internal_ref = &mut *internal;
@ -1055,11 +1068,51 @@ where
Kind::Unspecified, Kind::Unspecified,
) { ) {
Ok(buffer) => { Ok(buffer) => {
return vec![C::from(buffer)]; push_above(buffer.into());
} }
Err(err) => tracing::warn!("What? {:?}", err), Err(err) => tracing::warn!("What? {:?}", err),
} }
if internal_ref.theme.transparent {
for radius in radii.iter_mut() {
*radius = ((*radius as f64) * internal_ref.additional_scale).round() as u8;
}
match BlurElement::from_state(
renderer,
&mut internal_ref.blur,
Rectangle::new(
location
.to_f64()
.to_logical(scale)
.upscale(internal_ref.additional_scale),
internal_ref
.size
.to_f64()
.upscale(internal_ref.additional_scale)
.to_i32_round(),
),
scale.x,
radii,
(internal_ref.theme.cosmic().frosted as u8 + 1) as usize,
) {
Ok(Some(elem)) => {
if let Some(push_below) = push_below {
push_below(elem.into())
} else {
push_above(elem.into())
}
}
Ok(None) => {}
Err(err) => tracing::warn!("Blur elem error: {:?}", err),
}
}
} }
Vec::new()
} }
} }
render_elements! {
pub IcedRenderElement<R> where R: ImportMem + AsGlowRenderer, R::TextureId: Send;
UI=MemoryRenderBufferRenderElement<R>,
Blur=BlurElement,
}

View file

@ -1,12 +1,16 @@
use super::IcedProgram as Program; use super::IcedProgram as Program;
use cosmic::iced::core::event::{self, Event}; use cosmic::iced::{
use cosmic::iced::core::mouse; core::{
use cosmic::iced::core::renderer; self as iced_core, Clipboard, Size,
use cosmic::iced::core::widget::operation::{self, Operation}; event::{self, Event},
use cosmic::iced::core::{Clipboard, Size}; mouse, renderer,
use cosmic::iced_core; widget::operation::{self, Operation},
use cosmic::iced_runtime::Task; },
use cosmic::iced_runtime::user_interface::{self, UserInterface}; runtime::{
Task,
user_interface::{self, UserInterface},
},
};
/// The execution state of a [`Program`]. It leverages caching, event /// The execution state of a [`Program`]. It leverages caching, event
/// processing, and rendering primitive storage. /// processing, and rendering primitive storage.
@ -128,7 +132,7 @@ where
messages.append(&mut self.queued_messages); messages.append(&mut self.queued_messages);
let task = if messages.is_empty() { let task = if messages.is_empty() {
if let cosmic::iced_runtime::user_interface::State::Updated { if let cosmic::iced::runtime::user_interface::State::Updated {
mouse_interaction, .. mouse_interaction, ..
} = state } = state
{ {
@ -154,7 +158,7 @@ where
let mut user_interface = let mut user_interface =
build_user_interface(id, &mut self.program, temp_cache, renderer, bounds); build_user_interface(id, &mut self.program, temp_cache, renderer, bounds);
if let cosmic::iced_runtime::user_interface::State::Updated { if let cosmic::iced::runtime::user_interface::State::Updated {
mouse_interaction, .. mouse_interaction, ..
} = state } = state
{ {

View file

@ -3,9 +3,7 @@ use smithay::{
backend::{ backend::{
allocator::Fourcc, allocator::Fourcc,
renderer::{ renderer::{
ExportMem, ImportAll, Offscreen, Renderer, ExportMem, ImportAll, Offscreen, Renderer, damage::OutputDamageTracker,
damage::OutputDamageTracker,
element::{AsRenderElements, surface::WaylandSurfaceRenderElement},
gles::GlesRenderbuffer, gles::GlesRenderbuffer,
}, },
}, },
@ -16,7 +14,7 @@ use smithay::{
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
backend::render::RendererRef, backend::render::{RendererRef, element::AsGlowRenderer},
shell::element::CosmicSurface, shell::element::CosmicSurface,
state::{State, advertised_node_for_surface}, state::{State, advertised_node_for_surface},
}; };
@ -24,17 +22,24 @@ use crate::{
pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) {
fn render_window<R>(renderer: &mut R, window: &CosmicSurface) -> anyhow::Result<()> fn render_window<R>(renderer: &mut R, window: &CosmicSurface) -> anyhow::Result<()>
where where
R: Renderer + ImportAll + Offscreen<GlesRenderbuffer> + ExportMem, R: Renderer + ImportAll + Offscreen<GlesRenderbuffer> + ExportMem + AsGlowRenderer,
R::TextureId: Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: Send + Sync + 'static, R::Error: Send + Sync + 'static,
{ {
let bbox = bbox_from_surface_tree(&window.wl_surface().unwrap(), (0, 0)); let bbox = bbox_from_surface_tree(&window.wl_surface().unwrap(), (0, 0));
let elements = AsRenderElements::<R>::render_elements::<WaylandSurfaceRenderElement<R>>( let mut elements = Vec::new();
window, window.push_render_elements(
renderer, renderer,
(-bbox.loc.x, -bbox.loc.y).into(), (-bbox.loc.x, -bbox.loc.y).into(),
Scale::from(1.0), Scale::from(1.0),
1.0, 1.0,
None,
None,
false,
[0; 4],
0,
&mut |elem| elements.push(elem),
None,
); );
// TODO: 10-bit // TODO: 10-bit

View file

@ -0,0 +1,59 @@
use smithay::{
reexports::wayland_server::{DisplayHandle, protocol::wl_surface::WlSurface},
utils::{Logical, Rectangle},
wayland::{
background_effect::{Capability, ExtBackgroundEffectHandler},
compositor::{Cacheable, RectangleKind, RegionAttributes, with_states},
},
};
use crate::state::State;
#[derive(Debug, Clone, Default)]
pub struct ComputedBlurRegionCachedState {
/// Region of the surface that will have its background blurred.
pub blur_region: Option<Vec<Rectangle<i32, Logical>>>,
}
impl Cacheable for ComputedBlurRegionCachedState {
fn commit(&mut self, _dh: &DisplayHandle) -> Self {
self.clone()
}
fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
*into = self;
}
}
impl ExtBackgroundEffectHandler for State {
fn capabilities(&self) -> Capability {
Capability::Blur
}
fn set_blur_region(&mut self, surface: WlSurface, region: RegionAttributes) {
with_states(&surface, |states| {
let mut blur_state = states.cached_state.get::<ComputedBlurRegionCachedState>();
blur_state.pending().blur_region = Some({
let (added, subtracted) = region
.rects
.iter()
.cloned()
.partition::<Vec<_>, _>(|(op, _)| matches!(op, RectangleKind::Add));
let added = added.into_iter().map(|(_, rect)| rect).collect::<Vec<_>>();
Rectangle::subtract_rects_many_in_place(
added,
subtracted.into_iter().map(|(_, rect)| rect),
)
})
})
}
fn unset_blur_region(&mut self, surface: WlSurface) {
with_states(&surface, |states| {
let mut blur_state = states.cached_state.get::<ComputedBlurRegionCachedState>();
blur_state.pending().blur_region.take();
})
}
}

View file

@ -314,8 +314,7 @@ impl CompositorHandler for State {
.then(|| state.element()) .then(|| state.element())
}); });
if let Some(window) = moved_window { if let Some(window) = moved_window {
if window.is_stack() { if let Some(stack) = window.stack_ref() {
let stack = window.stack_ref().unwrap();
if let Some(i) = stack.surfaces().position(|s| { if let Some(i) = stack.surfaces().position(|s| {
s.wl_surface() s.wl_surface()
.as_deref() .as_deref()

View file

@ -1,7 +1,9 @@
use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1; use smithay::utils::{Logical, Rectangle, Size};
use smithay::wayland::compositor::SurfaceData;
use crate::wayland::protocols::corner_radius::{ use crate::wayland::protocols::corner_radius::{
CornerRadiusData, CornerRadiusHandler, CornerRadiusState, delegate_corner_radius, CacheableCorners, CacheablePadding, CornerRadiusData, CornerRadiusHandler, CornerRadiusState,
delegate_corner_radius,
}; };
use crate::state::State; use crate::state::State;
@ -11,39 +13,63 @@ impl CornerRadiusHandler for State {
&mut self.common.corner_radius_state &mut self.common.corner_radius_state
} }
fn set_corner_radius( fn set_corner_radius(&mut self, _data: &CornerRadiusData) {}
&mut self,
_: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
data: &CornerRadiusData,
) {
if force_redraw(self, data).is_none() {
tracing::warn!("Failed to force redraw for corner radius change.");
}
}
fn unset_corner_radius( fn unset_corner_radius(&mut self, _data: &CornerRadiusData) {}
&mut self,
_: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, fn set_padding(&mut self, _data: &CornerRadiusData) {}
data: &CornerRadiusData,
) { fn unset_padding(&mut self, _data: &CornerRadiusData) {}
if force_redraw(self, data).is_none() {
tracing::warn!("Failed to force redraw for corner radius reset.");
}
}
} }
fn force_redraw(state: &mut State, data: &CornerRadiusData) -> Option<()> { pub fn surface_corners(states: &SurfaceData, size: Size<i32, Logical>) -> Option<[u8; 4]> {
let guard = data.lock().unwrap(); let mut guard = states.cached_state.get::<CacheableCorners>();
let toplevel = guard.toplevel.upgrade().ok()?; // guard against corner radius being too large, potentially disconnecting the outline
let half_min_dim = u8::try_from(size.w.min(size.h) / 2).unwrap_or(u8::MAX);
let corners = guard.current().0?;
let surface = state.common.xdg_shell_state.get_toplevel(&toplevel)?; Some([
u8::try_from(corners.top_left)
.unwrap_or(u8::MAX)
.min(half_min_dim),
u8::try_from(corners.top_right)
.unwrap_or(u8::MAX)
.min(half_min_dim),
u8::try_from(corners.bottom_right)
.unwrap_or(u8::MAX)
.min(half_min_dim),
u8::try_from(corners.bottom_left)
.unwrap_or(u8::MAX)
.min(half_min_dim),
])
}
let guard = state.common.shell.read(); pub fn surface_padding(states: &SurfaceData, size: Size<i32, Logical>) -> Option<[i32; 4]> {
let output = guard.visible_output_for_surface(surface.wl_surface())?; let mut guard = states.cached_state.get::<CacheablePadding>();
state.backend.schedule_render(output); let padding = guard.current().0?;
Some(())
// guard against padding being too large
Some([
padding.top.min(size.h / 2),
padding.right.min(size.w / 2),
padding.bottom.min(size.h / 2),
padding.left.min(size.w / 2),
])
}
pub fn pad_rect(
mut rect: Rectangle<i32, Logical>,
padding: &[i32; 4],
) -> Option<Rectangle<i32, Logical>> {
rect.size.h = rect.size.h.checked_sub(padding[0])?;
rect.loc.x += padding[3];
rect.size.w = rect.size.w.checked_sub(padding[1])?;
rect.size.h = rect.size.h.checked_sub(padding[2])?;
rect.size.w = rect.size.w.checked_sub(padding[3])?;
rect.loc.y += padding[0];
Some(rect)
} }
delegate_corner_radius!(State); delegate_corner_radius!(State);

View file

@ -14,6 +14,7 @@ use smithay::{
}, },
}, },
desktop::space::SpaceElement, desktop::space::SpaceElement,
input::{Seat, pointer::PointerHandle},
output::Output, output::Output,
reexports::wayland_server::protocol::{wl_pointer::WlPointer, wl_shm::Format as ShmFormat}, reexports::wayland_server::protocol::{wl_pointer::WlPointer, wl_shm::Format as ShmFormat},
utils::{Buffer as BufferCoords, Point, Size, Transform}, utils::{Buffer as BufferCoords, Point, Size, Transform},
@ -30,7 +31,7 @@ use smithay::{
}; };
use crate::{ use crate::{
shell::CosmicSurface, shell::{CosmicSurface, CursorGeometry, Shell},
state::{BackendData, State}, state::{BackendData, State},
utils::prelude::{ utils::prelude::{
OutputExt, PointExt, PointGlobalExt, PointLocalExt, RectExt, RectLocalExt, SeatExt, OutputExt, PointExt, PointGlobalExt, PointLocalExt, RectExt, RectLocalExt, SeatExt,
@ -44,6 +45,36 @@ pub use self::render::*;
use self::user_data::*; use self::user_data::*;
pub use self::user_data::{FrameHolder, ImageCopySessions, SessionData, SessionHolder}; pub use self::user_data::{FrameHolder, ImageCopySessions, SessionData, SessionHolder};
fn default_cursor_size() -> Size<i32, BufferCoords> {
Size::new(64, 64)
}
fn seat_for_wl_pointer<'a>(shell: &'a Shell, pointer: &WlPointer) -> Option<&'a Seat<State>> {
let pointer_handle = PointerHandle::<State>::from_resource(pointer)?;
shell
.seats
.iter()
.find(|seat| seat.get_pointer().is_some_and(|p| p == pointer_handle))
}
pub fn cursor_capture_constraints(cursor_geometry: Option<CursorGeometry>) -> BufferConstraints {
let size = if let Some(cursor_geometry) = cursor_geometry {
let mut size = cursor_geometry.geometry.size;
// Client shouldn't try to allocate 0x0 buffer
if size == Size::new(0, 0) {
size = Size::new(1, 1);
}
size
} else {
default_cursor_size()
};
BufferConstraints {
size,
shm: vec![ShmFormat::Argb8888],
dma: None,
}
}
impl ImageCopyCaptureHandler for State { impl ImageCopyCaptureHandler for State {
fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState { fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {
&mut self.common.image_copy_capture_state &mut self.common.image_copy_capture_state
@ -73,26 +104,12 @@ impl ImageCopyCaptureHandler for State {
fn cursor_capture_constraints( fn cursor_capture_constraints(
&mut self, &mut self,
_source: &ImageCaptureSource, _source: &ImageCaptureSource,
_pointer: &WlPointer, pointer: &WlPointer,
) -> Option<BufferConstraints> { ) -> Option<BufferConstraints> {
let size = if let Some((geometry, _)) = self let shell = self.common.shell.read();
.common let seat = seat_for_wl_pointer(&shell, pointer)?;
.shell let cursor_geometry = seat.cursor_geometry((0.0, 0.0), self.common.clock.now());
.read() Some(cursor_capture_constraints(cursor_geometry))
.seats
.last_active()
.cursor_geometry((0.0, 0.0), self.common.clock.now())
{
geometry.size
} else {
Size::from((64, 64))
};
Some(BufferConstraints {
size,
shm: vec![ShmFormat::Argb8888],
dma: None,
})
} }
fn new_session(&mut self, session: Session) { fn new_session(&mut self, session: Session) {
@ -149,20 +166,23 @@ impl ImageCopyCaptureHandler for State {
fn new_cursor_session(&mut self, session: CursorSession) { fn new_cursor_session(&mut self, session: CursorSession) {
let (pointer_loc, pointer_size, hotspot) = { let (pointer_loc, pointer_size, hotspot) = {
let seat = self.common.shell.read().seats.last_active().clone(); let shell = self.common.shell.read();
if let Some(seat) = seat_for_wl_pointer(&shell, &session.pointer()) {
let pointer = seat.get_pointer().unwrap();
let pointer_loc = pointer.current_location().to_i32_round().as_global();
let pointer = seat.get_pointer().unwrap(); let (pointer_size, hotspot) = if let Some(CursorGeometry { geometry, hotspot }) =
let pointer_loc = pointer.current_location().to_i32_round().as_global(); seat.cursor_geometry((0.0, 0.0), self.common.clock.now())
{
(geometry.size, hotspot)
} else {
(default_cursor_size(), Point::from((0, 0)))
};
let (pointer_size, hotspot) = if let Some((geometry, hotspot)) = (pointer_loc, pointer_size, hotspot)
seat.cursor_geometry((0.0, 0.0), self.common.clock.now())
{
(geometry.size, hotspot)
} else { } else {
(Size::from((64, 64)), Point::from((0, 0))) (Point::new(0, 0), default_cursor_size(), Point::from((0, 0)))
}; }
(pointer_loc, pointer_size, hotspot)
}; };
session.user_data().insert_if_missing_threadsafe(|| { session.user_data().insert_if_missing_threadsafe(|| {
@ -299,8 +319,11 @@ impl ImageCopyCaptureHandler for State {
return; return;
} }
let seat = self.common.shell.read().seats.last_active().clone(); let shell = self.common.shell.read();
render_cursor_to_buffer(self, session, frame, &seat); if let Some(seat) = seat_for_wl_pointer(&shell, &session.pointer()).cloned() {
drop(shell);
render_cursor_to_buffer(self, session, frame, &seat);
}
} }
fn frame_aborted(&mut self, frame: FrameRef) { fn frame_aborted(&mut self, frame: FrameRef) {

View file

@ -9,8 +9,7 @@ use smithay::{
buffer_dimensions, buffer_type, buffer_dimensions, buffer_type,
damage::{Error as DTError, OutputDamageTracker, RenderOutputResult}, damage::{Error as DTError, OutputDamageTracker, RenderOutputResult},
element::{ element::{
AsRenderElements, RenderElement, RenderElement, UnderlyingStorage,
surface::WaylandSurfaceRenderElement,
utils::{Relocate, RelocateRenderElement}, utils::{Relocate, RelocateRenderElement},
}, },
gles::{GlesError, GlesRenderbuffer}, gles::{GlesError, GlesRenderbuffer},
@ -21,16 +20,14 @@ use smithay::{
desktop::space::SpaceElement, desktop::space::SpaceElement,
input::Seat, input::Seat,
output::{Output, OutputNoMode}, output::{Output, OutputNoMode},
reexports::wayland_server::protocol::{wl_buffer::WlBuffer, wl_shm::Format as ShmFormat}, reexports::wayland_server::protocol::wl_buffer::WlBuffer,
utils::{ utils::{
Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Size,
Transform, Transform,
}, },
wayland::{ wayland::{
dmabuf::get_dmabuf, dmabuf::get_dmabuf,
image_copy_capture::{ image_copy_capture::{CaptureFailureReason, CursorSessionRef, Frame, SessionRef},
BufferConstraints, CaptureFailureReason, CursorSessionRef, Frame, SessionRef,
},
seat::WaylandFocus, seat::WaylandFocus,
shm::{shm_format_to_fourcc, with_buffer_contents, with_buffer_contents_mut}, shm::{shm_format_to_fourcc, with_buffer_contents, with_buffer_contents_mut},
}, },
@ -40,9 +37,11 @@ use tracing::warn;
use crate::{ use crate::{
backend::render::{ backend::render::{
CursorMode, ElementFilter, RendererRef, cursor, CursorMode, ElementFilter, RendererRef,
element::{AsGlowRenderer, CosmicElement, DamageElement, FromGlesError}, cursor::{self, CursorRenderElement},
element::{AsGlowRenderer, CosmicElement, DamageElement},
render_workspace, render_workspace,
wayland::SurfaceRenderElement,
}, },
shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement},
state::{Common, KmsNodes, State}, state::{Common, KmsNodes, State},
@ -55,12 +54,34 @@ use crate::{
}, },
}; };
use super::{super::data_device::get_dnd_icon, user_data::SessionHolder}; use super::{
super::data_device::get_dnd_icon, cursor_capture_constraints, user_data::SessionHolder,
};
pub fn render_element_buffers<R, E>(
renderer: &mut R,
elements: &[E],
) -> Vec<smithay::backend::renderer::utils::Buffer>
where
R: AsGlowRenderer,
E: RenderElement<R>,
{
elements
.iter()
.filter_map(|elem| match elem.underlying_storage(renderer) {
Some(UnderlyingStorage::Wayland(buffer)) => Some(buffer.clone()),
Some(UnderlyingStorage::Memory(_)) | None => None,
})
.collect()
}
pub struct PendingImageCopyData { pub struct PendingImageCopyData {
pub frame: Frame, pub frame: Frame,
pub damage: Vec<smithay::utils::Rectangle<i32, BufferCoords>>, pub damage: Vec<smithay::utils::Rectangle<i32, BufferCoords>>,
pub sync: SyncPoint, pub sync: SyncPoint,
// Hold reference so `wl_buffer` isn't released and sync point isn't signaled
// until image copy completes.
_buffers: Vec<smithay::backend::renderer::utils::Buffer>,
} }
impl PendingImageCopyData { impl PendingImageCopyData {
@ -103,10 +124,10 @@ pub fn submit_buffer<R>(
transform: Transform, transform: Transform,
damage: Option<&[Rectangle<i32, Physical>]>, damage: Option<&[Rectangle<i32, Physical>]>,
mut sync: SyncPoint, mut sync: SyncPoint,
buffers: Vec<smithay::backend::renderer::utils::Buffer>,
) -> Result<Option<PendingImageCopyData>, R::Error> ) -> Result<Option<PendingImageCopyData>, R::Error>
where where
R: ExportMem, R: ExportMem + AsGlowRenderer,
R::Error: FromGlesError,
{ {
let Some(damage) = damage else { let Some(damage) = damage else {
frame.success( frame.success(
@ -164,7 +185,7 @@ where
Ok(()) Ok(())
}) })
.map_err(|err| R::Error::from_gles_error(GlesError::BufferAccessError(err))) .map_err(|err| R::from_gles_error(GlesError::BufferAccessError(err)))
.and_then(|x| x) .and_then(|x| x)
{ {
frame.fail(CaptureFailureReason::Unknown); frame.fail(CaptureFailureReason::Unknown);
@ -182,6 +203,7 @@ where
}) })
.collect(), .collect(),
sync, sync,
_buffers: buffers,
})) }))
} }
@ -194,7 +216,6 @@ pub fn render_session<F, R>(
) -> Result<Option<PendingImageCopyData>, DTError<R::Error>> ) -> Result<Option<PendingImageCopyData>, DTError<R::Error>>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::Error: FromGlesError,
F: for<'d> FnOnce( F: for<'d> FnOnce(
&WlBuffer, &WlBuffer,
&mut R, &mut R,
@ -202,7 +223,13 @@ where
&'d mut OutputDamageTracker, &'d mut OutputDamageTracker,
usize, usize,
Vec<Rectangle<i32, BufferCoords>>, Vec<Rectangle<i32, BufferCoords>>,
) -> Result<RenderOutputResult<'d>, DTError<R::Error>>, ) -> Result<
(
RenderOutputResult<'d>,
Vec<smithay::backend::renderer::utils::Buffer>,
),
DTError<R::Error>,
>,
{ {
let mut session_user_data = session.lock().unwrap(); let mut session_user_data = session.lock().unwrap();
@ -246,30 +273,25 @@ where
.as_mut() .as_mut()
.map(|(_, tex)| renderer.bind(tex).map_err(DTError::Rendering)) .map(|(_, tex)| renderer.bind(tex).map_err(DTError::Rendering))
.transpose()?; .transpose()?;
let res = render_fn( let (result, buffers) = render_fn(
&frame.buffer(), &frame.buffer(),
renderer, renderer,
fb.as_mut(), fb.as_mut(),
dt, dt,
age, age,
frame.damage(), frame.damage(),
); )?;
match res { submit_buffer(
Ok(result) => submit_buffer( frame,
frame, renderer,
renderer, fb.as_mut(),
fb.as_mut(), transform,
transform, result.damage.map(|x| x.as_slice()),
result.damage.map(|x| x.as_slice()), result.sync,
result.sync, buffers,
) )
.map_err(DTError::Rendering), .map_err(DTError::Rendering)
Err(err) => {
frame.fail(CaptureFailureReason::Unknown);
Err(err)
}
}
} }
pub fn render_workspace_to_buffer( pub fn render_workspace_to_buffer(
@ -317,11 +339,16 @@ pub fn render_workspace_to_buffer(
common: &mut Common, common: &mut Common,
output: &Output, output: &Output,
handle: (WorkspaceHandle, usize), handle: (WorkspaceHandle, usize),
) -> Result<RenderOutputResult<'d>, DTError<R::Error>> ) -> Result<
(
RenderOutputResult<'d>,
Vec<smithay::backend::renderer::utils::Buffer>,
),
DTError<R::Error>,
>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicElement<R>: RenderElement<R>, CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>, WorkspaceRenderElement<R>: RenderElement<R>,
@ -358,7 +385,7 @@ pub fn render_workspace_to_buffer(
.collect() .collect()
}); });
if let Ok(dmabuf) = get_dmabuf(buffer) { let (res, elements) = if let Ok(dmabuf) = get_dmabuf(buffer) {
let mut dmabuf = dmabuf.clone(); let mut dmabuf = dmabuf.clone();
let mut fb = renderer.bind(&mut dmabuf).map_err(DTError::Rendering)?; let mut fb = renderer.bind(&mut dmabuf).map_err(DTError::Rendering)?;
render_workspace( render_workspace(
@ -376,8 +403,7 @@ pub fn render_workspace_to_buffer(
handle, handle,
cursor_mode, cursor_mode,
ElementFilter::ExcludeWorkspaceOverview, ElementFilter::ExcludeWorkspaceOverview,
) )?
.map(|res| res.0)
} else { } else {
let target = offscreen.expect("shm buffers should have an offscreen target"); let target = offscreen.expect("shm buffers should have an offscreen target");
render_workspace( render_workspace(
@ -395,9 +421,12 @@ pub fn render_workspace_to_buffer(
handle, handle,
cursor_mode, cursor_mode,
ElementFilter::ExcludeWorkspaceOverview, ElementFilter::ExcludeWorkspaceOverview,
) )?
.map(|res| res.0) };
}
let buffers = render_element_buffers(renderer, &elements);
Ok((res, buffers))
} }
let draw_cursor = session.draw_cursor(); let draw_cursor = session.draw_cursor();
@ -504,9 +533,10 @@ pub fn render_workspace_to_buffer(
} }
smithay::render_elements! { smithay::render_elements! {
pub WindowCaptureElement<R> where R: ImportAll + ImportMem; pub WindowCaptureElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
WaylandElement=WaylandSurfaceRenderElement<R>, WaylandElement=SurfaceRenderElement<R>,
CursorElement=RelocateRenderElement<cursor::CursorRenderElement<R>>, CursorElement=RelocateRenderElement<cursor::CursorRenderElement<R>>,
DamageElement=DamageElement,
} }
pub fn render_window_to_buffer( pub fn render_window_to_buffer(
@ -550,15 +580,20 @@ pub fn render_window_to_buffer(
common: &mut Common, common: &mut Common,
toplevel: &CosmicSurface, toplevel: &CosmicSurface,
geometry: Rectangle<i32, Logical>, geometry: Rectangle<i32, Logical>,
) -> Result<RenderOutputResult<'d>, DTError<R::Error>> ) -> Result<
(
RenderOutputResult<'d>,
Vec<smithay::backend::renderer::utils::Buffer>,
),
DTError<R::Error>,
>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicElement<R>: RenderElement<R>, CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
let additional_damage_elements: Vec<_> = additional_damage let mut elements: Vec<_> = additional_damage
.into_iter() .into_iter()
.filter_map(|rect| { .filter_map(|rect| {
let logical_rect = rect.to_logical( let logical_rect = rect.to_logical(
@ -569,15 +604,21 @@ pub fn render_window_to_buffer(
logical_rect.intersection(Rectangle::from_size(geometry.size)) logical_rect.intersection(Rectangle::from_size(geometry.size))
}) })
.map(DamageElement::new) .map(DamageElement::new)
.map(WindowCaptureElement::<R>::from)
.collect(); .collect();
dt.damage_output(age, &additional_damage_elements)?;
let shell = common.shell.read(); let shell = common.shell.read();
let blur_strength = 9; // TODO
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
let pointer = seat.get_pointer().unwrap(); let pointer = seat.get_pointer().unwrap();
let pointer_loc = pointer.current_location().to_i32_round().as_global(); let pointer_loc = pointer.current_location().to_i32_round().as_global();
let mut location = None; let mut location = None;
if let Some(element) = shell.element_for_surface(toplevel) if let Some(element) = shell.element_for_surface(toplevel)
&& pointer
.current_focus()
.and_then(|f| f.toplevel(&shell))
.as_ref()
== Some(toplevel)
&& element.has_active_window(toplevel) && element.has_active_window(toplevel)
&& let Some(workspace) = shell.space_for(element) && let Some(workspace) = shell.space_for(element)
&& let Some(geometry) = workspace.element_geometry(element) && let Some(geometry) = workspace.element_geometry(element)
@ -591,67 +632,80 @@ pub fn render_window_to_buffer(
}; };
std::mem::drop(shell); std::mem::drop(shell);
let mut elements = Vec::new();
if let Some(location) = location { if let Some(location) = location {
if draw_cursor { if draw_cursor {
elements.extend( cursor::draw_cursor(
cursor::draw_cursor( renderer,
renderer, &seat,
&seat, location,
location, 1.0.into(),
1.0.into(), 1.0,
1.0, common.clock.now(),
common.clock.now(), blur_strength,
true, true,
) &mut |elem, hotspot| {
.into_iter() elements.push(WindowCaptureElement::CursorElement(
.map(|(elem, hotspot)| { RelocateRenderElement::from_element(
WindowCaptureElement::CursorElement(RelocateRenderElement::from_element( elem,
elem, Point::from((-hotspot.x, -hotspot.y)),
Point::from((-hotspot.x, -hotspot.y)), Relocate::Relative,
Relocate::Relative, ),
)) ));
}), },
); );
} }
// TODO cosmic-workspaces wants to omit, but metadata cursor capture in portal should // TODO cosmic-workspaces wants to omit, but metadata cursor capture in portal should
// still include dnd surface in window capture buffer? // still include dnd surface in window capture buffer?
if draw_cursor && let Some(dnd_icon) = get_dnd_icon(&seat) { if draw_cursor && let Some(dnd_icon) = get_dnd_icon(&seat) {
elements.extend( cursor::draw_dnd_icon(
cursor::draw_dnd_icon( renderer,
renderer, &dnd_icon.surface,
&dnd_icon.surface, (location + dnd_icon.offset.to_f64()).to_i32_round(),
(location + dnd_icon.offset.to_f64()).to_i32_round(), 1.0,
1.0, blur_strength,
) &mut |elem| {
.into_iter() elements.push(
.map(WindowCaptureElement::from), RelocateRenderElement::from_element(
CursorRenderElement::Surface(elem),
Point::new(0, 0),
Relocate::Relative,
)
.into(),
)
},
); );
} }
} }
elements.extend(AsRenderElements::<R>::render_elements::< toplevel.push_render_elements(
WindowCaptureElement<R>,
>(
toplevel,
renderer, renderer,
(-geometry.loc.x, -geometry.loc.y).into(), (-geometry.loc.x, -geometry.loc.y).into(),
Scale::from(1.0), Scale::from(1.0),
1.0, 1.0,
)); None,
None,
false,
[0; 4],
blur_strength,
&mut |elem| elements.push(elem.into()),
None,
);
if let Ok(dmabuf) = get_dmabuf(buffer) { let res = if let Ok(dmabuf) = get_dmabuf(buffer) {
let mut dmabuf_clone = dmabuf.clone(); let mut dmabuf_clone = dmabuf.clone();
let mut fb = renderer let mut fb = renderer
.bind(&mut dmabuf_clone) .bind(&mut dmabuf_clone)
.map_err(DTError::Rendering)?; .map_err(DTError::Rendering)?;
dt.render_output(renderer, &mut fb, age, &elements, Color32F::TRANSPARENT) dt.render_output(renderer, &mut fb, age, &elements, Color32F::TRANSPARENT)?
} else { } else {
let fb = offscreen.expect("shm buffer should have an offscreen target"); let fb = offscreen.expect("shm buffer should have an offscreen target");
dt.render_output(renderer, fb, age, &elements, Color32F::TRANSPARENT) dt.render_output(renderer, fb, age, &elements, Color32F::TRANSPARENT)?
} };
let buffers = render_element_buffers(renderer, &elements);
Ok((res, buffers))
} }
let common = &mut state.common; let common = &mut state.common;
@ -753,25 +807,17 @@ pub fn render_cursor_to_buffer(
seat: &Seat<State>, seat: &Seat<State>,
) { ) {
let buffer = frame.buffer(); let buffer = frame.buffer();
let mut cursor_size = seat let cursor_geometry = seat.cursor_geometry((0.0, 0.0), state.common.clock.now());
.cursor_geometry((0.0, 0.0), state.common.clock.now()) let constraints = cursor_capture_constraints(cursor_geometry);
.map(|(geo, _hotspot)| geo.size)
.unwrap_or_else(|| Size::from((64, 64)));
let buffer_size = buffer_dimensions(&buffer).unwrap(); let buffer_size = buffer_dimensions(&buffer).unwrap();
// Client shouldn't try to allocate 0x0 buffer if buffer_size != constraints.size {
if cursor_size == Size::new(0, 0) { session.update_constraints(constraints.clone());
cursor_size = Size::new(1, 1);
}
if buffer_size != cursor_size {
let constraints = BufferConstraints {
size: cursor_size,
shm: vec![ShmFormat::Argb8888],
dma: None,
};
session.update_constraints(constraints);
if let Some(data) = session.user_data().get::<SessionData>() { if let Some(data) = session.user_data().get::<SessionData>() {
*data.lock().unwrap() = SessionUserData::new(OutputDamageTracker::new( *data.lock().unwrap() = SessionUserData::new(OutputDamageTracker::new(
cursor_size.to_logical(1, Transform::Normal).to_physical(1), constraints
.size
.to_logical(1, Transform::Normal)
.to_physical(1),
1.0, 1.0,
Transform::Normal, Transform::Normal,
)); ));
@ -789,48 +835,59 @@ pub fn render_cursor_to_buffer(
additional_damage: Vec<Rectangle<i32, BufferCoords>>, additional_damage: Vec<Rectangle<i32, BufferCoords>>,
common: &mut Common, common: &mut Common,
seat: &Seat<State>, seat: &Seat<State>,
) -> Result<RenderOutputResult<'d>, DTError<R::Error>> ) -> Result<
(
RenderOutputResult<'d>,
Vec<smithay::backend::renderer::utils::Buffer>,
),
DTError<R::Error>,
>
where where
R: AsGlowRenderer, R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static, R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicElement<R>: RenderElement<R>, CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
{ {
let additional_damage_elements: Vec<_> = additional_damage let mut elements: Vec<_> = additional_damage
.into_iter() .into_iter()
.filter_map(|rect| { .filter_map(|rect| {
let logical_rect = rect.to_logical(1, Transform::Normal, &Size::from((64, 64))); let logical_rect = rect.to_logical(1, Transform::Normal, &Size::from((64, 64)));
logical_rect.intersection(Rectangle::from_size((64, 64).into())) logical_rect.intersection(Rectangle::from_size((64, 64).into()))
}) })
.map(DamageElement::new) .map(DamageElement::new)
.map(WindowCaptureElement::from)
.collect(); .collect();
dt.damage_output(age, &additional_damage_elements)?;
let elements = cursor::draw_cursor( cursor::draw_cursor(
renderer, renderer,
seat, seat,
Point::from((0.0, 0.0)), Point::from((0.0, 0.0)),
1.0.into(), 1.0.into(),
1.0, 1.0,
common.clock.now(), common.clock.now(),
0,
true, true,
) &mut |elem, _| {
.into_iter() elements.push(
.map(|(elem, _)| RelocateRenderElement::from_element(elem, (0, 0), Relocate::Relative)) RelocateRenderElement::from_element(elem, (0, 0), Relocate::Relative).into(),
.map(WindowCaptureElement::from) )
.collect::<Vec<_>>(); },
);
if let Ok(dmabuf) = get_dmabuf(buffer) { let res = if let Ok(dmabuf) = get_dmabuf(buffer) {
let mut dmabuf_clone = dmabuf.clone(); let mut dmabuf_clone = dmabuf.clone();
let mut fb = renderer let mut fb = renderer
.bind(&mut dmabuf_clone) .bind(&mut dmabuf_clone)
.map_err(DTError::Rendering)?; .map_err(DTError::Rendering)?;
dt.render_output(renderer, &mut fb, age, &elements, [0.0, 0.0, 0.0, 0.0]) dt.render_output(renderer, &mut fb, age, &elements, [0.0, 0.0, 0.0, 0.0])?
} else { } else {
let fb = offscreen.expect("shm buffers should have offscreen target"); let fb = offscreen.expect("shm buffers should have offscreen target");
dt.render_output(renderer, fb, age, &elements, [0.0, 0.0, 0.0, 0.0]) dt.render_output(renderer, fb, age, &elements, [0.0, 0.0, 0.0, 0.0])?
} };
let buffers = render_element_buffers(renderer, &elements);
Ok((res, buffers))
} }
let common = &mut state.common; let common = &mut state.common;

View file

@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::State;
use crate::wayland::protocols::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState};
impl KeyboardLayoutHandler for State {
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState {
&mut self.common.keyboard_layout_state
}
}

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
pub mod a11y; pub mod a11y;
pub mod background_effect;
pub mod buffer; pub mod buffer;
pub mod compositor; pub mod compositor;
pub mod corner_radius; pub mod corner_radius;
@ -18,6 +19,7 @@ pub mod idle_notify;
pub mod image_capture_source; pub mod image_capture_source;
pub mod image_copy_capture; pub mod image_copy_capture;
pub mod input_method; pub mod input_method;
pub mod keyboard_layout;
pub mod keyboard_shortcuts_inhibit; pub mod keyboard_shortcuts_inhibit;
pub mod layer_shell; pub mod layer_shell;
pub mod output; pub mod output;

View file

@ -63,7 +63,7 @@ impl PointerConstraintsHandler for State {
if let Some(region) = constraint.region() { if let Some(region) = constraint.region() {
if let Some(surface_location) = surface_location if let Some(surface_location) = surface_location
&& let position = pointer.current_location() && let position = pointer.current_location()
&& let point = (position - surface_location.as_logical()).to_i32_round() && let point = (position - surface_location.as_logical()).to_i32_floor()
&& region.contains(point) && region.contains(point)
{ {
constraint.activate(); constraint.activate();

View file

@ -317,8 +317,45 @@ impl XdgShellHandler for State {
} }
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
for (popup, _) in smithay::desktop::PopupManager::popups_for_surface(surface.wl_surface()) {
if let smithay::desktop::PopupKind::Xdg(ref xdg_popup) = popup {
xdg_popup.send_popup_done();
}
}
let (output, clients) = { let (output, clients) = {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
for seat in shell.seats.iter() {
if let Some(data) = seat.user_data().get::<PopupGrabData>() {
let mut should_ungrab = false;
let grab = data.take();
if let Some(ref grab) = grab {
if grab.has_ended() {
should_ungrab = true;
} else if let Some(target) = grab.current_grab() {
if let Some(wl_surface) = target.wl_surface() {
if wl_surface.as_ref() == surface.wl_surface()
|| smithay::desktop::PopupManager::popups_for_surface(
surface.wl_surface(),
)
.any(|(p, _)| p.wl_surface() == wl_surface.as_ref())
{
should_ungrab = true;
}
}
}
}
if should_ungrab {
if let Some(mut grab) = grab {
grab.ungrab(PopupUngrabStrategy::All);
}
} else {
data.set(grab);
}
}
}
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
// Clean up pending_windows for surfaces that were never mapped. // Clean up pending_windows for surfaces that were never mapped.

View file

@ -1,12 +1,21 @@
use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_layer_v1::{
self, CosmicCornerRadiusLayerV1,
};
use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1; use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1;
use cosmic_protocols::corner_radius::v1::server::{ use cosmic_protocols::corner_radius::v1::server::{
cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1, cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1,
}; };
use smithay::utils::HookId; use smithay::desktop::utils::bbox_from_surface_tree;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_popup::XdgPopup;
use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use smithay::reexports::wayland_server::New;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::{HookId, Logical, Point, Rectangle};
use smithay::wayland::compositor::Cacheable; use smithay::wayland::compositor::Cacheable;
use smithay::wayland::compositor::add_pre_commit_hook; use smithay::wayland::compositor::add_pre_commit_hook;
use smithay::wayland::compositor::with_states; use smithay::wayland::compositor::with_states;
use smithay::wayland::shell::xdg::SurfaceCachedState; use smithay::wayland::shell::wlr_layer::WlrLayerShellHandler;
use smithay::wayland::shell::xdg::{SurfaceCachedState, XdgShellSurfaceUserData};
use smithay::{ use smithay::{
reexports::{ reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel, wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
@ -18,6 +27,7 @@ use std::sync::Mutex;
use wayland_backend::server::GlobalId; use wayland_backend::server::GlobalId;
type ToplevelHookId = Mutex<Option<(HookId, Weak<CosmicCornerRadiusToplevelV1>)>>; type ToplevelHookId = Mutex<Option<(HookId, Weak<CosmicCornerRadiusToplevelV1>)>>;
type LayerHookId = Mutex<Option<(HookId, Weak<CosmicCornerRadiusLayerV1>)>>;
#[derive(Debug)] #[derive(Debug)]
pub struct CornerRadiusState { pub struct CornerRadiusState {
@ -33,12 +43,13 @@ impl CornerRadiusState {
+ Dispatch< + Dispatch<
cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
CornerRadiusData, CornerRadiusData,
> + CornerRadiusHandler > + Dispatch<cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static, + 'static,
{ {
let global = dh let global = dh
.create_global::<D, cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, _>( .create_global::<D, cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, _>(
1, 2,
(), (),
); );
CornerRadiusState { CornerRadiusState {
@ -52,18 +63,12 @@ impl CornerRadiusState {
} }
} }
pub trait CornerRadiusHandler: XdgShellHandler { pub trait CornerRadiusHandler: XdgShellHandler + WlrLayerShellHandler {
fn corner_radius_state(&mut self) -> &mut CornerRadiusState; fn corner_radius_state(&mut self) -> &mut CornerRadiusState;
fn set_corner_radius( fn set_corner_radius(&mut self, data: &CornerRadiusData);
&mut self, fn unset_corner_radius(&mut self, data: &CornerRadiusData);
toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, fn set_padding(&mut self, data: &CornerRadiusData);
data: &CornerRadiusData, fn unset_padding(&mut self, data: &CornerRadiusData);
);
fn unset_corner_radius(
&mut self,
toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
data: &CornerRadiusData,
);
} }
impl<D> GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, (), D> impl<D> GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, (), D>
@ -72,6 +77,7 @@ where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()> D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()> + Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData> + Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData>
+ Dispatch<cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData>
+ CornerRadiusHandler + CornerRadiusHandler
+ 'static, + 'static,
{ {
@ -96,6 +102,7 @@ where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()> D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()> + Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData> + Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData>
+ Dispatch<cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData>
+ CornerRadiusHandler + CornerRadiusHandler
+ 'static, + 'static,
{ {
@ -115,6 +122,71 @@ where
} }
cosmic_corner_radius_manager_v1::Request::GetCornerRadius { id, toplevel } => { cosmic_corner_radius_manager_v1::Request::GetCornerRadius { id, toplevel } => {
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) {
new_xdg(
surface.wl_surface(),
CornerRadiusSurface::Toplevel(surface.xdg_toplevel().downgrade()),
resource,
data_init,
id,
)
} // TODO: can this fail?
}
cosmic_corner_radius_manager_v1::Request::GetCornerRadiusSurface { id, surface } => {
if let Some(toplevel) =
state
.xdg_shell_state()
.toplevel_surfaces()
.iter()
.find(|toplevel| {
toplevel
.xdg_toplevel()
.data::<XdgShellSurfaceUserData>()
.unwrap()
.xdg_surface()
== &surface
})
{
new_xdg(
toplevel.wl_surface(),
CornerRadiusSurface::Toplevel(toplevel.xdg_toplevel().downgrade()),
resource,
data_init,
id,
)
} else if let Some(popup) =
state
.xdg_shell_state()
.popup_surfaces()
.iter()
.find(|popup| {
popup
.xdg_popup()
.data::<XdgShellSurfaceUserData>()
.unwrap()
.xdg_surface()
== &surface
})
{
new_xdg(
popup.wl_surface(),
CornerRadiusSurface::Popup(popup.xdg_popup().downgrade()),
resource,
data_init,
id,
)
} else {
resource.post_error(
cosmic_corner_radius_manager_v1::Error::NoRole as u32,
"xdg_surface has no known role object".to_string(),
);
}
}
cosmic_corner_radius_manager_v1::Request::GetCornerRadiusLayer { id, layer } => {
if let Some(surface) = state
.shell_state()
.layer_surfaces()
.find(|surface| surface.shell_surface() == &layer)
{
let radius_exists = with_states(surface.wl_surface(), |surface_data| { let radius_exists = with_states(surface.wl_surface(), |surface_data| {
let hook_id = surface_data let hook_id = surface_data
.data_map .data_map
@ -124,63 +196,33 @@ where
}); });
if radius_exists.unwrap_or_default() { if radius_exists.unwrap_or_default() {
resource.post_error( resource.post_error(
cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32, cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32,
format!("{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface"), format!(
); "{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface"
),
);
} }
let data = Mutex::new(CornerRadiusInternal { let data = Mutex::new(CornerRadiusInternal {
toplevel: toplevel.downgrade(), surface: CornerRadiusSurface::Layer(layer.downgrade()),
corners: None, corners: None,
padding: None,
}); });
let obj = data_init.init(id, data); let obj = data_init.init(id, data);
let obj_downgrade = obj.downgrade(); let obj_downgrade = obj.downgrade();
let needs_hook = radius_exists.is_none(); let needs_hook = radius_exists.is_none();
if needs_hook { if needs_hook {
let hook_id = add_pre_commit_hook::<D, _>( let hook_id =
surface.wl_surface(), add_pre_commit_hook::<D, _>(surface.wl_surface(), layer_radius_hook);
move |_, _dh, surface| {
let corner_radii_too_big = with_states(surface, |surface_data| {
let corners = *surface_data
.cached_state
.get::<CacheableCorners>()
.pending();
surface_data
.cached_state
.get::<SurfaceCachedState>()
.pending()
.geometry
.zip(corners.0.as_ref())
.is_some_and(|(geo, corners)| {
let half_min_dim =
u8::try_from(geo.size.w.min(geo.size.h) / 2)
.unwrap_or(u8::MAX);
corners.top_right > half_min_dim
|| corners.top_left > half_min_dim
|| corners.bottom_right > half_min_dim
|| corners.bottom_left > half_min_dim
})
});
if corner_radii_too_big {
obj.post_error(
cosmic_corner_radius_toplevel_v1::Error::RadiusTooLarge
as u32,
format!("{obj:?} corner radius too large"),
);
}
},
);
with_states(surface.wl_surface(), |surface_data| { with_states(surface.wl_surface(), |surface_data| {
let hook_ids = surface_data let hook_ids = surface_data
.data_map .data_map
.get_or_insert_threadsafe(|| ToplevelHookId::new(None)); .get_or_insert_threadsafe(|| LayerHookId::new(None));
let mut guard = hook_ids.lock().unwrap(); let mut guard = hook_ids.lock().unwrap();
*guard = Some((hook_id, obj_downgrade)); *guard = Some((hook_id, obj_downgrade));
}); });
} }
} } // TODO: can this fail?
} }
_ => unimplemented!(), _ => unimplemented!(),
} }
@ -197,6 +239,55 @@ where
} }
} }
fn new_xdg<D>(
wl_surface: &WlSurface,
surface: CornerRadiusSurface,
resource: &cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>,
id: New<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1>,
) where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
let radius_exists = with_states(wl_surface, |surface_data| {
let hook_id = surface_data
.data_map
.get_or_insert_threadsafe(|| ToplevelHookId::new(None));
let guard = hook_id.lock().unwrap();
guard.as_ref().map(|(_, t)| t.upgrade().is_ok())
});
if radius_exists.unwrap_or_default() {
resource.post_error(
cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32,
format!(
"{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface"
),
);
}
let data = Mutex::new(CornerRadiusInternal {
surface,
corners: None,
padding: None,
});
let obj = data_init.init(id, data);
let obj_downgrade = obj.downgrade();
let needs_hook = radius_exists.is_none();
if needs_hook {
let hook_id = add_pre_commit_hook::<D, _>(wl_surface, xdg_radius_hook);
with_states(wl_surface, |surface_data| {
let hook_ids = surface_data
.data_map
.get_or_insert_threadsafe(|| ToplevelHookId::new(None));
let mut guard = hook_ids.lock().unwrap();
*guard = Some((hook_id, obj_downgrade));
});
}
}
impl<D> impl<D>
Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData, D> Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData, D>
for CornerRadiusState for CornerRadiusState
@ -221,30 +312,35 @@ where
let mut guard = data.lock().unwrap(); let mut guard = data.lock().unwrap();
guard.corners = None; guard.corners = None;
let Ok(toplevel) = guard.toplevel.upgrade() else { let Some(wl_surface) = (match &guard.surface {
CornerRadiusSurface::Toplevel(toplevel) => toplevel
.upgrade()
.ok()
.and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel))
.map(|toplevel| toplevel.wl_surface().clone()),
CornerRadiusSurface::Popup(popup) => popup
.upgrade()
.ok()
.and_then(|popup| state.xdg_shell_state().get_popup(&popup))
.map(|popup| popup.wl_surface().clone()),
CornerRadiusSurface::Layer(_) => unreachable!(),
}) else {
return; return;
}; };
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { with_states(&wl_surface, |surface_data| {
with_states(surface.wl_surface(), |surface_data| { if let Some(hook_ids_mutex) = surface_data.data_map.get::<ToplevelHookId>() {
if let Some(hook_ids_mutex) = surface_data.data_map.get::<ToplevelHookId>() let mut hook_id = hook_ids_mutex.lock().unwrap();
{ *hook_id = None;
let mut hook_id = hook_ids_mutex.lock().unwrap(); }
*hook_id = None;
}
});
}
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { let mut cached = surface_data.cached_state.get::<CacheableCorners>();
with_states(surface.wl_surface(), |s| { let pending = cached.pending();
let mut cached = s.cached_state.get::<CacheableCorners>(); *pending = CacheableCorners(None);
let pending = cached.pending(); });
*pending = CacheableCorners(None);
});
}
drop(guard); drop(guard);
state.unset_corner_radius(resource, data); state.unset_corner_radius(data);
} }
cosmic_corner_radius_toplevel_v1::Request::SetRadius { cosmic_corner_radius_toplevel_v1::Request::SetRadius {
top_left, top_left,
@ -254,66 +350,388 @@ where
} => { } => {
let mut guard = data.lock().unwrap(); let mut guard = data.lock().unwrap();
guard.set_corner_radius(top_left, top_right, bottom_right, bottom_left); guard.set_corner_radius(top_left, top_right, bottom_right, bottom_left);
let Ok(toplevel) = guard.toplevel.upgrade() else {
let Some(wl_surface) = (match &guard.surface {
CornerRadiusSurface::Toplevel(toplevel) => toplevel
.upgrade()
.ok()
.and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel))
.map(|toplevel| toplevel.wl_surface().clone()),
CornerRadiusSurface::Popup(popup) => popup
.upgrade()
.ok()
.and_then(|popup| state.xdg_shell_state().get_popup(&popup))
.map(|popup| popup.wl_surface().clone()),
CornerRadiusSurface::Layer(_) => unreachable!(),
}) else {
resource.post_error( resource.post_error(
cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32, cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32,
format!("{:?} No toplevel found", resource), format!("{:?} No toplevel found", resource),
); );
return; return;
}; };
with_states(&wl_surface, |s| {
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { let mut cached = s.cached_state.get::<CacheableCorners>();
with_states(surface.wl_surface(), |s| { let pending = cached.pending();
let mut cached = s.cached_state.get::<CacheableCorners>(); *pending = CacheableCorners(guard.corners);
let pending = cached.pending(); });
*pending = CacheableCorners(guard.corners);
});
}
drop(guard); drop(guard);
state.set_corner_radius(resource, data); state.set_corner_radius(data);
} }
cosmic_corner_radius_toplevel_v1::Request::UnsetRadius => { cosmic_corner_radius_toplevel_v1::Request::UnsetRadius => {
let mut guard = data.lock().unwrap(); let mut guard = data.lock().unwrap();
guard.corners = None; guard.corners = None;
let Ok(toplevel) = guard.toplevel.upgrade() else {
let Some(wl_surface) = (match &guard.surface {
CornerRadiusSurface::Toplevel(toplevel) => toplevel
.upgrade()
.ok()
.and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel))
.map(|toplevel| toplevel.wl_surface().clone()),
CornerRadiusSurface::Popup(popup) => popup
.upgrade()
.ok()
.and_then(|popup| state.xdg_shell_state().get_popup(&popup))
.map(|popup| popup.wl_surface().clone()),
CornerRadiusSurface::Layer(_) => unreachable!(),
}) else {
resource.post_error( resource.post_error(
cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32, cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32,
format!("{:?} No toplevel found", resource), format!("{:?} No toplevel found", resource),
); );
return; return;
}; };
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { with_states(&wl_surface, |s| {
with_states(surface.wl_surface(), |s| { let mut cached = s.cached_state.get::<CacheableCorners>();
let mut cached = s.cached_state.get::<CacheableCorners>(); let pending = cached.pending();
let pending = cached.pending(); *pending = CacheableCorners(None);
*pending = CacheableCorners(None); });
});
}
drop(guard); drop(guard);
state.unset_corner_radius(resource, data); state.unset_corner_radius(data);
} }
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
} }
impl<D> Dispatch<cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData, D>
for CornerRadiusState
where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
fn request(
state: &mut D,
_client: &Client,
resource: &cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1,
request: <cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1 as Resource>::Request,
data: &CornerRadiusData,
_dhandle: &DisplayHandle,
_data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>,
) {
match request {
cosmic_corner_radius_layer_v1::Request::Destroy => {
let mut guard = data.lock().unwrap();
guard.corners = None;
let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else {
unreachable!("corner_radius_layer without layer shell?");
};
let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| {
state
.shell_state()
.layer_surfaces()
.find(|s| s.shell_surface() == &layer)
}) else {
resource.post_error(
cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32,
format!("{:?} No layer found", resource),
);
return;
};
with_states(layer_surface.wl_surface(), |surface_data| {
if let Some(hook_ids_mutex) = surface_data.data_map.get::<LayerHookId>() {
let mut hook_id = hook_ids_mutex.lock().unwrap();
*hook_id = None;
}
let mut cached = surface_data.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(None);
let mut cached = surface_data.cached_state.get::<CacheablePadding>();
let pending = cached.pending();
*pending = CacheablePadding(None);
});
drop(guard);
state.unset_corner_radius(data);
state.unset_padding(data);
}
cosmic_corner_radius_layer_v1::Request::SetRadius {
top_left,
top_right,
bottom_right,
bottom_left,
} => {
let mut guard = data.lock().unwrap();
guard.set_corner_radius(top_left, top_right, bottom_right, bottom_left);
let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else {
unreachable!("corner_radius_layer without layer shell?");
};
let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| {
state
.shell_state()
.layer_surfaces()
.find(|s| s.shell_surface() == &layer)
}) else {
resource.post_error(
cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32,
format!("{:?} No layer found", resource),
);
return;
};
with_states(layer_surface.wl_surface(), |s| {
let mut cached = s.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(guard.corners);
});
drop(guard);
state.set_corner_radius(data);
}
cosmic_corner_radius_layer_v1::Request::UnsetRadius => {
let mut guard = data.lock().unwrap();
guard.corners = None;
let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else {
unreachable!("corner_radius_layer without layer shell?");
};
let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| {
state
.shell_state()
.layer_surfaces()
.find(|s| s.shell_surface() == &layer)
}) else {
resource.post_error(
cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32,
format!("{:?} No layer found", resource),
);
return;
};
with_states(layer_surface.wl_surface(), |s| {
let mut cached = s.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(None);
});
drop(guard);
state.unset_corner_radius(data);
}
cosmic_corner_radius_layer_v1::Request::SetPadding {
top,
right,
bottom,
left,
} => {
let mut guard = data.lock().unwrap();
guard.set_padding(top, right, bottom, left);
let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else {
unreachable!("corner_radius_layer without layer shell?");
};
let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| {
state
.shell_state()
.layer_surfaces()
.find(|s| s.shell_surface() == &layer)
}) else {
resource.post_error(
cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32,
format!("{:?} No layer found", resource),
);
return;
};
with_states(layer_surface.wl_surface(), |s| {
let mut cached = s.cached_state.get::<CacheablePadding>();
let pending = cached.pending();
*pending = CacheablePadding(guard.padding);
});
drop(guard);
state.set_padding(data);
}
cosmic_corner_radius_layer_v1::Request::UnsetPadding => {
let mut guard = data.lock().unwrap();
guard.corners = None;
let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else {
unreachable!("corner_radius_layer without layer shell?");
};
let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| {
state
.shell_state()
.layer_surfaces()
.find(|s| s.shell_surface() == &layer)
}) else {
resource.post_error(
cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32,
format!("{:?} No layer found", resource),
);
return;
};
with_states(layer_surface.wl_surface(), |s| {
let mut cached = s.cached_state.get::<CacheablePadding>();
let pending = cached.pending();
*pending = CacheablePadding(None);
});
drop(guard);
state.unset_padding(data);
}
_ => unimplemented!(),
}
}
}
fn xdg_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) {
with_states(surface, |surface_data| {
let corners = *surface_data
.cached_state
.get::<CacheableCorners>()
.pending();
if surface_data
.cached_state
.get::<SurfaceCachedState>()
.pending()
.geometry
.zip(corners.0.as_ref())
.is_some_and(|(geo, corners)| {
let half_min_dim = (geo.size.w.min(geo.size.h) / 2) as u32;
corners.top_right > half_min_dim
|| corners.top_left > half_min_dim
|| corners.bottom_right > half_min_dim
|| corners.bottom_left > half_min_dim
})
&& let Some(hook) = surface_data.data_map.get::<ToplevelHookId>()
{
let hook_ref = hook.lock().unwrap();
if let Some((_, obj)) = hook_ref.as_ref()
&& let Ok(obj) = obj.upgrade()
{
obj.post_error(
cosmic_corner_radius_toplevel_v1::Error::RadiusTooLarge as u32,
format!("{obj:?} corner radius too large"),
);
}
}
});
}
fn pad_rect(
mut rect: Rectangle<i32, Logical>,
padding: &Padding,
) -> Option<Rectangle<i32, Logical>> {
rect.size.h = rect.size.h.checked_sub(padding.top)?;
rect.loc.x += padding.left;
rect.size.w = rect.size.w.checked_sub(padding.left)?;
rect.loc.y += padding.top;
rect.size.h = rect.size.h.checked_sub(padding.bottom)?;
rect.size.w = rect.size.w.checked_sub(padding.right)?;
Some(rect)
}
fn layer_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) {
let bbox = bbox_from_surface_tree(surface, Point::default());
with_states(surface, |surface_data| {
let corners = *surface_data
.cached_state
.get::<CacheableCorners>()
.pending();
let padding = *surface_data
.cached_state
.get::<CacheablePadding>()
.pending();
let empty = Padding::default();
let Some(padded_box) = pad_rect(bbox, padding.0.as_ref().unwrap_or(&empty)) else {
if let Some(hook) = surface_data.data_map.get::<LayerHookId>() {
let hook_ref = hook.lock().unwrap();
if let Some((_, obj)) = hook_ref.as_ref()
&& let Ok(obj) = obj.upgrade()
{
obj.post_error(
cosmic_corner_radius_layer_v1::Error::PaddingTooLarge as u32,
format!("{obj:?} padding too large"),
);
}
}
return;
};
if corners.0.as_ref().is_some_and(|corners| {
let half_min_dim = (padded_box.size.w.min(padded_box.size.h) / 2) as u32;
corners.top_right > half_min_dim
|| corners.top_left > half_min_dim
|| corners.bottom_right > half_min_dim
|| corners.bottom_left > half_min_dim
}) && let Some(hook) = surface_data.data_map.get::<LayerHookId>()
{
let hook_ref = hook.lock().unwrap();
if let Some((_, obj)) = hook_ref.as_ref()
&& let Ok(obj) = obj.upgrade()
{
obj.post_error(
cosmic_corner_radius_layer_v1::Error::RadiusTooLarge as u32,
format!("{obj:?} corner radius too large"),
);
}
}
});
}
pub type CornerRadiusData = Mutex<CornerRadiusInternal>; pub type CornerRadiusData = Mutex<CornerRadiusInternal>;
#[derive(Debug)]
pub enum CornerRadiusSurface {
Toplevel(Weak<XdgToplevel>),
Popup(Weak<XdgPopup>),
Layer(Weak<ZwlrLayerSurfaceV1>),
}
#[derive(Debug)] #[derive(Debug)]
pub struct CornerRadiusInternal { pub struct CornerRadiusInternal {
pub toplevel: Weak<XdgToplevel>, pub surface: CornerRadiusSurface,
pub corners: Option<Corners>, pub corners: Option<Corners>,
pub padding: Option<Padding>,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Corners { pub struct Corners {
pub top_left: u8, pub top_left: u32,
pub top_right: u8, pub top_right: u32,
pub bottom_right: u8, pub bottom_right: u32,
pub bottom_left: u8, pub bottom_left: u32,
}
#[derive(Debug, Copy, Clone, Default)]
pub struct Padding {
pub top: i32,
pub right: i32,
pub bottom: i32,
pub left: i32,
} }
#[derive(Default, Debug, Copy, Clone)] #[derive(Default, Debug, Copy, Clone)]
@ -328,6 +746,18 @@ impl Cacheable for CacheableCorners {
} }
} }
#[derive(Default, Debug, Copy, Clone)]
pub struct CacheablePadding(pub Option<Padding>);
impl Cacheable for CacheablePadding {
fn commit(&mut self, _dh: &DisplayHandle) -> Self {
*self
}
fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
*into = self;
}
}
impl CornerRadiusInternal { impl CornerRadiusInternal {
fn set_corner_radius( fn set_corner_radius(
&mut self, &mut self,
@ -337,13 +767,23 @@ impl CornerRadiusInternal {
bottom_left: u32, bottom_left: u32,
) { ) {
let corners = Corners { let corners = Corners {
top_left: top_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8, top_left,
top_right: top_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8, top_right,
bottom_right: bottom_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8, bottom_right,
bottom_left: bottom_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8, bottom_left,
}; };
self.corners = Some(corners); self.corners = Some(corners);
} }
fn set_padding(&mut self, top: i32, right: i32, bottom: i32, left: i32) {
let padding = Padding {
top,
right,
bottom,
left,
};
self.padding = Some(padding);
}
} }
macro_rules! delegate_corner_radius { macro_rules! delegate_corner_radius {
@ -357,6 +797,9 @@ macro_rules! delegate_corner_radius {
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1: CornerRadiusData cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1: CornerRadiusData
] => $crate::wayland::protocols::corner_radius::CornerRadiusState); ] => $crate::wayland::protocols::corner_radius::CornerRadiusState);
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1: CornerRadiusData
] => $crate::wayland::protocols::corner_radius::CornerRadiusState);
}; };
} }
pub(crate) use delegate_corner_radius; pub(crate) use delegate_corner_radius;

View file

@ -180,7 +180,7 @@ where
Modifier::Invalid, Modifier::Invalid,
DmabufFlags::empty(), DmabufFlags::empty(),
); );
dma.add_plane(name, 0, offset0 as u32, stride0 as u32); dma.add_plane(name, offset0 as u32, stride0 as u32);
match dma.build() { match dma.build() {
Some(dmabuf) => { Some(dmabuf) => {
match state.dmabuf_imported(&data.dmabuf_global, dmabuf.clone()) { match state.dmabuf_imported(&data.dmabuf_global, dmabuf.clone()) {

View file

@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic_protocols::keyboard_layout::v1::server::{
zcosmic_keyboard_layout_manager_v1::{self, ZcosmicKeyboardLayoutManagerV1},
zcosmic_keyboard_layout_v1::{self, ZcosmicKeyboardLayoutV1},
};
use smithay::{
input::{
SeatHandler,
keyboard::{KeyboardHandle, Layout},
},
reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
},
wayland::{Dispatch2, GlobalDispatch2},
};
use std::mem;
use wayland_backend::server::{ClientId, GlobalId};
pub trait KeyboardLayoutHandler {
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState;
}
#[derive(Debug)]
pub struct KeyboardLayoutState {
pub global: GlobalId,
keyboard_layouts: Vec<(ZcosmicKeyboardLayoutV1, Option<Layout>)>,
}
impl KeyboardLayoutState {
pub fn new<D, F>(dh: &DisplayHandle, client_filter: F) -> Self
where
D: GlobalDispatch<ZcosmicKeyboardLayoutManagerV1, LayoutGlobalData> + 'static,
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
{
let global = dh.create_global::<D, ZcosmicKeyboardLayoutManagerV1, _>(
1,
LayoutGlobalData {
filter: Box::new(client_filter),
},
);
KeyboardLayoutState {
global,
keyboard_layouts: Vec::new(),
}
}
pub fn refresh<D>(state: &mut D)
where
D: SeatHandler + KeyboardLayoutHandler + 'static,
{
let mut keyboard_layouts = mem::take(&mut state.keyboard_layout_state().keyboard_layouts);
for (keyboard_layout, last_layout) in &mut keyboard_layouts {
if let Some(data) = keyboard_layout.data::<LayoutUserData<D>>()
&& let Some(handle) = &data.handle
{
let active_layout = handle.with_xkb_state(state, |context| {
context.xkb().lock().unwrap().active_layout()
});
if *last_layout != Some(active_layout) {
keyboard_layout.group(active_layout.0);
*last_layout = Some(active_layout);
}
}
}
state.keyboard_layout_state().keyboard_layouts = keyboard_layouts;
}
}
pub struct LayoutGlobalData {
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
}
struct LayoutManagerData;
impl<D> GlobalDispatch2<ZcosmicKeyboardLayoutManagerV1, D> for LayoutGlobalData
where
D: Dispatch<ZcosmicKeyboardLayoutManagerV1, LayoutManagerData> + 'static,
{
fn bind(
&self,
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: New<ZcosmicKeyboardLayoutManagerV1>,
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, LayoutManagerData);
}
fn can_view(&self, client: &Client) -> bool {
(self.filter)(client)
}
}
impl<D> Dispatch2<ZcosmicKeyboardLayoutManagerV1, D> for LayoutManagerData
where
D: Dispatch<ZcosmicKeyboardLayoutV1, LayoutUserData<D>>,
D: 'static,
D: SeatHandler,
D: KeyboardLayoutHandler,
{
fn request(
&self,
state: &mut D,
_client: &Client,
_resource: &ZcosmicKeyboardLayoutManagerV1,
request: zcosmic_keyboard_layout_manager_v1::Request,
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
match request {
zcosmic_keyboard_layout_manager_v1::Request::GetKeyboardLayout {
keyboard_layout,
keyboard,
} => {
let handle = KeyboardHandle::<D>::from_resource(&keyboard);
let active_layout = handle.as_ref().map(|handle| {
handle.with_xkb_state(state, |context| {
context.xkb().lock().unwrap().active_layout()
})
});
let keyboard_layout = data_init.init(keyboard_layout, LayoutUserData { handle });
if let Some(layout) = active_layout {
keyboard_layout.group(layout.0);
}
state
.keyboard_layout_state()
.keyboard_layouts
.push((keyboard_layout, active_layout));
}
zcosmic_keyboard_layout_manager_v1::Request::Destroy => {}
_ => unreachable!(),
}
}
}
#[doc(hidden)]
struct LayoutUserData<D: SeatHandler> {
handle: Option<KeyboardHandle<D>>,
}
impl<D> Dispatch2<ZcosmicKeyboardLayoutV1, D> for LayoutUserData<D>
where
D: 'static,
D: SeatHandler,
D: KeyboardLayoutHandler,
{
fn request(
&self,
state: &mut D,
_client: &Client,
_resource: &ZcosmicKeyboardLayoutV1,
request: zcosmic_keyboard_layout_v1::Request,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, D>,
) {
match request {
zcosmic_keyboard_layout_v1::Request::SetGroup { group } => {
if let Some(handle) = self.handle.as_ref() {
handle.with_xkb_state(state, |mut context| {
context.set_layout(Layout(group));
});
}
}
zcosmic_keyboard_layout_v1::Request::Destroy => {}
_ => unreachable!(),
}
}
fn destroyed(
&self,
state: &mut D,
_client: ClientId,
keyboard_layout: &ZcosmicKeyboardLayoutV1,
) {
let keyboard_layouts = &mut state.keyboard_layout_state().keyboard_layouts;
if let Some(idx) = keyboard_layouts
.iter()
.position(|(x, _)| x == keyboard_layout)
{
keyboard_layouts.remove(idx);
}
}
}

View file

@ -4,6 +4,7 @@ pub mod a11y;
pub mod corner_radius; pub mod corner_radius;
pub mod drm; pub mod drm;
pub mod image_capture_source; pub mod image_capture_source;
pub mod keyboard_layout;
pub mod output_configuration; pub mod output_configuration;
pub mod output_power; pub mod output_power;
pub mod overlap_notify; pub mod overlap_notify;

View file

@ -807,9 +807,17 @@ impl XwmHandler for State {
*context, *context,
); );
} }
if !shell.pending_windows.iter().any(|w| w.surface == window) { let fullscreen = window.is_fullscreen().then(|| seat.active_output());
let fullscreen = window.is_fullscreen().then(|| seat.active_output()); let maximized = window.is_maximized();
let maximized = window.is_maximized(); if let Some(pending) = shell
.pending_windows
.iter_mut()
.find(|w| w.surface == window)
{
pending.seat = seat;
pending.fullscreen = fullscreen;
pending.maximized = maximized;
} else {
let surface = CosmicSurface::from(window); let surface = CosmicSurface::from(window);
shell.pending_windows.push(PendingWindow { shell.pending_windows.push(PendingWindow {
surface, surface,