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
65 changed files with 6343 additions and 2673 deletions

1436
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ edition = "2024"
license = "GPL-3.0-only"
name = "cosmic-comp"
version = "1.0.0"
rust-version = "1.90"
rust-version = "1.93"
[workspace]
members = ["cosmic-comp-config"]
@ -17,18 +17,18 @@ cosmic-comp-config = { path = "cosmic-comp-config", features = [
"libdisplay-info",
"output",
] }
cosmic-config = { git = "https://github.com/pop-os/libcosmic", features = [
cosmic-config = { path = "../libcosmic/cosmic-config", features = [
"calloop",
"macro",
] }
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", rev = "160b086", default-features = false, features = [
cosmic-protocols = { path = "../cosmic-protocols", default-features = false, features = [
"server",
] }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" }
cosmic-settings-daemon-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", features = [
cosmic-settings-config = { path = "../cosmic-settings-daemon/config" }
cosmic-settings-daemon-config = { path = "../cosmic-settings-daemon/cosmic-settings-daemon-config", features = [
"greeter",
] }
cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git", features = [
cosmic-text = { path = "../cosmic-text", features = [
"shape-run-cache",
] }
libdisplay-info = "0.3.0"
@ -39,10 +39,15 @@ i18n-embed = { version = "0.16", features = [
"desktop-requester",
] }
i18n-embed-fl = "0.10"
iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic" }
iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" }
iced_graphics = { path = "../libcosmic/iced/graphics", features = ["geometry"] }
indexmap = "2.13"
keyframe = "1.1.1"
libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false }
cosmic = { package = "libcosmic", path = "../libcosmic", default-features = false, features = [
"tokio",
"wayland",
"multi-window",
] }
libsystemd = { version = "0.7", optional = true }
log-panics = { version = "2", features = ["with-backtrace"] }
ordered-float = "5.1"
@ -73,6 +78,7 @@ xdg = "^3.0"
xdg-user = "0.2.1"
xkbcommon = "0.9"
zbus = "5.14.0"
tokio = { version = "1", features = ["rt-multi-thread"] }
profiling = { version = "1.0" }
rustix = { version = "1.1.4", features = ["process"] }
rand = "0.10"
@ -82,7 +88,8 @@ parking_lot = "0.12.5"
logind-zbus = { version = "5.3.2", optional = true }
futures-executor = { version = "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]
branch = "feature/copy_clone"
@ -138,9 +145,36 @@ inherits = "release"
[profile.release]
lto = "fat"
[patch."https://github.com/pop-os/libcosmic"]
cosmic-config = { path = "../libcosmic/cosmic-config" }
cosmic-theme = { path = "../libcosmic/cosmic-theme" }
iced = { path = "../libcosmic/iced" }
iced_accessibility = { path = "../libcosmic/iced/accessibility" }
iced_core = { path = "../libcosmic/iced/core" }
iced_futures = { path = "../libcosmic/iced/futures" }
iced_graphics = { path = "../libcosmic/iced/graphics" }
iced_renderer = { path = "../libcosmic/iced/renderer" }
iced_runtime = { path = "../libcosmic/iced/runtime" }
iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" }
iced_wgpu = { path = "../libcosmic/iced/wgpu" }
iced_widget = { path = "../libcosmic/iced/widget" }
iced_winit = { path = "../libcosmic/iced/winit" }
[patch."https://github.com/pop-os/cosmic-protocols"]
cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" }
cosmic-client-toolkit = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" }
cosmic-protocols = { path = "../cosmic-protocols" }
cosmic-client-toolkit = { path = "../cosmic-protocols/client-toolkit" }
[patch."https://github.com/pop-os/cosmic-settings-daemon"]
cosmic-settings-config = { path = "../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"]
cosmic-text = { path = "../cosmic-text" }
[patch."https://forge.aditua.com/leyoda/window_clipboard.git"]
window_clipboard = { path = "../window_clipboard" }
dnd = { path = "../window_clipboard/dnd" }
mime = { path = "../window_clipboard/mime" }
[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

@ -4,8 +4,8 @@ version = "1.0.0"
edition = "2024"
[dependencies]
cosmic-config = { git = "https://github.com/pop-os/libcosmic/" }
cosmic-randr-shell = { git = "https://github.com/pop-os/cosmic-randr/", optional = true }
cosmic-config = { path = "../../libcosmic/cosmic-config" }
cosmic-randr-shell = { path = "../../cosmic-randr/shell", optional = true }
input = "0.10.0"
libdisplay-info = { version = "0.3.0", optional = true }
serde = { version = "1", features = ["derive"] }

View file

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

View file

@ -1,7 +1,7 @@
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-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
shrink-window = Verkleinern
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

@ -1,3 +1,3 @@
[toolchain]
channel = "1.90"
channel = "1.93"
components = [ "rust-src" ]

View file

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

View file

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

View file

@ -12,10 +12,9 @@ use smithay::{
},
drm::{CreateDrmNodeError, DrmNode},
renderer::{
RendererSuper,
gles::{GlesError, GlesRenderer, ffi},
glow::GlowRenderer,
multigpu::{ApiDevice, Error as MultiError, GraphicsApi},
multigpu::{ApiDevice, GraphicsApi},
},
},
reexports::drm::control::Device,
@ -32,8 +31,6 @@ use std::{
sync::atomic::{AtomicBool, Ordering},
};
use crate::backend::render::element::FromGlesError;
/// Errors raised by the [`GbmGlesBackend`]
#[derive(Debug, thiserror::Error)]
pub enum Error {
@ -213,15 +210,3 @@ impl ApiDevice for GbmGlowDevice {
!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> {
let socket_name = format!(
"{}-{}",
&self.socket.to_string_lossy(),
self.socket.to_string_lossy(),
render_node
.dev_path()
.unwrap()

View file

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

View file

@ -1,6 +1,13 @@
// 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::{
backend::{
allocator::Fourcc,
@ -9,10 +16,10 @@ use smithay::{
element::{
Kind,
memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
},
},
},
desktop::utils::bbox_from_surface_tree,
input::{
Seat,
pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus},
@ -114,24 +121,63 @@ enum Error {
NoDefaultCursor,
#[error("Error opening xcursor file: {0}")]
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> {
let icon_path = theme
.load_icon(&shape.to_string())
.ok_or(Error::NoDefaultCursor)?;
let mut cursor_file = std::fs::File::open(&icon_path)?;
let mut cursor_data = Vec::new();
cursor_file.read_to_end(&mut cursor_data)?;
parse_xcursor(&cursor_data).ok_or(Error::Parse)
let shape_name = shape.to_string();
for name in cursor_aliases(&shape_name)
.iter()
.copied()
.chain(std::iter::once(shape_name.as_str()))
{
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! {
pub CursorRenderElement<R> where R: ImportAll + ImportMem;
pub CursorRenderElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
Static=MemoryRenderBufferRenderElement<R>,
Surface=WaylandSurfaceRenderElement<R>,
Surface=SurfaceRenderElement<R>,
}
pub fn draw_surface_cursor<R>(
@ -139,9 +185,10 @@ pub fn draw_surface_cursor<R>(
surface: &wl_surface::WlSurface,
location: Point<f64, Logical>,
scale: impl Into<Scale<f64>>,
) -> Vec<(CursorRenderElement<R>, Point<i32, Physical>)>
where
R: Renderer + ImportAll,
blur_strength: usize,
push: &mut dyn FnMut(CursorRenderElement<R>, Point<i32, Physical>),
) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static,
{
let scale = scale.into();
@ -156,17 +203,21 @@ where
.to_physical_precise_round(scale)
});
render_elements_from_surface_tree(
push_render_elements_from_surface_tree(
renderer,
surface,
location.to_physical(scale).to_i32_round(),
bbox_from_surface_tree(surface, location.to_i32_round()).to_f64(),
scale,
1.0,
false,
[0; 4],
None,
blur_strength,
Kind::Cursor,
)
.into_iter()
.map(|elem| (elem, h))
.collect()
&mut |elem| push(elem.into(), h),
None,
);
}
#[profiling::function]
@ -175,9 +226,10 @@ pub fn draw_dnd_icon<R>(
surface: &wl_surface::WlSurface,
location: Point<f64, Logical>,
scale: impl Into<Scale<f64>>,
) -> Vec<WaylandSurfaceRenderElement<R>>
where
R: Renderer + ImportAll,
blur_strength: usize,
push: &mut dyn FnMut(SurfaceRenderElement<R>),
) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static,
{
if get_role(surface) != Some("dnd_icon") {
@ -187,14 +239,21 @@ where
);
}
let scale = scale.into();
render_elements_from_surface_tree(
push_render_elements_from_surface_tree(
renderer,
surface,
location.to_physical(scale).to_i32_round(),
bbox_from_surface_tree(surface, location.to_i32_round()).to_f64(),
scale,
1.0,
false,
[0; 4],
None,
blur_strength,
FRAME_TIME_FILTER,
)
push,
None,
);
}
pub type CursorState = Mutex<CursorStateInner>;
@ -277,10 +336,11 @@ pub fn draw_cursor<R>(
scale: Scale<f64>,
buffer_scale: f64,
time: Time<Monotonic>,
blur_strength: usize,
draw_default: bool,
) -> Vec<(CursorRenderElement<R>, Point<i32, Physical>)>
where
R: Renderer + ImportMem + ImportAll,
push: &mut dyn FnMut(CursorRenderElement<R>, Point<i32, Physical>),
) where
R: Renderer + ImportMem + ImportAll + AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
{
// draw the cursor as relevant
@ -291,7 +351,7 @@ where
let state = &mut *state_ref;
if state.hidden {
return Vec::new();
return;
}
let named_cursor = state.current_cursor.or(match cursor_status {
@ -300,7 +360,7 @@ where
});
if let Some(current_cursor) = named_cursor {
if !draw_default && current_cursor == CursorIcon::Default {
return Vec::new();
return;
}
let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32;
@ -337,7 +397,7 @@ where
);
state.current_image = Some(frame);
return vec![(
push(
CursorRenderElement::Static(
MemoryRenderBufferRenderElement::from_buffer(
renderer,
@ -351,11 +411,9 @@ where
.expect("Failed to import cursor bitmap"),
),
hotspot.to_physical_precise_round(scale),
)];
);
} else if let CursorImageStatus::Surface(ref wl_surface) = cursor_status {
return draw_surface_cursor(renderer, wl_surface, location, scale);
} else {
Vec::new()
draw_surface_cursor(renderer, wl_surface, location, scale, blur_strength, push);
}
}

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")]
use smithay::backend::renderer::element::texture::TextureRenderElement;
use smithay::{
backend::{
allocator::dmabuf::Dmabuf,
drm::DrmDeviceFd,
renderer::{
Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer,
Bind, Blit, ContextId, ExportMem, ImportAll, ImportMem, Offscreen, Renderer,
element::{
Element, Id, Kind, RenderElement, UnderlyingStorage,
memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement},
},
gles::{GlesError, GlesRenderbuffer, GlesTexture, element::TextureShaderElement},
glow::{GlowFrame, GlowRenderer},
multigpu::MultiTexture,
utils::{CommitCounter, DamageSet, OpaqueRegions},
},
},
@ -28,19 +35,20 @@ use super::{GlMultiRenderer, cursor::CursorRenderElement};
pub enum CosmicElement<R>
where
R: AsGlowRenderer,
R::TextureId: 'static,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
Workspace(
RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>,
),
Cursor(RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>),
Dnd(WaylandSurfaceRenderElement<R>),
Dnd(SurfaceRenderElement<R>),
MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>),
Postprocess(
CropRenderElement<RelocateRenderElement<RescaleRenderElement<TextureShaderElement>>>,
),
Zoom(MemoryRenderBufferRenderElement<R>),
Zoom(IcedRenderElement<R>),
Damage(DamageElement),
#[cfg(feature = "debug")]
Egui(TextureRenderElement<GlesTexture>),
}
@ -48,7 +56,7 @@ where
impl<R> Element for CosmicElement<R>
where
R: AsGlowRenderer,
R::TextureId: 'static,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn id(&self) -> &Id {
@ -59,6 +67,7 @@ where
CosmicElement::MoveGrab(elem) => elem.id(),
CosmicElement::Postprocess(elem) => elem.id(),
CosmicElement::Zoom(elem) => elem.id(),
CosmicElement::Damage(elem) => elem.id(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.id(),
}
@ -72,6 +81,7 @@ where
CosmicElement::MoveGrab(elem) => elem.current_commit(),
CosmicElement::Postprocess(elem) => elem.current_commit(),
CosmicElement::Zoom(elem) => elem.current_commit(),
CosmicElement::Damage(elem) => elem.current_commit(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.current_commit(),
}
@ -85,6 +95,7 @@ where
CosmicElement::MoveGrab(elem) => elem.src(),
CosmicElement::Postprocess(elem) => elem.src(),
CosmicElement::Zoom(elem) => elem.src(),
CosmicElement::Damage(elem) => elem.src(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.src(),
}
@ -98,6 +109,7 @@ where
CosmicElement::MoveGrab(elem) => elem.geometry(scale),
CosmicElement::Postprocess(elem) => elem.geometry(scale),
CosmicElement::Zoom(elem) => elem.geometry(scale),
CosmicElement::Damage(elem) => elem.geometry(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.geometry(scale),
}
@ -111,6 +123,7 @@ where
CosmicElement::MoveGrab(elem) => elem.location(scale),
CosmicElement::Postprocess(elem) => elem.location(scale),
CosmicElement::Zoom(elem) => elem.location(scale),
CosmicElement::Damage(elem) => elem.location(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.location(scale),
}
@ -124,6 +137,7 @@ where
CosmicElement::MoveGrab(elem) => elem.transform(),
CosmicElement::Postprocess(elem) => elem.transform(),
CosmicElement::Zoom(elem) => elem.transform(),
CosmicElement::Damage(elem) => elem.transform(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.transform(),
}
@ -141,6 +155,7 @@ where
CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit),
CosmicElement::Postprocess(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")]
CosmicElement::Egui(elem) => elem.damage_since(scale, commit),
}
@ -154,6 +169,7 @@ where
CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale),
CosmicElement::Postprocess(elem) => elem.opaque_regions(scale),
CosmicElement::Zoom(elem) => elem.opaque_regions(scale),
CosmicElement::Damage(elem) => elem.opaque_regions(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.opaque_regions(scale),
}
@ -167,6 +183,7 @@ where
CosmicElement::MoveGrab(elem) => elem.alpha(),
CosmicElement::Postprocess(elem) => elem.alpha(),
CosmicElement::Zoom(elem) => elem.alpha(),
CosmicElement::Damage(elem) => elem.alpha(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.alpha(),
}
@ -180,6 +197,7 @@ where
CosmicElement::MoveGrab(elem) => elem.kind(),
CosmicElement::Postprocess(elem) => elem.kind(),
CosmicElement::Zoom(elem) => elem.kind(),
CosmicElement::Damage(elem) => elem.kind(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.kind(),
}
@ -193,6 +211,7 @@ where
CosmicElement::MoveGrab(elem) => elem.is_framebuffer_effect(),
CosmicElement::Postprocess(elem) => elem.is_framebuffer_effect(),
CosmicElement::Zoom(elem) => elem.is_framebuffer_effect(),
CosmicElement::Damage(elem) => elem.is_framebuffer_effect(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.is_framebuffer_effect(),
}
@ -202,8 +221,7 @@ where
impl<R> RenderElement<R> for CosmicElement<R>
where
R: AsGlowRenderer,
R::TextureId: 'static,
R::Error: FromGlesError,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn draw(
@ -237,9 +255,12 @@ where
opaque_regions,
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::Damage(elem) => {
RenderElement::<R>::draw(elem, frame, src, dst, damage, opaque_regions, cache)
}
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => {
let glow_frame = R::glow_frame_mut(frame);
@ -252,7 +273,7 @@ where
opaque_regions,
cache,
)
.map_err(FromGlesError::from_gles_error)
.map_err(R::from_gles_error)
}
}
}
@ -268,6 +289,7 @@ where
elem.underlying_storage(glow_renderer)
}
CosmicElement::Zoom(elem) => elem.underlying_storage(renderer),
CosmicElement::Damage(elem) => elem.underlying_storage(renderer),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => {
let glow_renderer = renderer.glow_renderer_mut();
@ -293,16 +315,19 @@ where
RenderElement::<GlowRenderer>::capture_framebuffer(
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::Damage(elem) => {
RenderElement::<R>::capture_framebuffer(elem, frame, src, dst, cache)
}
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => {
let glow_frame = R::glow_frame_mut(frame);
RenderElement::<GlowRenderer>::capture_framebuffer(
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>
where
R: AsGlowRenderer,
R::TextureId: 'static,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
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
R: AsGlowRenderer,
R::TextureId: 'static,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self {
fn from(value: IcedRenderElement<R>) -> Self {
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")]
impl<R> From<TextureRenderElement<GlesTexture>> for CosmicElement<R>
where
R: AsGlowRenderer,
R::TextureId: 'static,
R::TextureId: Send + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(elem: TextureRenderElement<GlesTexture>) -> Self {
@ -365,6 +401,12 @@ pub trait AsGlowRenderer:
fn glow_frame_mut<'a, 'frame, 'buffer>(
frame: &'a mut Self::Frame<'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 {
@ -384,6 +426,18 @@ impl AsGlowRenderer for GlowRenderer {
) -> &'a mut GlowFrame<'frame, 'buffer> {
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<'_> {
@ -403,6 +457,18 @@ impl AsGlowRenderer for GlMultiRenderer<'_> {
) -> &'b mut GlowFrame<'frame, 'buffer> {
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 {
@ -458,13 +524,3 @@ impl<R: Renderer> RenderElement<R> for DamageElement {
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::{
kms::render::gles::GbmGlowBackend,
render::{
clipped_surface::{CLIPPING_SHADER, ClippingShader},
element::DamageElement,
shadow::{SHADOW_SHADER, ShadowShader},
wayland::{
SurfaceRenderElement,
blur_effect::BlurShaders,
clipped_surface::{CLIPPING_SHADER, ClippingShader},
push_render_elements_from_surface_tree,
},
},
},
config::ScreenFilter,
shell::{
CosmicMappedRenderElement, OverviewMode, SeatExt, Trigger, WorkspaceDelta,
CosmicMappedRenderElement, OutputId, OverviewMode, SeatExt, Trigger, WorkspaceDelta,
WorkspaceRenderElement,
element::CosmicMappedKey,
focus::{FocusTarget, Stage, render_input_order, target::WindowGroup},
@ -34,25 +39,26 @@ use crate::{
wayland::{
handlers::{
compositor::FRAME_TIME_FILTER,
corner_radius::{pad_rect, surface_corners, surface_padding},
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,
},
};
use cosmic::Theme;
use element::FromGlesError;
use smithay::{
backend::{
allocator::Fourcc,
drm::{DrmDeviceFd, DrmNode},
renderer::{
Color32F, ImportAll, Offscreen, Renderer, Texture, TextureFilter,
Color32F, Offscreen, Texture, TextureFilter,
damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult},
element::{
Element, Id, Kind, RenderElement, WeakId,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
Element, Id, Kind, NamespacedElement, RenderElement, WeakId,
texture::{TextureRenderBuffer, TextureRenderElement},
utils::{
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate,
@ -69,22 +75,23 @@ use smithay::{
sync::SyncPoint,
},
},
desktop::utils::bbox_from_surface_tree,
input::Seat,
output::{Output, OutputModeSource, OutputNoMode},
utils::{
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")]
use smithay_egui::EguiState;
pub mod animations;
pub mod clipped_surface;
pub mod cursor;
pub mod element;
pub mod shadow;
pub mod wayland;
use self::element::{AsGlowRenderer, CosmicElement};
use super::kms::Timings;
@ -262,7 +269,7 @@ impl IndicatorShader {
.filter(|(old_settings, _)| &settings == old_settings)
.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 elem = PixelShaderElement::new(
@ -422,6 +429,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> {
UniformName::new("geo_size", UniformType::_2f),
UniformName::new("corner_radius", UniformType::_4f),
UniformName::new("input_to_geo", UniformType::Matrix3x3),
UniformName::new("noise", UniformType::_1f),
],
)?;
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),
],
)?;
let blur_shaders = BlurShaders::compile(renderer)?;
let egl_context = renderer.egl_context();
egl_context
@ -454,6 +463,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> {
egl_context
.user_data()
.insert_if_missing(|| ShadowShader(shadow_shader));
egl_context.user_data().insert_if_missing(|| blur_shaders);
Ok(())
}
@ -471,12 +481,14 @@ pub fn cursor_elements<'a, 'frame, R>(
seats: impl Iterator<Item = &'a Seat<State>>,
zoom_state: Option<&ZoomState>,
theme: &Theme,
blur_strength: usize,
now: Time<Monotonic>,
output: &Output,
mode: CursorMode,
exclude_dnd_icon: bool,
) -> Vec<CosmicElement<R>>
where
scanout_node: Option<DrmNode>,
push: &mut dyn FnMut(CosmicElement<R>),
) where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
@ -490,7 +502,6 @@ where
)
})
.unwrap_or_else(|| ((0., 0.).into(), 1.));
let mut elements = Vec::new();
for seat in seats {
let pointer = match seat.get_pointer() {
@ -500,19 +511,17 @@ where
let location = pointer.current_location() - output.current_location().to_f64();
if mode != CursorMode::None {
elements.extend(
cursor::draw_cursor(
renderer,
seat,
location,
scale.into(),
zoom_scale,
now,
mode != CursorMode::NotDefault,
)
.into_iter()
.map(|(elem, hotspot)| {
CosmicElement::Cursor(RescaleRenderElement::from_element(
cursor::draw_cursor(
renderer,
seat,
location,
scale.into(),
zoom_scale,
now,
blur_strength,
mode != CursorMode::NotDefault,
&mut |elem, hotspot| {
push(CosmicElement::Cursor(RescaleRenderElement::from_element(
RelocateRenderElement::from_element(
elem,
Point::from((-hotspot.x, -hotspot.y)),
@ -523,63 +532,55 @@ where
.to_physical(output.current_scale().fractional_scale())
.to_i32_round(),
zoom_scale,
))
}),
)))
},
);
}
if !exclude_dnd_icon && let Some(dnd_icon) = get_dnd_icon(seat) {
elements.extend(
cursor::draw_dnd_icon(
renderer,
&dnd_icon.surface,
(location + dnd_icon.offset.to_f64()).to_i32_round(),
scale,
)
.into_iter()
.map(CosmicElement::Dnd),
cursor::draw_dnd_icon(
renderer,
&dnd_icon.surface,
(location + dnd_icon.offset.to_f64()).to_i32_round(),
scale,
blur_strength,
&mut |elem| push(CosmicElement::Dnd(elem)),
);
}
let theme = theme.cosmic();
if let Some(grab_elements) = seat
if let Some(grab_state) = seat
.user_data()
.get::<SeatMoveGrabState>()
.unwrap()
.lock()
.unwrap()
.as_ref()
.map(|state| state.render::<CosmicMappedRenderElement<R>, R>(renderer, output, theme))
{
elements.extend(grab_elements.into_iter().map(|elem| {
CosmicElement::MoveGrab(RescaleRenderElement::from_element(
grab_state.render(renderer, output, theme, scanout_node, &mut |elem| {
push(CosmicElement::MoveGrab(RescaleRenderElement::from_element(
elem,
focal_point
.as_logical()
.to_physical(output.current_scale().fractional_scale())
.to_i32_round(),
zoom_scale,
))
}));
)));
})
}
if let Some((grab_elements, should_scale)) = seat
if let Some(grab_state) = seat
.user_data()
.get::<SeatMenuGrabState>()
.unwrap()
.lock()
.unwrap()
.as_ref()
.map(|state| {
(
state.render::<CosmicMappedRenderElement<R>, R>(renderer, output),
!state.is_in_screen_space(),
)
})
{
elements.extend(grab_elements.into_iter().map(|elem| {
CosmicElement::MoveGrab(RescaleRenderElement::from_element(
elem,
let should_scale = !grab_state.is_in_screen_space();
grab_state.render(renderer, output, &mut |elem| {
push(CosmicElement::MoveGrab(RescaleRenderElement::from_element(
elem.into(),
if should_scale {
focal_point
.as_logical()
@ -589,12 +590,10 @@ where
Point::from((0, 0))
},
if should_scale { zoom_scale } else { 1.0 },
))
}));
)));
})
}
}
elements
}
#[cfg(not(feature = "debug"))]
@ -615,11 +614,11 @@ pub fn output_elements<R>(
output: &Output,
cursor_mode: CursorMode,
_fps: Option<(&EguiState, &Timings)>,
scanout_node: Option<DrmNode>,
) -> Result<Vec<CosmicElement<R>>, RenderError<R::Error>>
where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicMappedRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>,
{
@ -646,7 +645,7 @@ where
),
scale,
)
.map_err(FromGlesError::from_gles_error)
.map_err(R::from_gles_error)
.map_err(RenderError::Rendering)?
.into(),
]
@ -690,6 +689,7 @@ where
workspace,
cursor_mode,
element_filter,
scanout_node,
)?;
#[cfg(feature = "debug")]
@ -713,15 +713,15 @@ pub fn workspace_elements<R>(
current: (WorkspaceHandle, usize),
cursor_mode: CursorMode,
element_filter: ElementFilter,
scanout_node: Option<DrmNode>,
) -> Result<Vec<CosmicElement<R>>, RenderError<R::Error>>
where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicMappedRenderElement<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 seats = shell_ref.seats.iter().cloned().collect::<Vec<_>>();
@ -729,21 +729,25 @@ where
return Ok(Vec::new());
}
let theme = shell_ref.theme().clone();
let blur_strength = (theme.cosmic().frosted as u8 + 1) as usize;
let scale = output.current_scale().fractional_scale();
// we don't want to hold a shell lock across `cursor_elements`,
// that is prone to deadlock with the main-thread on some grabs.
std::mem::drop(shell_ref);
elements.extend(cursor_elements(
cursor_elements(
renderer,
seats.iter(),
zoom_level,
&theme,
blur_strength,
now,
output,
cursor_mode,
element_filter == ElementFilter::ExcludeWorkspaceOverview,
));
scanout_node,
&mut |elem| elements.push(elem),
);
let shell = shell.read();
let overview = shell.overview_mode();
@ -819,72 +823,132 @@ where
render_input_order::<()>(&shell, output, previous, current, element_filter, |stage| {
match stage {
Stage::ZoomUI => {
elements.extend(ZoomState::render(renderer, output));
ZoomState::render(renderer, output, &mut |elem| {
elements.push(CosmicElement::Zoom(elem))
});
}
Stage::SessionLock(lock_surface) => {
elements.extend(
session_lock_elements(renderer, output, lock_surface)
.into_iter()
.map(Into::into)
.flat_map(crop_to_output)
.map(Into::into),
);
session_lock_elements(renderer, output, lock_surface, blur_strength, &mut |elem| {
elements.extend(crop_to_output(elem.into()).map(Into::into))
})
}
Stage::LayerPopup {
popup, location, ..
popup,
location,
workspace_idx,
..
} => {
elements.extend(
render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>(
renderer,
popup.wl_surface(),
location
.to_local(output)
.as_logical()
.to_physical_precise_round(scale),
Scale::from(scale),
1.0,
FRAME_TIME_FILTER,
)
.into_iter()
.flat_map(crop_to_output)
.map(Into::into),
);
let mut geometry = popup.geometry().as_global();
geometry.loc += location;
let radii = with_states(popup.wl_surface(), |states| {
surface_corners(states, geometry.size.as_logical())
})
.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,
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 } => {
elements.extend(
render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>(
renderer,
layer.wl_surface(),
location
.to_local(output)
.as_logical()
.to_physical_precise_round(scale),
Scale::from(scale),
1.0,
FRAME_TIME_FILTER,
)
.into_iter()
.flat_map(crop_to_output)
.map(Into::into),
Stage::LayerSurface {
layer,
location,
workspace_idx,
} => {
let mut geometry = layer.geometry().as_global();
geometry.loc += location;
let geometry = geometry.to_local(output).as_logical();
let padded = with_states(layer.wl_surface(), |states| {
surface_padding(states, geometry.size)
.and_then(|padding| pad_rect(geometry, &padding))
})
.unwrap_or(geometry);
let radii = with_states(layer.wl_surface(), |states| {
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 } => {
elements.extend(surface.wl_surface().into_iter().flat_map(|surface| {
render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>(
if let Some(wl_surface) = surface.wl_surface() {
let mut geometry = surface.geometry().as_global();
geometry.loc += location;
push_render_elements_from_surface_tree(
renderer,
&surface,
&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,
[0; 4],
None,
blur_strength,
FRAME_TIME_FILTER,
)
.into_iter()
.flat_map(crop_to_output)
.map(Into::into)
}));
&mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)),
None,
);
}
}
Stage::StickyPopups(layout) => {
let alpha = match &overview.0 {
@ -905,14 +969,11 @@ where
OverviewMode::None => 1.0,
};
elements.extend(
layout
.render_popups(renderer, alpha)
.into_iter()
.map(Into::into)
.flat_map(crop_to_output)
.map(Into::into),
);
layout.render_popups(renderer, alpha, scanout_node, &mut |elem| {
if let Some(elem) = crop_to_output(elem.into()) {
elements.push(elem.into())
}
});
}
Stage::Sticky(layout) => {
let alpha = match &overview.0 {
@ -937,79 +998,66 @@ where
.then_some(last_active_seat)
.map(|seat| workspace.focus_stack.get(seat));
elements.extend(
layout
.render(
renderer,
current_focus.as_ref().and_then(|stack| {
stack.last().and_then(|t| match t {
FocusTarget::Window(w) => Some(w),
_ => None,
})
}),
resize_indicator.clone(),
active_hint,
alpha,
theme.cosmic(),
)
.into_iter()
.map(Into::into)
.flat_map(crop_to_output)
.map(Into::into),
)
layout.render(
renderer,
current_focus.as_ref().and_then(|stack| {
stack.last().and_then(|t| match t {
FocusTarget::Window(w) => Some(w),
_ => None,
})
}),
resize_indicator.clone(),
active_hint,
alpha,
theme.cosmic(),
scanout_node,
&mut |elem| {
if let Some(elem) = crop_to_output(elem.into()) {
elements.push(elem.into())
}
},
);
}
Stage::WorkspacePopups { workspace, offset } => {
elements.extend(
match workspace.render_popups(
renderer,
last_active_seat,
!move_active && is_active_space,
overview.clone(),
theme.cosmic(),
) {
Ok(elements) => {
elements
.into_iter()
.flat_map(crop_to_output)
.map(|element| {
CosmicElement::Workspace(RelocateRenderElement::from_element(
element,
offset.to_physical_precise_round(scale),
Relocate::Relative,
))
})
}
Err(_) => {
return ControlFlow::Break(Err(OutputNoMode));
workspace.render_popups(
renderer,
last_active_seat,
!move_active && is_active_space,
overview.clone(),
theme.cosmic(),
scanout_node,
&mut |elem| {
if let Some(elem) = crop_to_output(elem) {
elements.push(CosmicElement::Workspace(
RelocateRenderElement::from_element(
elem,
offset.to_physical_precise_round(scale),
Relocate::Relative,
),
));
}
},
);
}
Stage::Workspace { workspace, offset } => {
elements.extend(
match workspace.render(
renderer,
last_active_seat,
!move_active && is_active_space,
overview.clone(),
resize_indicator.clone(),
active_hint,
theme.cosmic(),
) {
Ok(elements) => {
elements
.into_iter()
.flat_map(crop_to_output)
.map(|element| {
CosmicElement::Workspace(RelocateRenderElement::from_element(
element,
offset.to_physical_precise_round(scale),
Relocate::Relative,
))
})
}
Err(_) => {
return ControlFlow::Break(Err(OutputNoMode));
workspace.render(
renderer,
last_active_seat,
!move_active && is_active_space,
overview.clone(),
resize_indicator.clone(),
active_hint,
theme.cosmic(),
scanout_node,
&mut |elem| {
if let Some(elem) = crop_to_output(elem) {
elements.push(CosmicElement::Workspace(
RelocateRenderElement::from_element(
elem,
offset.to_physical_precise_round(scale),
Relocate::Relative,
),
));
}
},
);
@ -1026,23 +1074,29 @@ fn session_lock_elements<R>(
renderer: &mut R,
output: &Output,
lock_surface: Option<&LockSurface>,
) -> Vec<WaylandSurfaceRenderElement<R>>
where
R: Renderer + ImportAll,
blur_strength: usize,
push: &mut dyn FnMut(SurfaceRenderElement<R>),
) where
R: AsGlowRenderer,
R::TextureId: Clone + 'static,
{
if let Some(surface) = lock_surface {
let scale = Scale::from(output.current_scale().fractional_scale());
render_elements_from_surface_tree(
push_render_elements_from_surface_tree(
renderer,
surface.wl_surface(),
(0, 0),
bbox_from_surface_tree(surface.wl_surface(), (0, 0)).to_f64(),
scale,
1.0,
false,
[0; 4],
None,
blur_strength,
FRAME_TIME_FILTER,
push,
None,
)
} else {
Vec::new()
}
}
@ -1190,7 +1244,6 @@ pub fn render_output<'d, R>(
where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<R>: RenderElement<R>,
WorkspaceRenderElement<R>: RenderElement<R>,
@ -1454,11 +1507,16 @@ where
}
}
Ok(RenderOutputResult {
damage: res.0,
sync,
states: res.1,
})
let buffers = render_element_buffers(renderer, &elements);
Ok((
RenderOutputResult {
damage: res.0,
sync,
states: res.1,
},
buffers,
))
},
)? {
pending_image_copy_data.send_success_when_ready(
@ -1495,12 +1553,23 @@ pub fn render_workspace<'d, R>(
where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
R::Error: FromGlesError,
CosmicElement<R>: RenderElement<R>,
CosmicMappedRenderElement<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,
renderer,
shell,
@ -1511,17 +1580,8 @@ where
current,
cursor_mode,
element_filter,
)?;
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)?;
}
None,
)?);
let res = damage_tracker.render_output(
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 vec4 corner_radius;
uniform mat3 input_to_geo;
uniform float noise;
float rounding_alpha(vec2 coords, vec2 size) {
vec2 center;
@ -51,15 +52,26 @@ float rounding_alpha(vec2 coords, vec2 size) {
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
}
void main() {
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0);
float hash(vec2 p) {
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);
#if defined(NO_ALPHA)
color = vec4(color.rgb, 1.0);
#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) {
// Clip outside geometry.
color = vec4(0.0);
@ -68,8 +80,20 @@ void main() {
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.
color = color * alpha;
color *= alpha;
#if defined(DEBUG_FLAGS)
if (tint == 1.0)

View file

@ -1,6 +1,6 @@
use std::{borrow::Borrow, cell::RefCell, collections::HashMap};
use cgmath::{Matrix3, Vector2};
use glam::{Affine2, Mat3, Vec2};
use smithay::{
backend::renderer::{
element::Kind,
@ -113,26 +113,26 @@ impl ShadowShader {
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 area_size = Vector2::new(shader_geo.size.w, shader_geo.size.h);
let geo_loc = Vector2::new(-shader_geo.loc.x, -shader_geo.loc.y);
let area_size = Vec2::new(shader_geo.size.w as f32, shader_geo.size.h as f32);
let geo_loc = Vec2::new(-shader_geo.loc.x as f32, -shader_geo.loc.y as f32);
shader_geo.loc += offset + geo.loc;
let input_to_geo = (Matrix3::from_nonuniform_scale(area_size.x, area_size.y)
* Matrix3::from_translation(Vector2::new(
-geo_loc.x / area_size.x,
-geo_loc.y / area_size.y,
)))
.cast::<f32>()
.unwrap();
let input_to_geo = Mat3::from(
Affine2::from_scale(area_size)
* Affine2::from_translation(Vec2::new(
-geo_loc.x / area_size.x,
-geo_loc.y / area_size.y,
)),
);
let window_geo_loc = Vector2::new(window_geo.loc.x, window_geo.loc.y);
let window_input_to_geo = (Matrix3::from_nonuniform_scale(area_size.x, area_size.y)
* Matrix3::from_translation(Vector2::new(
-window_geo_loc.x / area_size.x,
-window_geo_loc.y / area_size.y,
)))
.cast::<f32>()
.unwrap();
let window_geo_loc = Vec2::new(window_geo.loc.x as f32, window_geo.loc.y as f32);
let window_input_to_geo = Mat3::from(
Affine2::from_scale(area_size)
* Affine2::from_translation(Vec2::new(
-window_geo_loc.x / area_size.x,
-window_geo_loc.y / area_size.y,
)),
);
let element = PixelShaderElement::new(
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 cgmath::{Matrix3, Vector2};
use glam::{Affine2, Mat3, Vec2};
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use smithay::{
backend::renderer::{
ImportAll, ImportMem, Renderer,
ImportAll, Renderer,
element::{
Element, Id, Kind, RenderElement, UnderlyingStorage,
surface::WaylandSurfaceRenderElement,
@ -19,7 +19,7 @@ use smithay::{
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);
impl ClippingShader {
@ -35,10 +35,7 @@ impl ClippingShader {
}
#[derive(Debug)]
pub struct ClippedSurfaceRenderElement<R>
where
R: Renderer + ImportAll + ImportMem,
{
pub struct ClippedSurfaceRenderElement<R: Renderer> {
inner: WaylandSurfaceRenderElement<R>,
program: GlesTexProgram,
radius: [u8; 4],
@ -48,7 +45,7 @@ where
impl<R> ClippedSurfaceRenderElement<R>
where
R: Renderer + ImportAll + ImportMem,
R: Renderer + ImportAll,
{
pub fn new(
renderer: &mut R,
@ -66,18 +63,18 @@ where
let view = elem.view();
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()
* Matrix3::<f32>::from_translation(-Vector2::new(0.5, 0.5));
* Affine2::from_translation(-Vec2::new(0.5, 0.5));
let geo_scale = {
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 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.y / elem_geo.size.h as f64) as f32,
))
@ -85,16 +82,17 @@ where
let buf_scale = {
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.y / buf_size.h as f64) as f32,
));
let input_to_geo =
transform_matrix * geo_scale * geo_translation * buf_scale * buf_translation;
let input_to_geo = Mat3::from(
transform_matrix * geo_scale * geo_translation * buf_scale * buf_translation,
);
let uniforms = vec![
Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)),
@ -114,6 +112,7 @@ where
transpose: false,
},
),
Uniform::new("noise", UniformValue::_1f(0.0)),
];
Self {
@ -174,7 +173,8 @@ where
impl<R> Element for ClippedSurfaceRenderElement<R>
where
R: Renderer + ImportAll + ImportMem,
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: 'static,
{
fn id(&self) -> &Id {
self.inner.id()
@ -247,7 +247,7 @@ where
impl<R> RenderElement<R> for ClippedSurfaceRenderElement<R>
where
R: AsGlowRenderer + Renderer + ImportAll + ImportMem,
R: AsGlowRenderer + Renderer + ImportAll,
R::TextureId: 'static,
{
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},
wayland::handlers::{
image_copy_capture::SessionHolder, xwayland_keyboard_grab::XWaylandGrabSeat,
image_copy_capture::{SessionHolder, cursor_capture_constraints},
xwayland_keyboard_grab::XWaylandGrabSeat,
},
};
use calloop::{
@ -35,14 +36,15 @@ use calloop::{
use cosmic_comp_config::{NumlockState, workspace::WorkspaceLayout};
use cosmic_settings_config::shortcuts;
use cosmic_settings_config::shortcuts::action::{Direction, ResizeDirection};
#[cfg(feature = "logind")]
use smithay::backend::input::{Switch, SwitchState, SwitchToggleEvent};
use smithay::{
backend::input::{
AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, Device, DeviceCapability,
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _,
GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState, KeyboardKeyEvent,
PointerAxisEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent,
TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent,
TabletToolTipState, TouchEvent,
PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
},
desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree},
input::{
@ -59,22 +61,19 @@ use smithay::{
output::Output,
reexports::{
input::Device as InputDevice,
wayland_server::{
Resource as _,
protocol::{wl_shm::Format as ShmFormat, wl_surface::WlSurface},
},
wayland_server::{Resource as _, protocol::wl_surface::WlSurface},
},
utils::{Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size},
utils::{Clock, Logical, Monotonic, Point, Rectangle, SERIAL_COUNTER, Serial},
wayland::{
compositor::CompositorHandler,
image_copy_capture::{BufferConstraints, CursorSessionRef},
image_copy_capture::CursorSessionRef,
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat,
pointer_constraints::{PointerConstraint, with_pointer_constraint},
seat::WaylandFocus,
tablet_manager::{TabletDescriptor, TabletSeatTrait},
},
};
use tracing::{error, trace, warn};
use tracing::{error, trace};
use xkbcommon::xkb::{Keycode, Keysym};
use std::{
@ -336,7 +335,7 @@ impl State {
// Constraint does not apply if not within region
if !constraint.region().is_none_or(|x| {
x.contains(
(ptr.current_location() - *surface_loc).to_i32_round(),
(ptr.current_location() - *surface_loc).to_i32_floor(),
)
}) {
return;
@ -381,14 +380,25 @@ impl State {
.unwrap_or(current_output.clone());
drop(shell);
let output_geometry = output.geometry();
position.x = position.x.clamp(
output_geometry.loc.x as f64,
(output_geometry.loc.x + output_geometry.size.w - 1) as f64,
);
position.y = position.y.clamp(
output_geometry.loc.y as f64,
(output_geometry.loc.y + output_geometry.size.h - 1) as f64,
);
let scale = output.current_scale().fractional_scale();
let physical = output
.current_mode()
.map(|mode| output.current_transform().transform_size(mode.size))
.unwrap_or_default();
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 seat
@ -542,7 +552,7 @@ impl State {
if let Some(region) = &confine_region
&& !region
.contains((pos.as_logical() - *surface_loc).to_i32_round())
.contains((pos.as_logical() - *surface_loc).to_i32_floor())
{
return (false, None);
}
@ -613,7 +623,7 @@ impl State {
PointerConstraint::Confined(confined) => confined.region(),
};
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)) {
constraint.activate();
}
@ -638,37 +648,13 @@ impl State {
seat.set_active_output(&output);
}
for session in cursor_sessions_for_output(&shell, &output) {
if let Some((geometry, offset)) = 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(),
),
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));
}
}
update_output_image_copy_cursor_position(
&shell,
&self.common.clock,
&output,
&seat,
position,
);
}
}
InputEvent::PointerMotionAbsolute { event, .. } => {
@ -707,37 +693,13 @@ impl State {
ptr.frame(self);
let shell = self.common.shell.read();
for session in cursor_sessions_for_output(&shell, &output) {
if let Some((geometry, offset)) = 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(),
),
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));
}
}
update_output_image_copy_cursor_position(
&shell,
&self.common.clock,
&output,
&seat,
position,
);
}
}
InputEvent::PointerButton { event, .. } => {
@ -787,7 +749,12 @@ impl State {
let shell = self.common.shell.read();
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)
&& seat.get_keyboard().unwrap().modifier_state().logo
&& !shortcuts_inhibited
@ -1002,6 +969,10 @@ impl State {
if let Some(horizontal_amount) = event.amount(Axis::Horizontal) {
if horizontal_amount != 0.0 {
frame = frame
.relative_direction(
Axis::Horizontal,
event.relative_direction(Axis::Horizontal),
)
.value(Axis::Horizontal, scroll_factor * horizontal_amount);
if let Some(discrete) = event.amount_v120(Axis::Horizontal) {
frame = frame.v120(
@ -1015,8 +986,12 @@ impl State {
}
if let Some(vertical_amount) = event.amount(Axis::Vertical) {
if vertical_amount != 0.0 {
frame =
frame.value(Axis::Vertical, scroll_factor * vertical_amount);
frame = frame
.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) {
frame = frame.v120(
Axis::Vertical,
@ -1599,6 +1574,7 @@ impl State {
}
}
InputEvent::Special(_) => {}
#[allow(unused_variables)]
InputEvent::SwitchToggle { event } => {
#[cfg(feature = "logind")]
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 !closed {
warn!(?err, "Failed to re-enable internal connector");
tracing::warn!(?err, "Failed to re-enable internal connector");
if let Some(output) = output {
use cosmic_comp_config::output::comp::OutputState;
@ -2132,6 +2108,7 @@ impl State {
layer,
popup,
location,
..
} => {
if layer.can_receive_keyboard_focus() {
let surface = popup.wl_surface();
@ -2149,7 +2126,9 @@ impl State {
}
}
}
Stage::LayerSurface { layer, location } => {
Stage::LayerSurface {
layer, location, ..
} => {
if under_from_surface_tree(
layer.wl_surface(),
global_pos.as_logical(),
@ -2192,7 +2171,7 @@ impl State {
if Rectangle::new(offset.as_local(), output_geo.size)
.intersection(output_geo)
.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)
{
@ -2206,7 +2185,7 @@ impl State {
if Rectangle::new(offset.as_local(), output_geo.size)
.intersection(output_geo)
.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)
{
@ -2301,7 +2280,9 @@ impl State {
))));
}
}
Stage::LayerSurface { layer, location } => {
Stage::LayerSurface {
layer, location, ..
} => {
let surface = layer.wl_surface();
if let Some((surface, surface_loc)) = under_from_surface_tree(
surface,
@ -2407,7 +2388,7 @@ impl State {
if let Some(constraint) = constraint
&& 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);
}
true
@ -2420,7 +2401,7 @@ impl State {
if is_legal(pos_in_element) {
let x = workspace_origin.x + origin.x + pos_in_element.x;
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 {
None
}
@ -2430,11 +2411,34 @@ impl State {
};
if let Some((point, output)) = point_and_output {
// TODO: Replace with `wl_pointer.warp`
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();
shell.update_pointer_position(point.as_global().to_local(&output), &output);
shell.update_pointer_position(point.to_local(&output), &output);
let seat = shell
.seats
@ -2449,31 +2453,13 @@ impl State {
self.common.config.cosmic_conf.accessibility_zoom.view_moves,
);
let output_geometry = output.geometry();
for session in cursor_sessions_for_output(&shell, &output) {
if let Some((geometry, offset)) = seat.cursor_geometry(
point.to_buffer(
output.current_scale().fractional_scale(),
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)
{
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));
}
}
update_output_image_copy_cursor_position(
&shell,
&self.common.clock,
&output,
&seat,
point,
);
}
}
}
@ -2534,3 +2520,36 @@ fn mapped_output_for_device<'a, D: Device + 'static>(
};
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},
};
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;
@ -79,11 +81,8 @@ impl State {
warn!(?err, "Failed to setup cosmic-session communication");
}
let mut args = env::args().skip(1);
self.common.kiosk_child = if let Some(exec) = args.next() {
self.common.kiosk_child = if let Some(mut command) = self.kiosk_command.take() {
// Run command in kiosk mode
let mut command = process::Command::new(&exec);
command.args(args);
command.envs(
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
.spawn()
.map_err(|err| {
@ -113,8 +112,10 @@ impl State {
pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
let raw_args = RawArgs::from_args();
let mut cursor = raw_args.cursor();
raw_args.next_os(&mut cursor);
let git_hash = option_env!("GIT_HASH").unwrap_or("unknown");
let mut kiosk_command = None;
let mut with_xwayland = true;
// Parse the arguments
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(());
}
_ => {}
_ => {
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.get_signal(),
with_xwayland,
kiosk_command,
);
// init backend
backend::init_backend_auto(&display, &mut event_loop, &mut state)?;
@ -331,5 +337,6 @@ fn refresh(state: &mut State) {
state::Common::refresh_focus(state);
OverlapNotifyState::refresh(state);
state.common.update_x11_stacking_order();
KeyboardLayoutState::refresh(state);
state.last_refresh = LastRefresh::At(Instant::now());
}

View file

@ -1,6 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-only
fn main() {
// libcosmic-yoda enables zbus' `tokio` feature, so zbus 5.14 expects an
// ambient Tokio runtime via Handle::current(). cosmic-comp's loop is not
// async, so hold a runtime guard for the lifetime of run().
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("failed to build tokio runtime");
let _guard = runtime.enter();
if let Err(err) = cosmic_comp::run(Default::default()) {
tracing::error!("Error occured in main(): {}", err);
std::process::exit(1);

View file

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

View file

@ -1,10 +1,11 @@
use std::sync::Mutex;
use std::sync::{Arc, Mutex};
use crate::{
backend::render::element::AsGlowRenderer,
config::Config,
fl,
shell::grabs::ResizeEdge,
utils::iced::{IcedElement, Program},
utils::iced::{IcedElement, IcedRenderElement, Program},
};
use calloop::LoopHandle;
@ -12,175 +13,330 @@ use cosmic::{
Apply,
iced::{
Alignment,
widget::{column, container, row, space},
core::{Background, Border, Color, Length},
widget::{container, row, space},
},
iced_core::{Background, Border, Color, Length},
theme,
widget::{icon::from_name, text},
};
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(
direction: ResizeDirection,
config: &Config,
evlh: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> ResizeIndicator {
ResizeIndicator::new(
ResizeIndicatorInternal {
edges: Mutex::new(ResizeEdge::all()),
direction,
shortcut1: config
.shortcuts
.iter()
.find_map(|(pattern, action)| {
(*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,
theme,
)
center_elem: IcedElement<ResizeIndicatorInternal>,
left_elem: IcedElement<ResizeIndicatorArrow>,
top_elem: IcedElement<ResizeIndicatorArrow>,
right_elem: IcedElement<ResizeIndicatorArrow>,
down_elem: IcedElement<ResizeIndicatorArrow>,
}
const ARROW_SIZE: i32 = 36;
impl ResizeIndicator {
pub fn new(
direction: ResizeDirection,
config: &Config,
evlh: LoopHandle<'static, crate::state::State>,
mut theme: cosmic::Theme,
) -> ResizeIndicator {
theme.transparent = theme.cosmic().frosted_system_interface;
let direction = Arc::new(Mutex::new(direction));
ResizeIndicator {
edges: ResizeEdge::all(),
direction: direction.clone(),
size: Size::default(),
center_elem: IcedElement::new(
ResizeIndicatorInternal {
shortcut1: config
.shortcuts
.iter()
.find_map(|(pattern, action)| {
(*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 edges: Mutex<ResizeEdge>,
pub direction: ResizeDirection,
pub shortcut1: String,
pub shortcut2: String,
shortcut1: String,
shortcut2: String,
}
impl Program for ResizeIndicatorInternal {
type Message = ();
fn view(&self) -> cosmic::Element<'_, Self::Message> {
let edges = self.edges.lock().unwrap();
let icon_container_style = || {
theme::Container::custom(|theme| container::Style {
row(vec![
text::heading(&self.shortcut1).into(),
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,
icon_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 {
radius: 18.0.into(),
radius: theme.cosmic().radius_s().into(),
width: 0.0,
color: Color::TRANSPARENT,
},
shadow: Default::default(),
})
};
column(vec![
if edges.contains(ResizeEdge::TOP) {
from_name(if self.direction == ResizeDirection::Outwards {
"go-up-symbolic"
} else {
"go-down-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()
},
row(vec![
if edges.contains(ResizeEdge::LEFT) {
from_name(if self.direction == ResizeDirection::Outwards {
"go-previous-symbolic"
} else {
"go-next-symbolic"
})
.size(20)
.prefer_svg(true)
.apply(container)
.padding(8)
.class(icon_container_style())
.width(Length::Shrink)
.apply(container)
.center_y(Length::Fill)
.into()
} else {
space::horizontal().width(36).into()
},
row(vec![
text::heading(&self.shortcut1).into(),
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(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()
},
])
}
}))
.width(Length::Shrink)
.height(Length::Shrink)
.into()
}
}
pub struct ResizeIndicatorArrow {
direction: Arc<Mutex<ResizeDirection>>,
icon_outwards: &'static str,
icon_inwards: &'static str,
}
impl Program for ResizeIndicatorArrow {
type Message = ();
fn view(&self) -> cosmic::Element<'_, Self::Message> {
from_name(
if *self.direction.lock().unwrap() == ResizeDirection::Outwards {
self.icon_outwards
} else {
self.icon_inwards
},
)
.size(20)
.prefer_svg(true)
.apply(container)
.padding(8)
.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,
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)
.into()
}
}

View file

@ -4,11 +4,8 @@ use super::{
};
use crate::{
backend::render::{
IndicatorShader, Key, Usage,
clipped_surface::ClippedSurfaceRenderElement,
cursor::CursorState,
element::{AsGlowRenderer, FromGlesError},
shadow::ShadowShader,
IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer,
shadow::ShadowShader, wayland::SurfaceRenderElement,
},
hooks::{Decorations, HOOKS},
shell::{
@ -19,17 +16,20 @@ use crate::{
},
state::State,
utils::{
iced::{IcedElement, Program},
iced::{IcedElement, IcedRenderElement, Program},
prelude::*,
},
};
use calloop::LoopHandle;
use cosmic::{
Apply, Element as CosmicElement, Theme,
iced::{Alignment, id::Id, widget as iced_widget},
iced_core::{Background, Border, Color, Length, border::Radius},
iced_runtime::Task,
iced_widget::scrollable::AbsoluteOffset,
iced::{
Alignment,
core::{Background, Border, Color, Length, border::Radius},
id::Id,
runtime::Task,
widget::{self as iced_widget, scrollable::AbsoluteOffset},
},
theme, widget as cosmic_widget,
};
use cosmic_comp_config::AppearanceConfig;
@ -37,14 +37,11 @@ use cosmic_settings_config::shortcuts;
use shortcuts::action::{Direction, FocusDirection};
use smithay::{
backend::{
drm::DrmNode,
input::KeyState,
renderer::{
ImportAll, ImportMem, Renderer,
element::{
AsRenderElements, Element, Id as RendererId, Kind, RenderElement,
UnderlyingStorage, memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
},
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
gles::element::PixelShaderElement,
glow::GlowRenderer,
utils::{CommitCounter, DamageSet, OpaqueRegions},
@ -149,9 +146,10 @@ impl CosmicStack {
pub fn new<I: Into<CosmicSurface>>(
windows: impl Iterator<Item = I>,
handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
mut theme: cosmic::Theme,
appearance: AppearanceConfig,
) -> CosmicStack {
theme.transparent = theme.cosmic().frosted_windows;
let windows = windows.map(Into::into).collect::<Vec<_>>();
assert!(!windows.is_empty());
@ -532,7 +530,7 @@ impl CosmicStack {
let geo = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry();
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)
|| (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0)
|| (point_i32.x - geo.loc.x >= geo.size.w
@ -648,30 +646,38 @@ impl CosmicStack {
self.0.loop_handle()
}
pub fn popup_render_elements<R, C>(
pub fn push_popup_render_elements<R>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
) -> Vec<C>
where
scanout_node: Option<DrmNode>,
push: &mut dyn FnMut(CosmicStackRenderElement<R>),
) where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
C: From<CosmicStackRenderElement<R>>,
{
let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32));
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 frosted = if theme.cosmic().frosted_windows {
(theme.cosmic().frosted as u8 + 1) as usize
} else {
0
};
windows[active]
.popup_render_elements::<R, CosmicStackRenderElement<R>>(
renderer, window_loc, scale, alpha,
)
.into_iter()
.map(C::from)
.collect()
windows[active].push_popup_render_elements(
renderer,
window_loc,
scale,
alpha,
scanout_node,
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,
renderer: &mut R,
location: Point<i32, Physical>,
@ -750,17 +756,18 @@ impl CosmicStack {
scale: Scale<f64>,
alpha: f32,
scanout_override: Option<bool>,
) -> Vec<C>
where
scanout_node: Option<DrmNode>,
push_above: &mut dyn FnMut(CosmicStackRenderElement<R>),
push_below: &mut dyn FnMut(CosmicStackRenderElement<R>),
) where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
C: From<CosmicStackRenderElement<R>>,
{
if !self
.0
.with_program(|p| p.override_alive.load(Ordering::Acquire))
{
return Vec::new();
return;
}
let geometry = self
@ -769,27 +776,36 @@ impl CosmicStack {
.to_physical_precise_round(scale);
let stack_loc = location + geometry.loc;
let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32));
let mut elements = AsRenderElements::<R>::render_elements::<CosmicStackRenderElement<R>>(
&self.0, renderer, stack_loc, scale, alpha,
);
elements.extend(self.0.with_program(|p| {
let frosted = self.0.with_program(|p| {
let theme = p.theme.lock().unwrap();
if theme.cosmic().frosted_windows {
(theme.cosmic().frosted as u8 + 1) as usize
} else {
0
}
});
let radii = 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 appearance = p.appearance_conf.lock().unwrap();
let theme = p.theme.lock().unwrap();
let tiled = p.tiled.load(Ordering::Acquire);
let maximized = windows[active].is_maximized(false);
let round = (appearance.clip_tiled_windows || !tiled) && !maximized;
let radii = round.then(|| {
round.then(|| {
theme
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 4.0 })
.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();
geo.loc += location.to_f64().to_logical(scale);
@ -801,53 +817,49 @@ impl CosmicStack {
let window_key =
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();
CosmicStackRenderElement::Border(IndicatorShader::element(
push_above(CosmicStackRenderElement::Border(IndicatorShader::element(
renderer,
Key::Window(Usage::Border, window_key.clone()),
geo.to_i32_round().as_local(),
1,
radii.unwrap_or([0, 0, 0, 0]),
radii.unwrap_or([0; 4]),
a * alpha,
scale.x,
[r, g, b],
))
});
)));
};
border.into_iter().chain(
windows[active]
.render_elements::<R, WaylandSurfaceRenderElement<R>>(
renderer,
window_loc,
scale,
alpha,
scanout_override,
)
.into_iter()
.map(move |elem| {
let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]);
if radii.is_some_and(|radii| {
ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radii)
}) {
CosmicStackRenderElement::Clipped(ClippedSurfaceRenderElement::new(
renderer,
elem,
scale,
geo,
radii.unwrap(),
))
} else {
CosmicStackRenderElement::Window(elem)
}
}),
)
}));
let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]);
windows[active].push_render_elements(
renderer,
window_loc,
scale,
alpha,
scanout_override,
scanout_node,
radii.is_some(),
radii.unwrap_or([0; 4]),
frosted,
&mut |elem| push_above(elem.into()),
Some(&mut |elem| push_below(elem.into())),
);
});
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| {
*p.theme.lock().unwrap() = theme.clone();
});
@ -1356,16 +1368,19 @@ impl Decorations<CosmicStackInternal, Message> for DefaultDecorations {
.class(theme::Container::custom(move |theme| {
let cosmic_theme = theme.cosmic();
let background = if group_focused {
let mut background = if group_focused {
cosmic_theme.accent_color()
} else {
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 {
snap: true,
icon_color: Some(cosmic_theme.background.on.into()),
text_color: Some(cosmic_theme.background.on.into()),
icon_color: Some(cosmic_theme.background(false).on.into()),
text_color: Some(cosmic_theme.background(false).on.into()),
background: Some(Background::Color(background.into())),
border: Border {
radius,
@ -1877,40 +1892,32 @@ impl TouchTarget<State> for CosmicStack {
}
pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> {
Header(MemoryRenderBufferRenderElement<R>),
Header(IcedRenderElement<R>),
Shadow(PixelShaderElement),
Border(PixelShaderElement),
Window(WaylandSurfaceRenderElement<R>),
Clipped(ClippedSurfaceRenderElement<R>),
Window(SurfaceRenderElement<R>),
}
impl<R: Renderer + ImportAll + ImportMem> From<MemoryRenderBufferRenderElement<R>>
impl<R: Renderer + ImportAll + ImportMem> From<IcedRenderElement<R>>
for CosmicStackRenderElement<R>
{
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self {
fn from(value: IcedRenderElement<R>) -> Self {
Self::Header(value)
}
}
impl<R: Renderer + ImportAll + ImportMem> From<WaylandSurfaceRenderElement<R>>
impl<R: Renderer + ImportAll + ImportMem> From<SurfaceRenderElement<R>>
for CosmicStackRenderElement<R>
{
fn from(value: WaylandSurfaceRenderElement<R>) -> Self {
fn from(value: SurfaceRenderElement<R>) -> Self {
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>
where
R: Renderer + ImportAll + ImportMem,
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
R::TextureId: Send + 'static,
{
fn id(&self) -> &RendererId {
match self {
@ -1918,7 +1925,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.id(),
CosmicStackRenderElement::Border(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::Border(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::Border(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::Border(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::Border(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::Border(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::Border(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::Border(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::Border(elem) => elem.alpha(),
CosmicStackRenderElement::Window(elem) => elem.alpha(),
CosmicStackRenderElement::Clipped(elem) => elem.alpha(),
}
}
@ -2012,7 +2010,6 @@ where
CosmicStackRenderElement::Shadow(elem) => elem.kind(),
CosmicStackRenderElement::Border(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::Border(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>
where
R: AsGlowRenderer,
R::TextureId: 'static,
R::Error: FromGlesError,
R::TextureId: Send + 'static,
{
fn draw(
&self,
frame: &mut <R>::Frame<'_, '_>,
frame: &mut R::Frame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
@ -2056,14 +2051,11 @@ where
opaque_regions,
cache,
)
.map_err(FromGlesError::from_gles_error)
.map_err(R::from_gles_error)
}
CosmicStackRenderElement::Window(elem) => {
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())
}
CosmicStackRenderElement::Window(elem) => elem.underlying_storage(renderer),
CosmicStackRenderElement::Clipped(elem) => elem.underlying_storage(renderer),
}
}
@ -2097,14 +2088,11 @@ where
dst,
cache,
)
.map_err(FromGlesError::from_gles_error)
.map_err(R::from_gles_error)
}
CosmicStackRenderElement::Window(elem) => {
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,
iced::{
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,
widget::{Icon, icon::from_name, text},
};
@ -65,7 +64,7 @@ impl From<TabBackgroundTheme> for theme::Container<'_> {
icon_color: Some(Color::from(theme.cosmic().accent_text_color())),
text_color: Some(Color::from(theme.cosmic().accent_text_color())),
background: Some(Background::Color(
theme.cosmic().primary.component.selected.into(),
theme.cosmic().primary(false).component.selected.into(),
)),
border: Border {
radius: 0.0.into(),
@ -81,7 +80,7 @@ impl From<TabBackgroundTheme> for theme::Container<'_> {
icon_color: None,
text_color: None,
background: Some(Background::Color(
theme.cosmic().primary.component.base.into(),
theme.cosmic().primary(false).component.base.into(),
)),
border: Border {
radius: 0.0.into(),
@ -289,8 +288,8 @@ where
.min_height(size.height)
.width(size.width)
.height(size.height);
cosmic::iced_core::layout::flex::resolve(
cosmic::iced_core::layout::flex::Axis::Horizontal,
cosmic::iced::core::layout::flex::resolve(
cosmic::iced::core::layout::flex::Axis::Horizontal,
renderer,
&limits,
Length::Fill,

View file

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

View file

@ -1,6 +1,7 @@
use crate::{
backend::render::element::AsGlowRenderer,
fl,
utils::iced::{IcedElement, Program},
utils::iced::{IcedElement, IcedRenderElement, Program},
};
use calloop::LoopHandle;
@ -8,22 +9,78 @@ use cosmic::{
Apply,
iced::{
Alignment,
core::{Background, Border, Color, Length},
widget::{container, row},
},
iced_core::{Background, Border, Color, Length},
theme,
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(
evlh: LoopHandle<'static, crate::state::State>,
size: Size<i32, Logical>,
theme: cosmic::Theme,
) -> StackHover {
StackHover::new(StackHoverInternal, size, evlh, theme)
impl StackHover {
pub fn new(
evlh: LoopHandle<'static, crate::state::State>,
size: Size<i32, Logical>,
mut theme: cosmic::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;
@ -47,23 +104,30 @@ impl Program for StackHoverInternal {
.align_y(Alignment::Center)
.padding(16)
.apply(container)
.class(theme::Container::custom(|theme| container::Style {
snap: true,
icon_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())),
border: Border {
radius: 18.0.into(),
width: 0.0,
color: Color::TRANSPARENT,
},
shadow: Default::default(),
.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,
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)
.height(Length::Shrink)
.apply(container)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into()
}
}

View file

@ -1,7 +1,12 @@
use crate::{
backend::render::{
element::AsGlowRenderer,
wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree},
},
shell::focus::target::PointerFocusTarget,
wayland::{
handlers::compositor::frame_time_filter_fn, protocols::corner_radius::CacheableCorners,
wayland::handlers::{
background_effect::ComputedBlurRegionCachedState, compositor::frame_time_filter_fn,
corner_radius::surface_corners,
},
};
use std::{
@ -14,11 +19,12 @@ use std::{
};
use smithay::{
backend::renderer::{
ImportAll, Renderer,
element::{
AsRenderElements, Kind, RenderElementStates,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
backend::{
drm::DrmNode,
renderer::{
ImportAll, Renderer, buffer_has_alpha,
element::{Kind, RenderElementStates, surface::KindEvaluation},
utils::RendererSurfaceStateUserData,
},
},
desktop::{
@ -45,10 +51,12 @@ use smithay::{
IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, user_data::UserDataMap,
},
wayland::{
alpha_modifier::AlphaModifierSurfaceCachedState,
compositor::{
SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states,
with_surface_tree_downward,
},
dmabuf::get_dmabuf,
seat::WaylandFocus,
shell::xdg::{
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)]
pub struct CosmicSurface(pub Window);
@ -144,22 +229,7 @@ impl CosmicSurface {
pub fn corner_radius(&self, geometry_size: Size<i32, Logical>) -> Option<[u8; 4]> {
self.wl_surface().and_then(|surface| {
with_states(&surface, |states| {
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),
])
})
with_states(&surface, |states| surface_corners(states, geometry_size))
})
}
@ -810,97 +880,112 @@ impl CosmicSurface {
self.0.user_data()
}
pub fn popup_render_elements<R, C>(
pub fn push_popup_render_elements<R>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
) -> Vec<C>
where
R: Renderer + ImportAll,
scanout_node: Option<DrmNode>,
blur_strength: usize,
push: &mut dyn FnMut(SurfaceRenderElement<R>),
) where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static,
C: From<WaylandSurfaceRenderElement<R>>,
{
match self.0.underlying_surface() {
WindowSurface::Wayland(toplevel) => {
let surface = toplevel.wl_surface();
PopupManager::popups_for_surface(surface)
.flat_map(move |(popup, popup_offset)| {
let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc)
.to_physical_precise_round(scale);
render_elements_from_surface_tree(
renderer,
popup.wl_surface(),
location + offset,
scale,
alpha,
FRAME_TIME_FILTER,
)
for (popup, popup_offset) in PopupManager::popups_for_surface(surface) {
let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc)
.to_physical_precise_round(scale);
let mut geometry = popup.geometry().to_f64();
geometry.loc += location.to_f64().to_logical(scale) + popup_offset.to_f64();
let radii = with_states(popup.wl_surface(), |states| {
surface_corners(states, geometry.size.to_i32_round())
})
.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,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
scanout_override: Option<bool>,
) -> Vec<C>
where
R: Renderer + ImportAll,
scanout_node: Option<DrmNode>,
should_clip: bool,
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,
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() {
WindowSurface::Wayland(toplevel) => {
let surface = toplevel.wl_surface();
render_elements_from_surface_tree(
push_render_elements_from_surface_tree(
renderer,
surface,
location,
geometry,
scale,
alpha,
scanout_override
.map(|val| {
if val {
Kind::ScanoutCandidate
} else {
Kind::Unspecified
}
.into()
})
.unwrap_or(FRAME_TIME_FILTER),
should_clip,
radii,
None,
blur_strength,
scanout_kind_eval(scanout_override, scanout_node, alpha),
push_above,
push_below,
)
}
WindowSurface::X11(surface) => {
let Some(surface) = surface.wl_surface() else {
return Vec::new();
return;
};
render_elements_from_surface_tree(
push_render_elements_from_surface_tree(
renderer,
&surface,
location,
geometry,
scale,
alpha,
scanout_override
.map(|val| {
if val {
Kind::ScanoutCandidate
} else {
Kind::Unspecified
}
.into()
})
.unwrap_or(FRAME_TIME_FILTER),
should_clip,
radii,
None,
blur_strength,
scanout_kind_eval(scanout_override, scanout_node, alpha),
push_above,
push_below,
)
}
}
@ -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>(
toplevel: &ToplevelSurface,
pending: bool,

View file

@ -1,25 +1,88 @@
use crate::{
backend::render::element::AsGlowRenderer,
fl,
utils::iced::{IcedElement, Program},
utils::iced::{IcedElement, IcedRenderElement, Program},
};
use calloop::LoopHandle;
use cosmic::{
Apply,
iced::widget::{container, row, space},
iced_core::{Alignment, Background, Border, Color, Length},
iced::{
core::{Alignment, Background, Border, Color, Length},
widget::{container, row, space},
},
theme,
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(
evlh: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> SwapIndicator {
SwapIndicator::new(SwapIndicatorInternal, Size::from((1, 1)), evlh, theme)
impl SwapIndicator {
pub fn new(
evlh: LoopHandle<'static, crate::state::State>,
mut theme: cosmic::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;
@ -43,23 +106,30 @@ impl Program for SwapIndicatorInternal {
.align_y(Alignment::Center)
.padding(16)
.apply(container)
.class(theme::Container::custom(|theme| container::Style {
snap: true,
icon_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())),
border: Border {
radius: 18.0.into(),
width: 0.0,
color: Color::TRANSPARENT,
},
shadow: Default::default(),
.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,
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)
.height(Length::Shrink)
.apply(container)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into()
}
}

View file

@ -1,10 +1,7 @@
use crate::{
backend::render::{
IndicatorShader, Key, Usage,
clipped_surface::ClippedSurfaceRenderElement,
cursor::CursorState,
element::{AsGlowRenderer, FromGlesError},
shadow::ShadowShader,
IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer,
shadow::ShadowShader, wayland::SurfaceRenderElement,
},
hooks::{Decorations, HOOKS},
shell::{
@ -14,7 +11,7 @@ use crate::{
},
state::State,
utils::{
iced::{IcedElement, Program},
iced::{IcedElement, IcedRenderElement, Program},
prelude::*,
},
};
@ -23,14 +20,11 @@ use cosmic::iced::{Color, Task};
use cosmic_comp_config::AppearanceConfig;
use smithay::{
backend::{
drm::DrmNode,
input::KeyState,
renderer::{
ImportAll, ImportMem, Renderer,
element::{
AsRenderElements, Element, Id as RendererId, Kind, RenderElement,
UnderlyingStorage, memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
},
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
gles::element::PixelShaderElement,
glow::GlowRenderer,
utils::{CommitCounter, DamageSet, OpaqueRegions},
@ -119,7 +113,7 @@ impl Focus {
location: Point<f64, Logical>,
) -> Option<Focus> {
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 {
Some(Focus::ResizeTopLeft)
} else if loc.y < 0 && loc.x >= geo.size.w {
@ -196,12 +190,13 @@ impl CosmicWindow {
pub fn new(
window: impl Into<CosmicSurface>,
handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
mut theme: cosmic::Theme,
appearance: AppearanceConfig,
) -> CosmicWindow {
let window = window.into();
let width = window.geometry().size.w;
let last_title = window.title();
theme.transparent = theme.cosmic().frosted_windows;
if appearance.clip_floating_windows {
window.set_tiled(true);
@ -297,7 +292,7 @@ impl CosmicWindow {
{
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 };
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()
}
pub fn popup_render_elements<R, C>(
pub fn push_popup_render_elements<R>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
) -> Vec<C>
where
R: Renderer + ImportAll + ImportMem,
scanout_node: Option<DrmNode>,
push: &mut dyn FnMut(CosmicWindowRenderElement<R>),
) where
R: Renderer + AsGlowRenderer + ImportAll + ImportMem,
R::TextureId: Send + Clone + 'static,
C: From<CosmicWindowRenderElement<R>>,
{
let has_ssd = self.0.with_program(|p| p.has_ssd(false));
@ -372,13 +367,22 @@ impl CosmicWindow {
};
self.0.with_program(|p| {
p.window
.popup_render_elements::<R, CosmicWindowRenderElement<R>>(
renderer, window_loc, scale, alpha,
)
.into_iter()
.map(C::from)
.collect()
let theme = p.theme.lock().unwrap();
let frosted = if theme.cosmic().frosted_windows {
(theme.cosmic().frosted as u8 + 1) as usize
} else {
0
};
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,
renderer: &mut R,
location: Point<i32, Physical>,
@ -470,11 +474,12 @@ impl CosmicWindow {
scale: Scale<f64>,
alpha: f32,
scanout_override: Option<bool>,
) -> Vec<C>
where
scanout_node: Option<DrmNode>,
push_above: &mut dyn FnMut(CosmicWindowRenderElement<R>),
push_below: &mut dyn FnMut(CosmicWindowRenderElement<R>),
) where
R: AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
C: From<CosmicWindowRenderElement<R>>,
{
let (has_ssd, is_tiled, is_maximized, mut radii, appearance) = self.0.with_program(|p| {
(
@ -511,8 +516,6 @@ impl CosmicWindow {
location
};
let mut elements = Vec::new();
let (mut geo, bg_divider) = self.0.with_program(|p| {
(
SpaceElement::geometry(&p.window).to_f64(),
@ -542,55 +545,57 @@ impl CosmicWindow {
scale.x,
[r, g, b],
));
elements.push(elem);
push_above(elem);
}
let window_elements = self.0.with_program(|p| {
p.window
.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| {
self.0.with_program(|p| {
let mut radii = radii;
if has_ssd {
radii[1] = 0;
radii[3] = 0;
}
if radii.iter().any(|x| *x != 0)
&& clip
&& ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radii)
{
CosmicWindowRenderElement::Clipped(ClippedSurfaceRenderElement::new(
renderer, elem, scale, geo, radii,
))
let theme = p.theme.lock().unwrap();
let frosted = if theme.cosmic().frosted_windows {
(theme.cosmic().frosted as u8 + 1) as usize
} 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 {
radii[0] = 0;
radii[2] = 0;
let ssd_loc = location
+ self
.0
.with_program(|p| p.window.geometry().loc)
.to_physical_precise_round(scale);
elements.extend(AsRenderElements::<R>::render_elements::<
CosmicWindowRenderElement<R>,
>(&self.0, renderer, ssd_loc, scale, alpha))
.with_program(|p| p.window.geometry().loc.to_physical_precise_round(scale));
self.0.push_render_elements(
renderer,
ssd_loc,
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| {
*p.theme.lock().unwrap() = theme.clone();
});
@ -826,7 +831,7 @@ impl Program for CosmicWindowInternal {
fn background_color(&self, theme: &cosmic::Theme) -> Color {
if self.window.is_maximized(false) {
theme.cosmic().background.base.into()
theme.cosmic().background(false).base.into()
} else {
Color::TRANSPARENT
}
@ -1243,41 +1248,33 @@ impl WaylandFocus for CosmicWindow {
}
}
pub enum CosmicWindowRenderElement<R: Renderer + ImportAll + ImportMem> {
Header(MemoryRenderBufferRenderElement<R>),
pub enum CosmicWindowRenderElement<R: AsGlowRenderer + ImportAll + ImportMem> {
Header(IcedRenderElement<R>),
Shadow(PixelShaderElement),
Border(PixelShaderElement),
Window(WaylandSurfaceRenderElement<R>),
Clipped(ClippedSurfaceRenderElement<R>),
Window(SurfaceRenderElement<R>),
}
impl<R: Renderer + ImportAll + ImportMem> From<MemoryRenderBufferRenderElement<R>>
impl<R: AsGlowRenderer + ImportAll + ImportMem> From<IcedRenderElement<R>>
for CosmicWindowRenderElement<R>
{
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self {
fn from(value: IcedRenderElement<R>) -> Self {
Self::Header(value)
}
}
impl<R: Renderer + ImportAll + ImportMem> From<WaylandSurfaceRenderElement<R>>
impl<R: AsGlowRenderer + ImportAll + ImportMem> From<SurfaceRenderElement<R>>
for CosmicWindowRenderElement<R>
{
fn from(value: WaylandSurfaceRenderElement<R>) -> Self {
fn from(value: SurfaceRenderElement<R>) -> Self {
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>
where
R: Renderer + ImportAll + ImportMem,
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
R::TextureId: Send + 'static,
{
fn id(&self) -> &RendererId {
match self {
@ -1285,7 +1282,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.id(),
CosmicWindowRenderElement::Border(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::Border(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::Border(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::Border(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::Border(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::Border(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::Border(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::Border(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::Border(elem) => elem.alpha(),
CosmicWindowRenderElement::Window(elem) => elem.alpha(),
CosmicWindowRenderElement::Clipped(elem) => elem.alpha(),
}
}
@ -1379,7 +1367,6 @@ where
CosmicWindowRenderElement::Shadow(elem) => elem.kind(),
CosmicWindowRenderElement::Border(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::Border(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>
where
R: AsGlowRenderer,
R::TextureId: 'static,
R::Error: FromGlesError,
R::TextureId: Send + 'static,
{
fn draw(
&self,
@ -1423,14 +1408,11 @@ where
opaque_regions,
cache,
)
.map_err(FromGlesError::from_gles_error)
.map_err(R::from_gles_error)
}
CosmicWindowRenderElement::Window(elem) => {
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())
}
CosmicWindowRenderElement::Window(elem) => elem.underlying_storage(renderer),
CosmicWindowRenderElement::Clipped(elem) => elem.underlying_storage(renderer),
}
}
@ -1464,14 +1445,11 @@ where
dst,
cache,
)
.map_err(FromGlesError::from_gles_error)
.map_err(R::from_gles_error)
}
CosmicWindowRenderElement::Window(elem) => {
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()
.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)
.map(|(focus, loc)| (focus, loc.as_logical()));
//drop here to avoid multiple borrows

View file

@ -32,10 +32,12 @@ pub enum Stage<'a> {
layer: LayerSurface,
popup: &'a PopupKind,
location: Point<i32, Global>,
workspace_idx: usize,
},
LayerSurface {
layer: LayerSurface,
location: Point<i32, Global>,
workspace_idx: usize,
},
OverrideRedirect {
surface: &'a X11Surface,
@ -95,10 +97,15 @@ fn render_input_order_internal<R: 'static>(
layer,
popup: &popup,
location,
workspace_idx: current.1,
})?;
}
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
@ -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) {
(WorkspaceLayout::Vertical, true) => (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,
popup: &popup,
location,
workspace_idx: current.1,
})?;
}
}
@ -236,7 +244,7 @@ fn render_input_order_internal<R: 'static>(
if element_filter != ElementFilter::LayerShellOnly {
// 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 {
return ControlFlow::Break(Err(OutputNoMode));
};
@ -265,11 +273,12 @@ fn render_input_order_internal<R: 'static>(
layer,
popup: &popup,
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
{
// previous bottom layer popups
@ -278,6 +287,7 @@ fn render_input_order_internal<R: 'static>(
layer,
popup: &popup,
location: location + offset.as_global(),
workspace_idx: **idx,
})?;
}
}
@ -289,11 +299,12 @@ fn render_input_order_internal<R: 'static>(
layer,
popup: &popup,
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
{
// previous background layer popups
@ -302,6 +313,7 @@ fn render_input_order_internal<R: 'static>(
layer,
popup: &popup,
location: location + offset.as_global(),
workspace_idx: **idx,
})?;
}
}
@ -309,7 +321,11 @@ fn render_input_order_internal<R: 'static>(
if !has_focused_fullscreen {
// top-layer shell
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
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 {
return ControlFlow::Break(Err(OutputNoMode));
};
@ -341,17 +357,25 @@ fn render_input_order_internal<R: 'static>(
// bottom layer
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
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
{
// previous bottom layer
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
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
for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) {
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
{
// previous background layer
for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) {
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::{
shell::{
CosmicSurface, SeatExt,
CosmicSurface, CursorGeometry, SeatExt,
element::{CosmicMapped, CosmicStack, CosmicWindow},
layout::tiling::ResizeForkTarget,
zoom::ZoomFocusTarget,
@ -272,7 +272,7 @@ impl PointerFocusTarget {
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())
{
hotspot

View file

@ -1,10 +1,12 @@
use cosmic::{
iced::Element,
iced_core::{
Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle,
Renderer as IcedRenderer, Shell, Size, layout, mouse, overlay,
renderer::{Quad, Style},
widget::{Id, Tree, Widget, tree},
iced::{
Alignment, Element,
core::{
Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle,
Renderer as IcedRenderer, Shell, Size, layout, mouse, overlay,
renderer::{Quad, Style},
widget::{Id, Tree, Widget, tree},
},
},
widget::button::Catalog,
};
@ -73,11 +75,26 @@ where
viewport: &Rectangle,
) {
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)
} else {
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(
Quad {
@ -85,7 +102,7 @@ where
bounds: layout.bounds(),
border: Border {
radius: styling.border_radius,
width: styling.border_width,
width: 0.,
color: styling.border_color,
},
shadow: Default::default(),

View file

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

View file

@ -6,10 +6,7 @@ use crate::{
},
shell::{
CosmicMapped, CosmicSurface, Direction, ManagedLayer,
element::{
CosmicMappedRenderElement,
stack_hover::{StackHover, stack_hover},
},
element::{CosmicMappedRenderElement, stack_hover::StackHover},
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
layout::floating::TiledCorners,
},
@ -19,12 +16,14 @@ use crate::{
use calloop::LoopHandle;
use cosmic::theme::CosmicTheme;
use smallvec::SmallVec;
use smithay::{
backend::{
drm::DrmNode,
input::ButtonState,
renderer::{
ImportAll, ImportMem, Renderer,
element::{AsRenderElements, RenderElement, utils::RescaleRenderElement},
ImportAll, ImportMem,
element::{RenderElement, utils::RescaleRenderElement},
},
},
desktop::{WindowSurfaceType, layer_map_for_output, space::SpaceElement},
@ -68,12 +67,17 @@ pub struct MoveGrabState {
impl MoveGrabState {
#[profiling::function]
pub fn render<I, R>(&self, renderer: &mut R, output: &Output, theme: &CosmicTheme) -> Vec<I>
where
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
pub fn render<R>(
&self,
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,
CosmicMappedRenderElement<R>: RenderElement<R>,
I: From<CosmicMappedRenderElement<R>>,
{
let scale = if self.previous == ManagedLayer::Tiling {
0.6 + ((1.0
@ -98,7 +102,7 @@ impl MoveGrabState {
.intersection(window_geo)
.is_none()
{
return Vec::new();
return;
}
let output_scale: Scale<f64> = output.current_scale().fractional_scale().into();
@ -108,13 +112,33 @@ impl MoveGrabState {
+ self.window_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 radius = self
.element()
.corner_radius(window_geo.size, self.indicator_thickness);
let focus_element = if self.indicator_thickness > 0 {
Some(CosmicMappedRenderElement::from(
if self.indicator_thickness > 0 {
push(
IndicatorShader::focus_element(
renderer,
Key::Window(Usage::MoveGrabIndicator, self.window.key()),
@ -137,12 +161,57 @@ impl MoveGrabState {
active_window_hint.green,
active_window_hint.blue,
],
),
))
} else {
None
)
.into(),
)
}
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 layers = layer_map_for_output(output);
layers.non_exclusive_zone()
@ -151,117 +220,46 @@ impl MoveGrabState {
let gaps = (theme.gaps.0 as i32, theme.gaps.1 as i32);
let thickness = self.indicator_thickness.max(1);
let snapping_indicator = match &self.snapping_zone {
Some(t) if &self.cursor_output == output => {
let base_color = theme.palette.neutral_9;
let overlay_geometry = t.overlay_geometry(non_exclusive_geometry, gaps);
vec![
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![],
};
if let Some(t) = &self.snapping_zone
&& &self.cursor_output == output
{
let base_color = theme.palette.neutral_9;
let overlay_geometry = t.overlay_geometry(non_exclusive_geometry, gaps);
let w_elements = self
.window
.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(
push(
IndicatorShader::element(
renderer,
location.to_physical_precise_round(output_scale),
output_scale,
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,
],
)
})
.chain(p_elements)
.chain(focus_element)
.chain(
w_elements
.into_iter()
.chain(shadow_element)
.map(|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,
}),
.into(),
);
push(
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],
)
.into(),
)
.chain(snapping_indicator)
.map(I::from)
.collect()
}
}
pub fn element(&self) -> CosmicMapped {
@ -441,7 +439,7 @@ impl MoveGrab {
if let Some(indicator) =
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) {
@ -455,16 +453,14 @@ impl MoveGrab {
let indicator_location = shell.stacking_indicator(&current_output, self.previous);
if indicator_location.is_some() != grab_state.stacking_indicator.is_some() {
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(),
geo.size.as_logical(),
size,
state.common.theme.clone(),
);
for output in &self.window_outputs {
element.output_enter(
output,
Rectangle::from_size(output.geometry().size.as_logical()),
);
element.output_enter(output);
}
(element, geo.loc.as_logical())
});

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -3,15 +3,15 @@ use std::{sync::Mutex, time::Instant};
use calloop::LoopHandle;
use cosmic::{
Apply,
iced::{Alignment, Background, Border, Length, alignment::Vertical},
iced_widget, theme,
iced::{Alignment, Background, Border, Length, alignment::Vertical, widget as iced_widget},
theme,
widget::{self, icon::Named},
};
use cosmic_comp_config::ZoomMovement;
use cosmic_config::ConfigSet;
use keyframe::{ease, functions::Linear};
use smithay::{
backend::renderer::{ImportMem, Renderer, element::AsRenderElements},
backend::renderer::ImportMem,
desktop::space::SpaceElement,
input::{
Seat,
@ -27,14 +27,15 @@ use smithay::{
},
},
output::Output,
utils::{IsAlive, Point, Rectangle, Serial, Size},
utils::{FrameExtents, IsAlive, Point, Rectangle, Serial, Size},
};
use tracing::error;
use crate::{
backend::render::element::AsGlowRenderer,
state::State,
utils::{
iced::{IcedElement, Program},
iced::{IcedElement, IcedRenderElement, Program},
prelude::*,
tween::EasePoint,
},
@ -71,45 +72,17 @@ impl OutputZoomState {
increment: u32,
movement: ZoomMovement,
loop_handle: LoopHandle<'static, State>,
theme: cosmic::Theme,
mut theme: cosmic::Theme,
) -> OutputZoomState {
theme.transparent = theme.cosmic().frosted_system_interface;
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 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 element = IcedElement::new(program, Size::default(), loop_handle, theme);
let mut size = element.minimum_size();
size.w = (size.w + 32/*TODO: figure out why iced is calculating too little*/)
.min(output_geometry.size.w.round() as i32);
size.w = size.w.min(output_geometry.size.w.round() as i32);
element.set_activate(true);
element.resize(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>
where
C: From<<IcedElement<ZoomProgram> as AsRenderElements<R>>::RenderElement>,
R: Renderer + ImportMem,
fn render<R>(
&mut self,
renderer: &mut R,
output: &Output,
push: &mut dyn FnMut(IcedRenderElement<R>),
) where
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
let size = self.element.current_size().to_f64();
@ -208,8 +184,17 @@ impl OutputZoomState {
.to_physical(scale.fractional_scale())
.to_i32_round();
self.element
.render_elements(renderer, location, scale.fractional_scale().into(), 1.0)
self.element.push_render_elements(
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,
output: &Output,
cursor_position: Point<f64, Global>,
original_position: Point<f64, Global>,
_original_position: Point<f64, Global>,
movement: ZoomMovement,
) {
let output_geometry = output.geometry().to_f64();
let mut zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64();
let output_geometry = output.geometry().to_f64().to_local(output);
let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64().to_local(output);
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
let mut output_state_ref = output_state.lock().unwrap();
// animate movement type changes
if self.movement != movement {
let level = output_state_ref.current_level();
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()));
self.movement = movement;
}
let cursor_position = cursor_position.to_local(output);
match movement {
ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position,
ZoomMovement::OnEdge => {
if !zoomed_output_geometry
.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::Continuously => {
// Focal point is the cursor position
output_state_ref.focal_point = cursor_position;
}
ZoomMovement::Centered => {
zoomed_output_geometry.loc = cursor_position.to_global(output)
- zoomed_output_geometry.size.downscale(2.).to_point();
ZoomMovement::OnEdge => {
if is_level_change {
// Ensure the cursor doesn't disappear off screen
output_state_ref.focal_point = cursor_position;
return;
}
let mut focal_point = zoomed_output_geometry
.loc
.to_local(output)
.upscale(
(output_geometry.size.w
/ (output_geometry.size.w - zoomed_output_geometry.size.w)
.max(f64::EPSILON))
.max(1.),
)
.to_global(output);
// Compute small margin relative to zoomed output to keep cursor within
// (can be user-configurable in the future)
let margin_size = zoomed_output_geometry.size.h * 0.02;
let margins = FrameExtents::new(margin_size, margin_size, margin_size, margin_size);
let inner_rect = zoomed_output_geometry - margins;
if inner_rect.contains(cursor_position) {
// Do not move if cursor within margins
return;
}
// 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(
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(
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
}
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
C: From<<IcedElement<ZoomProgram> as AsRenderElements<R>>::RenderElement>,
R: Renderer + ImportMem,
R: AsGlowRenderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
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)
}
}
@ -505,7 +504,8 @@ impl Program for ZoomProgram {
.padding(8)
.class(theme::Container::custom(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
let component = &cosmic.background(false).component;
let component = &cosmic.background(false).component;
iced_widget::container::Style {
snap: true,
icon_color: Some(component.on.into()),
@ -581,6 +581,8 @@ impl Program for ZoomProgram {
let level = output_state_ref.level;
std::mem::drop(output_state_ref);
let mut theme = state.common.theme.clone();
theme.transparent = theme.cosmic().frosted_system_interface;
let grab = MenuGrab::new(
start_data,
&seat,
@ -693,7 +695,7 @@ impl Program for ZoomProgram {
),
Some(level.min(4.)),
state.common.event_loop_handle.clone(),
state.common.theme.clone(),
theme,
);
std::mem::drop(shell);
@ -746,6 +748,8 @@ impl Program for ZoomProgram {
let level = output_state_ref.level;
std::mem::drop(output_state_ref);
let mut theme = state.common.theme.clone();
theme.transparent = theme.cosmic().frosted_system_interface;
let grab = MenuGrab::new(
start_data,
&seat,
@ -778,7 +782,7 @@ impl Program for ZoomProgram {
MenuAlignment::PREFER_CENTERED,
Some(level.min(4.)),
state.common.event_loop_handle.clone(),
state.common.theme.clone(),
theme,
);
std::mem::drop(shell);

View file

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

View file

@ -12,15 +12,15 @@ use cosmic::{
iced::{
Limits, Point as IcedPoint, Size as IcedSize, Task,
advanced::{graphics::text::font_system, widget::Tree},
core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style},
event::Event,
futures::{FutureExt, StreamExt},
keyboard::{Event as KeyboardEvent, Modifiers as IcedModifiers},
mouse::{Button as MouseButton, Cursor, Event as MouseEvent, ScrollDelta},
runtime::{Action, task::into_stream},
touch::{Event as TouchEvent, Finger},
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::{
Layer,
@ -33,9 +33,9 @@ use smithay::{
allocator::Fourcc,
input::{ButtonState, KeyState},
renderer::{
ImportMem, Renderer,
ImportMem,
element::{
AsRenderElements, Kind,
Kind,
memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
},
},
@ -57,13 +57,20 @@ use smithay::{
},
output::Output,
reexports::calloop::{self, LoopHandle, RegistrationToken, futures::Scheduler},
render_elements,
utils::{
Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size,
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"));
@ -173,6 +180,7 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
outputs: HashSet<Output>,
buffers: HashMap<OrderedFloat<f64>, (MemoryRenderBuffer, Option<(Vec<Layer>, Color)>)>,
pending_realloc: bool,
blur: BlurState,
// state
size: Size<i32, Logical>,
@ -225,6 +233,7 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
outputs: self.outputs.clone(),
buffers: self.buffers.clone(),
pending_realloc: self.pending_realloc,
blur: BlurState::default(),
size: self.size,
last_seat: self.last_seat.clone(),
cursor_pos: self.cursor_pos,
@ -310,6 +319,7 @@ impl<P: Program + Send + 'static> IcedElement<P> {
outputs: HashSet::new(),
buffers: HashMap::new(),
pending_realloc: false,
blur: BlurState::default(),
size,
cursor_pos: None,
last_seat,
@ -342,7 +352,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
let node = element
.as_widget_mut()
.layout(
// TODO Avoid creating a new tree here?
&mut tree,
&internal.renderer,
&Limits::new(IcedSize::ZERO, IcedSize::INFINITE)
@ -386,6 +395,11 @@ impl<P: Program + Send + 'static> IcedElement<P> {
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) {
let mut guard = self.0.lock().unwrap();
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>
where
P: Program + Send + 'static,
R: Renderer + ImportMem,
R::TextureId: Send + Clone + 'static,
{
type RenderElement = MemoryRenderBufferRenderElement<R>;
fn render_elements<C: From<Self::RenderElement>>(
impl<P: Program + Send + 'static> IcedElement<P> {
pub fn push_render_elements<R>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
mut scale: Scale<f64>,
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();
// makes partial borrows easier
let internal_ref = &mut *internal;
@ -1055,11 +1068,51 @@ where
Kind::Unspecified,
) {
Ok(buffer) => {
return vec![C::from(buffer)];
push_above(buffer.into());
}
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 cosmic::iced::core::event::{self, Event};
use cosmic::iced::core::mouse;
use cosmic::iced::core::renderer;
use cosmic::iced::core::widget::operation::{self, Operation};
use cosmic::iced::core::{Clipboard, Size};
use cosmic::iced_core;
use cosmic::iced_runtime::Task;
use cosmic::iced_runtime::user_interface::{self, UserInterface};
use cosmic::iced::{
core::{
self as iced_core, Clipboard, Size,
event::{self, Event},
mouse, renderer,
widget::operation::{self, Operation},
},
runtime::{
Task,
user_interface::{self, UserInterface},
},
};
/// The execution state of a [`Program`]. It leverages caching, event
/// processing, and rendering primitive storage.
@ -128,7 +132,7 @@ where
messages.append(&mut self.queued_messages);
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, ..
} = state
{
@ -154,7 +158,7 @@ where
let mut user_interface =
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, ..
} = state
{

View file

@ -3,9 +3,7 @@ use smithay::{
backend::{
allocator::Fourcc,
renderer::{
ExportMem, ImportAll, Offscreen, Renderer,
damage::OutputDamageTracker,
element::{AsRenderElements, surface::WaylandSurfaceRenderElement},
ExportMem, ImportAll, Offscreen, Renderer, damage::OutputDamageTracker,
gles::GlesRenderbuffer,
},
},
@ -16,7 +14,7 @@ use smithay::{
use tracing::warn;
use crate::{
backend::render::RendererRef,
backend::render::{RendererRef, element::AsGlowRenderer},
shell::element::CosmicSurface,
state::{State, advertised_node_for_surface},
};
@ -24,17 +22,24 @@ use crate::{
pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) {
fn render_window<R>(renderer: &mut R, window: &CosmicSurface) -> anyhow::Result<()>
where
R: Renderer + ImportAll + Offscreen<GlesRenderbuffer> + ExportMem,
R::TextureId: Clone + 'static,
R: Renderer + ImportAll + Offscreen<GlesRenderbuffer> + ExportMem + AsGlowRenderer,
R::TextureId: Send + Clone + 'static,
R::Error: Send + Sync + 'static,
{
let bbox = bbox_from_surface_tree(&window.wl_surface().unwrap(), (0, 0));
let elements = AsRenderElements::<R>::render_elements::<WaylandSurfaceRenderElement<R>>(
window,
let mut elements = Vec::new();
window.push_render_elements(
renderer,
(-bbox.loc.x, -bbox.loc.y).into(),
Scale::from(1.0),
1.0,
None,
None,
false,
[0; 4],
0,
&mut |elem| elements.push(elem),
None,
);
// 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())
});
if let Some(window) = moved_window {
if window.is_stack() {
let stack = window.stack_ref().unwrap();
if let Some(stack) = window.stack_ref() {
if let Some(i) = stack.surfaces().position(|s| {
s.wl_surface()
.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::{
CornerRadiusData, CornerRadiusHandler, CornerRadiusState, delegate_corner_radius,
CacheableCorners, CacheablePadding, CornerRadiusData, CornerRadiusHandler, CornerRadiusState,
delegate_corner_radius,
};
use crate::state::State;
@ -11,39 +13,63 @@ impl CornerRadiusHandler for State {
&mut self.common.corner_radius_state
}
fn set_corner_radius(
&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 set_corner_radius(&mut self, _data: &CornerRadiusData) {}
fn unset_corner_radius(
&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 reset.");
}
}
fn unset_corner_radius(&mut self, _data: &CornerRadiusData) {}
fn set_padding(&mut self, _data: &CornerRadiusData) {}
fn unset_padding(&mut self, _data: &CornerRadiusData) {}
}
fn force_redraw(state: &mut State, data: &CornerRadiusData) -> Option<()> {
let guard = data.lock().unwrap();
pub fn surface_corners(states: &SurfaceData, size: Size<i32, Logical>) -> Option<[u8; 4]> {
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();
let output = guard.visible_output_for_surface(surface.wl_surface())?;
pub fn surface_padding(states: &SurfaceData, size: Size<i32, Logical>) -> Option<[i32; 4]> {
let mut guard = states.cached_state.get::<CacheablePadding>();
state.backend.schedule_render(output);
Some(())
let padding = guard.current().0?;
// 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);

View file

@ -14,6 +14,7 @@ use smithay::{
},
},
desktop::space::SpaceElement,
input::{Seat, pointer::PointerHandle},
output::Output,
reexports::wayland_server::protocol::{wl_pointer::WlPointer, wl_shm::Format as ShmFormat},
utils::{Buffer as BufferCoords, Point, Size, Transform},
@ -30,7 +31,7 @@ use smithay::{
};
use crate::{
shell::CosmicSurface,
shell::{CosmicSurface, CursorGeometry, Shell},
state::{BackendData, State},
utils::prelude::{
OutputExt, PointExt, PointGlobalExt, PointLocalExt, RectExt, RectLocalExt, SeatExt,
@ -44,6 +45,36 @@ pub use self::render::*;
use self::user_data::*;
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 {
fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {
&mut self.common.image_copy_capture_state
@ -73,26 +104,12 @@ impl ImageCopyCaptureHandler for State {
fn cursor_capture_constraints(
&mut self,
_source: &ImageCaptureSource,
_pointer: &WlPointer,
pointer: &WlPointer,
) -> Option<BufferConstraints> {
let size = if let Some((geometry, _)) = self
.common
.shell
.read()
.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,
})
let shell = self.common.shell.read();
let seat = seat_for_wl_pointer(&shell, pointer)?;
let cursor_geometry = seat.cursor_geometry((0.0, 0.0), self.common.clock.now());
Some(cursor_capture_constraints(cursor_geometry))
}
fn new_session(&mut self, session: Session) {
@ -149,20 +166,23 @@ impl ImageCopyCaptureHandler for State {
fn new_cursor_session(&mut self, session: CursorSession) {
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_loc = pointer.current_location().to_i32_round().as_global();
let (pointer_size, hotspot) = if let Some(CursorGeometry { geometry, hotspot }) =
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)) =
seat.cursor_geometry((0.0, 0.0), self.common.clock.now())
{
(geometry.size, hotspot)
(pointer_loc, pointer_size, hotspot)
} else {
(Size::from((64, 64)), Point::from((0, 0)))
};
(pointer_loc, pointer_size, hotspot)
(Point::new(0, 0), default_cursor_size(), Point::from((0, 0)))
}
};
session.user_data().insert_if_missing_threadsafe(|| {
@ -299,8 +319,11 @@ impl ImageCopyCaptureHandler for State {
return;
}
let seat = self.common.shell.read().seats.last_active().clone();
render_cursor_to_buffer(self, session, frame, &seat);
let shell = self.common.shell.read();
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) {

View file

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

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
pub mod a11y;
pub mod background_effect;
pub mod buffer;
pub mod compositor;
pub mod corner_radius;
@ -18,6 +19,7 @@ pub mod idle_notify;
pub mod image_capture_source;
pub mod image_copy_capture;
pub mod input_method;
pub mod keyboard_layout;
pub mod keyboard_shortcuts_inhibit;
pub mod layer_shell;
pub mod output;

View file

@ -63,7 +63,7 @@ impl PointerConstraintsHandler for State {
if let Some(region) = constraint.region() {
if let Some(surface_location) = surface_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)
{
constraint.activate();

View file

@ -317,8 +317,45 @@ impl XdgShellHandler for State {
}
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 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();
// 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_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::add_pre_commit_hook;
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::{
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
@ -18,6 +27,7 @@ use std::sync::Mutex;
use wayland_backend::server::GlobalId;
type ToplevelHookId = Mutex<Option<(HookId, Weak<CosmicCornerRadiusToplevelV1>)>>;
type LayerHookId = Mutex<Option<(HookId, Weak<CosmicCornerRadiusLayerV1>)>>;
#[derive(Debug)]
pub struct CornerRadiusState {
@ -33,12 +43,13 @@ impl CornerRadiusState {
+ Dispatch<
cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
CornerRadiusData,
> + CornerRadiusHandler
> + Dispatch<cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
let global = dh
.create_global::<D, cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, _>(
1,
2,
(),
);
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 set_corner_radius(
&mut self,
toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
data: &CornerRadiusData,
);
fn unset_corner_radius(
&mut self,
toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
data: &CornerRadiusData,
);
fn set_corner_radius(&mut self, data: &CornerRadiusData);
fn unset_corner_radius(&mut self, data: &CornerRadiusData);
fn set_padding(&mut self, data: &CornerRadiusData);
fn unset_padding(&mut self, data: &CornerRadiusData);
}
impl<D> GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, (), D>
@ -72,6 +77,7 @@ where
D: GlobalDispatch<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_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
@ -96,6 +102,7 @@ where
D: GlobalDispatch<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_layer_v1::CosmicCornerRadiusLayerV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
@ -115,6 +122,71 @@ where
}
cosmic_corner_radius_manager_v1::Request::GetCornerRadius { id, 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 hook_id = surface_data
.data_map
@ -124,63 +196,33 @@ where
});
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"),
);
cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32,
format!(
"{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface"
),
);
}
let data = Mutex::new(CornerRadiusInternal {
toplevel: toplevel.downgrade(),
surface: CornerRadiusSurface::Layer(layer.downgrade()),
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, _>(
surface.wl_surface(),
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"),
);
}
},
);
let hook_id =
add_pre_commit_hook::<D, _>(surface.wl_surface(), layer_radius_hook);
with_states(surface.wl_surface(), |surface_data| {
let hook_ids = surface_data
.data_map
.get_or_insert_threadsafe(|| ToplevelHookId::new(None));
.get_or_insert_threadsafe(|| LayerHookId::new(None));
let mut guard = hook_ids.lock().unwrap();
*guard = Some((hook_id, obj_downgrade));
});
}
}
} // TODO: can this fail?
}
_ => 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>
Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData, D>
for CornerRadiusState
@ -221,30 +312,35 @@ where
let mut guard = data.lock().unwrap();
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;
};
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) {
with_states(surface.wl_surface(), |surface_data| {
if let Some(hook_ids_mutex) = surface_data.data_map.get::<ToplevelHookId>()
{
let mut hook_id = hook_ids_mutex.lock().unwrap();
*hook_id = None;
}
});
}
with_states(&wl_surface, |surface_data| {
if let Some(hook_ids_mutex) = surface_data.data_map.get::<ToplevelHookId>() {
let mut hook_id = hook_ids_mutex.lock().unwrap();
*hook_id = None;
}
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) {
with_states(surface.wl_surface(), |s| {
let mut cached = s.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(None);
});
}
let mut cached = surface_data.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(None);
});
drop(guard);
state.unset_corner_radius(resource, data);
state.unset_corner_radius(data);
}
cosmic_corner_radius_toplevel_v1::Request::SetRadius {
top_left,
@ -254,66 +350,388 @@ where
} => {
let mut guard = data.lock().unwrap();
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(
cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32,
format!("{:?} No toplevel found", resource),
);
return;
};
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) {
with_states(surface.wl_surface(), |s| {
let mut cached = s.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(guard.corners);
});
}
with_states(&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(resource, data);
state.set_corner_radius(data);
}
cosmic_corner_radius_toplevel_v1::Request::UnsetRadius => {
let mut guard = data.lock().unwrap();
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(
cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32,
format!("{:?} No toplevel found", resource),
);
return;
};
if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) {
with_states(surface.wl_surface(), |s| {
let mut cached = s.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(None);
});
}
with_states(&wl_surface, |s| {
let mut cached = s.cached_state.get::<CacheableCorners>();
let pending = cached.pending();
*pending = CacheableCorners(None);
});
drop(guard);
state.unset_corner_radius(resource, data);
state.unset_corner_radius(data);
}
_ => 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>;
#[derive(Debug)]
pub enum CornerRadiusSurface {
Toplevel(Weak<XdgToplevel>),
Popup(Weak<XdgPopup>),
Layer(Weak<ZwlrLayerSurfaceV1>),
}
#[derive(Debug)]
pub struct CornerRadiusInternal {
pub toplevel: Weak<XdgToplevel>,
pub surface: CornerRadiusSurface,
pub corners: Option<Corners>,
pub padding: Option<Padding>,
}
#[derive(Debug, Copy, Clone)]
pub struct Corners {
pub top_left: u8,
pub top_right: u8,
pub bottom_right: u8,
pub bottom_left: u8,
pub top_left: u32,
pub top_right: u32,
pub bottom_right: u32,
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)]
@ -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 {
fn set_corner_radius(
&mut self,
@ -337,13 +767,23 @@ impl CornerRadiusInternal {
bottom_left: u32,
) {
let corners = Corners {
top_left: top_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8,
top_right: top_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8,
bottom_right: bottom_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8,
bottom_left: bottom_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8,
top_left,
top_right,
bottom_right,
bottom_left,
};
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 {
@ -357,6 +797,9 @@ macro_rules! delegate_corner_radius {
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1: CornerRadiusData
] => $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;

View file

@ -180,7 +180,7 @@ where
Modifier::Invalid,
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() {
Some(dmabuf) => {
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 drm;
pub mod image_capture_source;
pub mod keyboard_layout;
pub mod output_configuration;
pub mod output_power;
pub mod overlap_notify;

View file

@ -807,9 +807,17 @@ impl XwmHandler for State {
*context,
);
}
if !shell.pending_windows.iter().any(|w| w.surface == window) {
let fullscreen = window.is_fullscreen().then(|| seat.active_output());
let maximized = window.is_maximized();
let fullscreen = window.is_fullscreen().then(|| seat.active_output());
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);
shell.pending_windows.push(PendingWindow {
surface,