Compare commits

..

94 commits

Author SHA1 Message Date
30d8868cca chore: fige Cargo.lock (epoch-1.9.0, [patch] locaux)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 21:03:54 +02:00
51c6cfedf6 merge: epoch-1.9.0 d'upstream
# Conflicts:
#	Cargo.lock
2026-09-24 20:48:00 +02:00
Ashley Wulber
0fbd4574ef fix: skip loading shortcuts for kiosk mode 2026-09-22 17:12:28 -06:00
Hojjat
d36480c1b5 fix: only unmaximize on activation if maximized != target 2026-09-22 16:55:54 -06:00
Hojjat
50c170c355 fix: only cancel grabs the compositor started for its own interactions 2026-09-22 16:10:23 -04:00
Hojjat
26daf75d93 fix: forward release for modifier-only bindings 2026-09-22 14:22:06 +02:00
Flori Ava Star Weber
cd881a4df2 cli: change incorrect text -v to -V 2026-09-21 13:17:44 +02:00
Hojjat
651f701f84 shell/focus: Don't dismiss grabbing popups of exclusive layer surfaces 2026-09-16 11:04:07 -06:00
Ian Douglas Scott
a7349b72e3 Add cosmic-session-lock-layer-v1 protocol
https://github.com/pop-os/cosmic-protocols/pull/88
2026-09-11 14:38:07 -06:00
Ian Douglas Scott
f9e02f4321 Add comments to functions that iterate over surfaces
These should be kept in sync with `render_input_order_internal`.
2026-09-11 14:38:07 -06:00
a103e7619b chore: fige Cargo.lock 2026-09-10 18:12:37 +02:00
a69f659983 Merge tag 'epoch-1.8.0' into local/yoda-main
Epoch 1.8.0

# Conflicts:
#	Cargo.lock
2026-09-10 17:57:28 +02:00
apassert
a55785993e config: sort outputs by connector for deterministic fallback layout
When no saved configuration matches the current output set, positions
were assigned in hotplug/registration order. On hotplug races (e.g.
reconnecting monitors through a KVM switch) outputs can appear in
arbitrary order, producing a swapped left/right layout that then gets
persisted to outputs.ron, poisoning all future reconnects.

Sort outputs by connector name before generating fallback positions so
the layout is deterministic regardless of arrival order.

AI disclosure: implemented with the assistance of an LLM coding agent,
directed, reviewed, and tested by the author.

Signed-off-by: apassert <249084898+apassert@users.noreply.github.com>
2026-09-09 15:54:44 +02:00
Aleksandr Kosachev
7d1daab7bc fix: remove foreign toplevel handle too 2026-09-08 23:50:31 +02:00
Victoria Brekenfeld
6026520bd3
Merge pull request #2832 from weblate/weblate-pop-os-cosmic-comp 2026-09-08 17:13:58 +02:00
Hosted Weblate
8ad541dbe3
i18n: translation updates from weblate
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/oc/
Translation: Pop OS/COSMIC Comp
2026-09-08 16:51:34 +02:00
haydonryan
f61c0f9f76 tabs: avoid two HashSet allocations in tab-change detection
Replace the symmetric_difference over two freshly-materialized HashSets
with direct contains_key lookups on the existing key sets. Semantically
identical (set difference non-empty iff any key is in exactly one set) but
removes two heap HashSet allocations plus hashing of every key on each
widget update. Also drops the now-unused HashSet import.

Implements compact-idiomatic-code-20 from the cosmic-comp optimization
report. Created with the help of DeepSeek V4 Flash.
2026-09-07 15:42:06 +02:00
Hojjat
0f56d214b9 chore: clippy fixes 2026-09-05 01:16:34 +02:00
Hojjat
057e7b72cc chore: remove the gaurd against large paddings 2026-09-04 16:05:57 -04:00
Hojjat
e8caf335b3 refactor: align the corner radii order with the theme
It's [TL, TR, BR, BL] everywhere now
2026-09-04 13:43:40 +02:00
Victoria Brekenfeld
f8344126a1 input: Update the pointer state on absolute pointer events as well 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
bd0b834128 element/stack: Use correct serial for tablet grabs 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
1c8a0070c0 element/stack: Handle touch (and tablet) inputs on tabs 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
e563dc85bf input: Don't update pointer position on proximity_out 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
2a3b4dca5f shell: fix client initiated tablet grabs 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
0e15b14ab2 utils/iced: Fix tablet serials and buttons 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
6bf75e1c10 shell: Handle Tablet grabs 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
bb95a57f5a move: Make sure to always unset tool grab 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
87d73dbce3 resize/tiling: Set cursor icons on tool input 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
f00ff834c9 resize/tiling: Fix location for tablet tools 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
b8824128a0 element: Add cursor shape and resize handling on tool input 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
e65f6608a4 element: Remove unnecessary grab variants 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
85564f0336 input: Focus on tool tip down 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
a66f629a4e input: Make sure to update internal pointer position state on tool input 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
67aa6a0c49 input: Don't send out pointer events for tool events 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
9125f8a2a7 input: Add pointer-emulation grab for tablet tools 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
6467f79025 shell: Add tablet grabs 2026-09-03 21:00:11 +02:00
Victoria Brekenfeld
ca9dd27350 chore: Fix debug feature by updating smithay-egui 2026-09-03 21:00:11 +02:00
Ian Douglas Scott
8ec1ffb740 protocols/keyboard-layout: Send ei modifiers on group change
This makes `cosmic-osk` show a group change on `super+space`. Previously
a change like that wouldn't be sent until a keyboard input changed the
modifier state, for instance releasing the shift key.
2026-09-03 19:31:56 +02:00
Frederic Laing
0d75928579 chore: refactor throttle function 2026-09-03 17:46:37 +02:00
Frederic Laing
e62e35e5cc fix: keep Xwayland frame callbacks progressing 2026-09-03 17:46:37 +02:00
Hojjat
8691956644 fix: validate layer radius against the pending surface size 2026-09-02 15:32:26 -06:00
Konstantinos
9059873c84 fix: Do not magnify cursor when there is an active grab. 2026-09-02 15:39:42 +02:00
Shehriyar Qureshi
e93ec67a11 fix(kms): typo in log message 2026-09-02 15:23:09 +02:00
Victoria Brekenfeld
a7d31a75d2 backend/kms: Check all connectors for primary gpu 2026-09-01 08:02:38 -06:00
Victoria Brekenfeld
b407ed560a wayland/decoration: Make default decoration mode configurable 2026-09-01 08:01:43 -06:00
Victoria Brekenfeld
a830784f11
Merge pull request #2796 from weblate/weblate-pop-os-cosmic-comp 2026-08-31 19:13:08 +02:00
Hojjat
9fe22c86b6 fix(corner-radius): use layer surface size for radius validation 2026-08-31 19:10:49 +02:00
RipleyTom
94178034dd feat: allow XWayland window to start minimized 2026-08-31 16:29:03 +02:00
Hosted Weblate
158e2b7f91
i18n: translation updates from weblate
Co-authored-by: Kurt Harren Catindig <kurtharrencatindig03@gmail.com>
2026-08-29 18:07:44 +02:00
af6d31f413 Merge tag 'epoch-1.7.0' into local/yoda-main
Epoch 1.7.0
2026-08-27 07:49:24 +02:00
Victoria Brekenfeld
5c93094574 chore: update smithay 2026-08-26 20:07:05 +02:00
Hojjat
31827ed240 improv: clear image_cache on refresh 2026-08-25 17:33:56 +02:00
Hojjat
80b8eb47d7 improv: round up and down sample cursor sizes to limit cache 2026-08-25 17:33:56 +02:00
Hojjat
01b79c3eae feat: scalable cursor support 2026-08-25 17:33:56 +02:00
Tom Grushka
8304b18886 Clamp zoom level to 1.0 if < 1.01
Fix floating point edge cases that keep zoom enabled when level ≈ 1.0

Co-authored-by: Luuk van der Duim <luukvanderduim@gmail.com>
2026-08-21 22:56:16 +02:00
Tom Grushka
738e0b3124 a11y: magnifier: change zoom stepping to multiplicative, bound max, add presets
1. Change magnifier zoom stepping from additive to multiplicative,
   because zoom is a ratio, not a difference, and human perception is
   logarithmic (browser zoom steps, map zoom levels, microscope powers).

2. Add `pub const ZoomConfig::ZOOM_INCREMENT_PRESETS` with two
   additional presets: 10% (macOS, Cinnamon) and 75% (for users at
   70-90x zoom).

3. Bound the zoom level to a generous maximum, beyond what anyone is expected
   to need, to prevent runaway / unrecoverable levels (#1230).

Full background and rationale at #2615.

I used AI to assist with research and issue writeup. Code is 100% mine.

- Closes #2615.
- Fixes #1239.
- Partially addresses #1230 and #1237.
2026-08-21 22:56:16 +02:00
8ac198de15 kms: revient au mode prefere pour le choix initial de sortie
Revient sur 7187efd5, qui elisait le rafraichissement le plus eleve a la
resolution native au lieu du mode marque PREFERRED.

Sans effet au quotidien : ~/.local/state/cosmic-comp/outputs.ron memorise
le mode choisi par sortie et pin deja DP-1 en 3840x2160 a 119,88 Hz.
populate_modes ne decide qu'en l'absence d'etat memorise, c'est-a-dire sur
un profil neuf ou un ecran jamais vu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 09:54:25 +02:00
be1fab51ea comp: retire le runtime Tokio, devenu sans objet
Revient sur 3d01a219. La feature "tokio" de libcosmic-yoda activait
zbus?/tokio, ce qui imposait un Handle::current() ambiant alors que la
boucle de cosmic-comp n'est pas async ; d'ou le garde de runtime dans
main(). La feature est retiree de la dependance libcosmic, zbus n'a donc
plus besoin de reacteur Tokio et le garde n'a plus lieu d'etre.

Cargo.lock perd tokio et toute la chaine qui venait avec la feature :
ashpd, rfd, atspi, accesskit_unix, zbus_xml, tokio-stream.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 09:54:17 +02:00
Hojjat
f20de50e41 feat: shake cursor to find by magnifying the cursor 2026-08-21 00:39:02 +02:00
chris-010
04026259e0 fix: treat unlocking the session as user activity
SessionLockHandler::unlock clears the lock and schedules a render, but
never tells IdleNotifierState that the user is there. Unlock paths that
produce no input event -- fingerprint and smartcard readers -- therefore
leave the idle clock running across the entire lock period, and it is
already past its timeouts by the time the session is unlocked.

The visible result is that the screen stays off after a successful
unlock until the user moves the mouse or presses a key, or that the
session locks itself again seconds after being unlocked.

Notifying activity for every seat in unlock() resets the timers the same
way an input event would.

Drafted with AI assistance (Claude Code); the change was reviewed,
built and tested by me on real hardware as described.

Signed-off-by: chris-010 <10660568+chris-010@users.noreply.github.com>
2026-08-20 13:19:53 +02:00
f60c573f4d Merge upstream/master (COSMIC 1.6) into local/yoda-main
32 commits amont, aucun conflit de code : seul le lockfile divergeait,
il est regenere. Les 9 commits yoda sont preserves, dont le patch des
onglets de piles suivant WindowControlsPosition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 21:56:05 +02:00
Victoria Brekenfeld
314fc670a8
Merge pull request #2754 from weblate/weblate-pop-os-cosmic-comp
Translations update from Hosted Weblate
2026-08-18 13:26:18 +02:00
Hosted Weblate
f1d1c00257
i18n: translation updates from weblate
Co-authored-by: Michael Millet <mikevlet@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/da/
Translation: Pop OS/COSMIC Comp
2026-08-18 11:09:23 +02:00
Ian Douglas Scott
8806436f81 libei: Add com.system76.CosmicOSK to DBus whitelist 2026-08-17 12:22:35 -07:00
Ian Douglas Scott
861a460e6c chore: Fix clippy lints 2026-08-17 12:22:35 -07:00
Ian Douglas Scott
c423e82820 libei: on KMS, schedule render after handling input events
Matches behavior of libinput backend.
2026-08-17 12:22:35 -07:00
Hojjat
f4ee52c46a Ei protocol support
- Use the configured keyboard layout, track ei seats to reconfigure their keyboards
- DBus interface
- Disambiguate input devices per backend instance
- Add support for `ei_keysym` and `ei_text`
- Act as input method for ei_text UTF-8 injection
- Create a region per output for abosolute pointer position
- Map ei region for touchscreen emulation
- Release pointer buttons and keys on removal
- Send modifiers to EI connections
2026-08-17 12:22:35 -07:00
Ian Douglas Scott
e939a72b30 Initial Ei protocol support 2026-08-17 12:22:35 -07:00
Ian Douglas Scott
861b732671 input: Handle scroll events that only have amount_v120
X11, winit, and libei backends all can produce scroll events with
`amount_v120()` but no `amount()`.
2026-08-17 12:22:35 -07:00
Ian Douglas Scott
18ae082615 Update smithay with tablet API changes
For now, only `WlSurface` receives tablet focus, as was previously the
case.

Also updates `reis` to latest bugfix release.
2026-08-17 12:22:35 -07:00
Victoria Brekenfeld
0cf70521ff
Merge pull request #2751 from weblate/weblate-pop-os-cosmic-comp
Translations update from Hosted Weblate
2026-08-17 12:51:34 +02:00
Hosted Weblate
6af1dba69b
i18n: translation updates from weblate
Co-authored-by: Michael Millet <mikevlet@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/da/
Translation: Pop OS/COSMIC Comp
2026-08-17 08:51:26 +02:00
Victoria Brekenfeld
f97a852e95
Merge pull request #2735 from weblate/weblate-pop-os-cosmic-comp
Translations update from Hosted Weblate
2026-08-14 15:57:29 +02:00
Victoria Brekenfeld
3b8bac8335
Merge pull request #2683 from hojjatabdollahi/hojjat/fix-chrome
fix(shell): take presentation feedback from fullscreen surfaces
2026-08-14 15:36:28 +02:00
Hosted Weblate
abfb25a672
i18n: translation updates from weblate
Co-authored-by: Michael Millet <mikevlet@protonmail.com>
Co-authored-by: Steen Rabol <steen@rabol.dev>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/da/
Translation: Pop OS/COSMIC Comp
2026-08-14 08:51:32 +02:00
Victoria Brekenfeld
647228b9ce
Merge pull request #2700 from weblate/weblate-pop-os-cosmic-comp
Translations update from Hosted Weblate
2026-08-10 15:54:16 +02:00
Hosted Weblate
f89c78b9bd
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Martin Meizoso <martinmeizoso@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/gl/
Translation: Pop OS/COSMIC Comp
2026-08-05 16:02:35 +02:00
Hojjat
58c018dd40 fix(shell): take presentation feedback from fullscreen surfaces 2026-07-31 13:42:00 -06:00
Victoria Brekenfeld
d3ffa81494
Merge pull request #2671 from weblate/weblate-pop-os-cosmic-comp 2026-07-30 15:43:15 +02:00
Hosted Weblate
789e0b3634
i18n: translation updates from weblate
Co-authored-by: Andi Chandler <andi@gowling.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/en_GB/
Translation: Pop OS/COSMIC Comp
2026-07-30 11:03:25 +00:00
Victoria Brekenfeld
091583ac84 backend: Support wl-dmabuf v6 2026-07-29 22:08:23 +02:00
Victoria Brekenfeld
81cd5fdbaa
Merge pull request #2667 from weblate/weblate-pop-os-cosmic-comp 2026-07-29 17:14:04 +02:00
Hosted Weblate
d687285338
i18n: translation updates from weblate
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Davide <davidemadonna@outlook.it>
Co-authored-by: Sproston <ojspr2007@gmail.com>
Co-authored-by: Wilbur Lindqvist <william@willmantrade.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/it/
Translation: Pop OS/COSMIC Comp
2026-07-29 11:01:49 +02:00
zyads
ffeda3375a fix(corner_radius): check LayerHookId for layer surfaces
GetCornerRadiusLayer looked up the existing hook via ToplevelHookId while
inserting into LayerHookId a few lines later. Both are
Mutex<Option<(HookId, Weak<..>)>> so it compiles, but they are distinct
data_map slots: the "does this surface already have a corner-radius object?"
check for a layer surface read the toplevel slot, so it could not see a layer
object that was already there, and it created a toplevel entry on a layer
surface as a side effect.

Every other read of the layer hook already uses LayerHookId.

Disclosure: found and written with AI assistance (Claude). The change is the
one line quoted above; I understand it and verified the tree builds with it.
2026-07-29 00:30:01 +02:00
Hojjat
8d450d4977 fix: clamp pointer edge after applying the output offset 2026-07-29 00:27:23 +02:00
v0up3r
9514b4945e fix(kms): don't panic when renderer creation fails after a GPU reset
When the DRM device is lost transiently, for example when the amdgpu driver
performs a GPU reset and recovers ("device wedged, but recovered through
reset"), acquiring a renderer in `SurfaceThreadState::redraw` fails. The three
`renderer`/`single_renderer` calls used `.unwrap()`, so the failure panicked
the surface's render thread. Because that panic unwinds across the EGL/GBM FFI
boundary it aborts the whole process (SIGABRT), dropping the session back to
the greeter and closing every running application.

`redraw` is already fallible and its caller logs the error and reschedules a
redraw, and the frame-submission path (`render_frame`/`queue_frame`) already
propagates errors the same way. Propagate the renderer-acquisition errors too,
so a transient device loss is retried on the next frame, once the device is
back, instead of taking down the compositor.

Relates to #649.

Assisted by an AI coding tool; I have reviewed and fully understand the change
and am able to maintain it and respond to review.
2026-07-28 15:28:34 +02:00
RipleyTom
5ce21fb4f3 fix: ensure X window focus on unmap/map 2026-07-27 21:38:26 +02:00
Tom Grushka
44de3063ee dbus: Fix deadlock in a11y keyboard monitor
A11yKeyboardMonitorState::key_event calls self.has_key_grab() inside
self.clients guard loop.

A "well-behaved" a11y client calls `WatchKeyboard` before `SetKeyGrabs`.

If WatchKeyboard is not called first, the `!client.watched` guard does
not short-circuit, so `has_key_grab()` runs and locks `self.clients` again.

This causes a deadlock and the compositor freezes, requiring a hard reboot.

This commit moves the `self.has_key_grab()` before the guard to
prevent a double lock.
2026-07-25 01:14:20 +02:00
RipleyTom
6108cef8b3 fix: workaround for Chromium apps breaking below 1.0 scaling 2026-07-25 01:13:59 +02:00
Timo Strunk
24d29008f0 fix(kms): skip empty DRM cleanup commit in display_configuration
display_configuration() issued an ALLOW_MODESET atomic commit on every udev
event, even when no planes needed detaching. The empty commit is a no-op
modeset that interferes with in-flight commits on the render thread, and it
fails with EPERM whenever we don't hold DRM master (e.g. a render-only
secondary GPU), aborting device enumeration.

Track whether any property was added and skip the commit when there is nothing
to do. A commit that does have changes still propagates its error as before, so
a genuine failure to apply cleanup is not silently swallowed.

https://github.com/pop-os/cosmic-comp/issues/2375

Authored by Claude Opus 4.8
Reviewed by Timo Strunk <Timo.Strunk@gmail.com>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 18:17:51 -06:00
Timo Strunk
53a8d0bea2 fix(kms): join surface threads before exit to avoid SIGABRT on shutdown
When the kiosk child exits, calling process::exit() directly from the calloop
callback runs libc's atexit handlers (Mesa's util_queue_kill_threads) while a
KMS surface thread may still be mid-frame in eglCreateSync(). Concurrent Mesa
teardown and live EGL use corrupt the heap and glibc aborts the process
("free(): corrupted unsorted chunks", SIGABRT).

Stop the event loop cleanly via should_stop instead, stashing the child's exit
code in Common::kiosk_exit_code. After the loop returns, pause() each DRM device
(releasing master so the surface drop path skips its blocking clear_state(), and
the incoming compositor gets master immediately), then drop_and_join() every
surface so no thread is left in Mesa. Only then is process::exit() called, at
which point atexit is safe to run.

The join is done here rather than in Surface::Drop because an unconditional join
in Drop deadlocks against apply_config_for_outputs during normal operation; once
the event loop has stopped, the surface thread can process ThreadCommand::End
and exit without the main thread servicing any pending message.

https://github.com/pop-os/cosmic-comp/issues/2375

Authored by Claude Opus 4.8
Reviewed by Timo Strunk <Timo.Strunk@gmail.com>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 18:17:51 -06:00
Victoria Brekenfeld
9718699ec7
Merge pull request #2619 from weblate/weblate-pop-os-cosmic-comp
Translations update from Hosted Weblate
2026-07-23 19:04:37 +02:00
Hosted Weblate
e4bb42f2c0
i18n: translation updates from weblate
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
2026-07-23 18:49:35 +02:00
79 changed files with 6022 additions and 1554 deletions

567
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -44,7 +44,6 @@ iced_graphics = { path = "../libcosmic/iced/graphics", features = ["geometry"] }
indexmap = "2.13"
keyframe = "1.1.1"
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [
"tokio",
"wayland",
"multi-window",
] }
@ -53,6 +52,7 @@ log-panics = { version = "2", features = ["with-backtrace"] }
ordered-float = "5.1"
png = "0.18"
regex = "1"
resvg = "0.45"
ron = "0.12"
rust-embed = { version = "8.11", features = ["debug-embed"] }
sanitize-filename = "0.6.0"
@ -78,7 +78,6 @@ 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"
@ -102,6 +101,7 @@ features = [
"backend_drm",
"backend_gbm",
"backend_egl",
"backend_libei",
"backend_libinput",
"backend_session_libseat",
"backend_udev",
@ -177,4 +177,5 @@ dnd = { path = "../window_clipboard/dnd" }
mime = { path = "../window_clipboard/mime" }
[patch.crates-io]
smithay = { git = "https://github.com/smithay/smithay.git", rev = "1ed69cb" }
smithay = { git = "https://github.com/smithay/smithay.git", rev = "e3d461a" }
smithay-egui = { git = "https://github.com/Smithay/smithay-egui.git", rev = "6511552" }

View file

@ -66,6 +66,13 @@ impl Default for AppearanceConfig {
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum DecorationPreference {
#[default]
ClientSide,
ServerSide,
}
#[derive(Clone, Debug, PartialEq, CosmicConfigEntry)]
#[version = 1]
pub struct CosmicCompConfig {
@ -101,7 +108,10 @@ pub struct CosmicCompConfig {
pub appearance_settings: AppearanceConfig,
/// Hide the cursor after this many seconds of pointer inactivity (None disables)
pub cursor_hide_timeout: Option<u32>,
/// Briefly magnify the cursor when the pointer is shaken, to help locate it
pub cursor_shake_to_find: bool,
pub activation_policy: ActivationPolicy,
pub decoration_preference: DecorationPreference,
}
impl Default for CosmicCompConfig {
@ -139,7 +149,9 @@ impl Default for CosmicCompConfig {
accessibility_zoom: ZoomConfig::default(),
appearance_settings: AppearanceConfig::default(),
cursor_hide_timeout: None,
cursor_shake_to_find: true,
activation_policy: ActivationPolicy::default(),
decoration_preference: DecorationPreference::default(),
}
}
}
@ -195,6 +207,10 @@ pub struct ZoomConfig {
pub enable_mouse_zoom_shortcuts: bool,
}
impl ZoomConfig {
pub const ZOOM_INCREMENT_PRESETS: &[u32] = &[10, 25, 50, 75, 100, 150, 200];
}
impl Default for ZoomConfig {
fn default() -> Self {
ZoomConfig {

View file

@ -0,0 +1,28 @@
a11y-zoom-move-continuously = Visningen bevæger sig kontinuerligt med markøren
a11y-zoom-move-onedge = Vis bevægelser når markør rammer kant
a11y-zoom-move-centered = Vis bevægelser, for at holde markør centreret
a11y-zoom-settings = Forstørrelsesglas-indstillinger...
grow-window = Gro
shrink-window = Skrump
swap-windows = Byt Vinduer
stack-windows = Stabel Vinduer
window-menu-tiled = Gør vindue flydende
window-menu-screenshot = Tag skærmbillede
window-menu-move = Flyt
window-menu-resize = Skift størrelse
window-menu-move-prev-workspace = Flyt til forrige arbejdsområde
window-menu-move-next-workspace = Flyt til næste arbejdsområde
window-menu-stack = Opret vinduestak
window-menu-unstack-all = Udtag vinduer af stak
window-menu-unstack = Udtag vindue af stak
window-menu-sticky = Fastgjort vindue
window-menu-close = Luk
window-menu-close-all = Luk alle vinduer
window-menu-resize-edge-top = Top
window-menu-resize-edge-left = Venstre
window-menu-resize-edge-right = Højre
window-menu-resize-edge-bottom = Bund
window-menu-minimize = Minimér
window-menu-maximize = Maksimér
window-menu-fullscreen = Fuld skærm
unknown-keybinding = <uangiven>

View file

@ -0,0 +1,28 @@
a11y-zoom-move-continuously = View moves continuously with pointer
a11y-zoom-move-onedge = View moves when pointer reaches edge
a11y-zoom-move-centered = View moves to keep pointer centred
a11y-zoom-settings = Magnifier settings...
grow-window = Grow
shrink-window = Shrink
swap-windows = Swap Windows
stack-windows = Stack Windows
unknown-keybinding = <unset>
window-menu-minimize = Minimise
window-menu-maximize = Maximise
window-menu-fullscreen = Fullscreen
window-menu-tiled = Float window
window-menu-screenshot = Take screenshot
window-menu-move = Move
window-menu-resize = Resize
window-menu-move-prev-workspace = Move to previous workspace
window-menu-move-next-workspace = Move to next workspace
window-menu-stack = Create window stack
window-menu-unstack-all = Unstack windows
window-menu-unstack = Unstack window
window-menu-sticky = Sticky window
window-menu-close = Close
window-menu-close-all = Close all windows
window-menu-resize-edge-top = Top
window-menu-resize-edge-left = Left
window-menu-resize-edge-right = Right
window-menu-resize-edge-bottom = Bottom

View file

View file

@ -0,0 +1,28 @@
a11y-zoom-move-continuously = A vista móvese continuamente co punteiro
a11y-zoom-move-onedge = A vista móvese cando o punteiro chega ao bordo
a11y-zoom-move-centered = Mover a vista para manter o cursor centrado
a11y-zoom-settings = Configuración da lupa...
grow-window = Aumentar
shrink-window = Encoller
swap-windows = Cambiar xanelas
stack-windows = Apilar xanelas
unknown-keybinding = <unset>
window-menu-minimize = Minimizar
window-menu-maximize = Maximizar
window-menu-fullscreen = Pantalla Completa
window-menu-tiled = Xanela flotante
window-menu-screenshot = Capturar pantalla
window-menu-move = Mover
window-menu-resize = Redimensionar
window-menu-move-prev-workspace = Mover ao espazo de traballo anterior
window-menu-move-next-workspace = Mover ao seguinte espazo de traballo
window-menu-stack = Crear pila de xanelas
window-menu-unstack-all = Desapilar xanelas
window-menu-unstack = Desapilar xanela
window-menu-sticky = Xanela persistente
window-menu-close = Pechar
window-menu-close-all = Pechar todas as xanelas
window-menu-resize-edge-top = Arriba
window-menu-resize-edge-left = Esquerda
window-menu-resize-edge-right = Dereita
window-menu-resize-edge-bottom = Fondo

View file

@ -1,4 +1,4 @@
grow-window = Ingrandisci
grow-window = Crescere
shrink-window = Riduci
swap-windows = Scambia le finestre
stack-windows = Sovrapponi le finestre
@ -6,7 +6,7 @@ unknown-keybinding = <unset>
window-menu-minimize = Minimizza
window-menu-maximize = Massimizza
window-menu-tiled = Finestra flottante
window-menu-screenshot = Cattura una schermata
window-menu-screenshot = Scatta uno screenshot
window-menu-move = Sposta
window-menu-resize = Ridimensiona
window-menu-move-prev-workspace = Sposta all'area di lavoro precedente
@ -22,7 +22,7 @@ window-menu-resize-edge-left = Sinistra
window-menu-resize-edge-right = Destra
window-menu-resize-edge-bottom = In basso
window-menu-fullscreen = Schermo intero
a11y-zoom-settings = Opzioni lente d'ingrandimento...
a11y-zoom-settings = Impostazioni lente d'ingrandimento...
a11y-zoom-move-continuously = Segue sempre il cursorse
a11y-zoom-move-centered = Mantiene il cursore centrato
a11y-zoom-move-onedge = Segue il cursore ai bordi

View file

@ -0,0 +1,20 @@
a11y-zoom-move-continuously = La vista bolega de contunh amb lo ponchador
a11y-zoom-move-onedge = La lista bolega quand lo ponchador atenh un caire
a11y-zoom-move-centered = La vista bolega per gardar lo ponchador al centre
a11y-zoom-settings = Paramètres de la lópia...
unknown-keybinding = <unset>
window-menu-tiled = Fenèstras bandejanta
window-menu-screenshot = Prendre una captura d’ecran
window-menu-resize = Redimensionar
window-menu-move-prev-workspace = Desplaçar a l’espaci de trabalh precedent
window-menu-move-next-workspace = Desplaçar a l'espaci de trabalh seguent
window-menu-stack = Crear una pila de fenèstras
window-menu-unstack-all = Desapilar totas las fenèstras
window-menu-unstack = Desapilar la fenèstra
window-menu-sticky = Fenèstra adesiva
window-menu-close = Tampar
window-menu-close-all = Tampar totas las fenèstras
window-menu-resize-edge-top = Amont
window-menu-resize-edge-left = Esquèrra
window-menu-resize-edge-right = Drecha
window-menu-resize-edge-bottom = Aval

View file

View file

@ -59,7 +59,7 @@ use std::{
time::Duration,
};
use super::{drm_helpers, socket::Socket, surface::Surface};
use super::{drm_helpers, surface::Surface};
#[derive(Debug)]
pub struct EGLInternals {
@ -98,14 +98,12 @@ pub struct Device {
pub texture_formats: FormatSet,
event_token: Option<RegistrationToken>,
pub socket: Option<Socket>,
}
#[derive(Debug)]
struct ReusableDevice {
leasing_global: Option<DrmLeaseState>,
active_clients: HashSet<ClientId>,
socket: Option<Socket>,
}
#[derive(Debug)]
@ -593,7 +591,7 @@ impl State {
}
}
pub fn device_removed(&mut self, dev: dev_t, dh: &DisplayHandle) -> Result<()> {
pub fn device_removed(&mut self, dev: dev_t, _dh: &DisplayHandle) -> Result<()> {
let backend = self.backend.kms();
// we can't use DrmNode::from_node_id, because that assumes the node is still on sysfs
let drm_node = backend
@ -616,13 +614,6 @@ impl State {
if let Some(token) = device.event_token.take() {
self.common.event_loop_handle.remove(token);
}
if let Some(socket) = device.socket.take() {
self.common.event_loop_handle.remove(socket.token);
self.common
.dmabuf_state
.destroy_global::<State>(dh, socket.dmabuf_global);
dh.remove_global::<State>(socket.drm_global);
}
backend.api.as_mut().remove_node(&device.inner.render_node);
backend
.primary_node
@ -702,7 +693,7 @@ impl Device {
)
.with_context(|| {
format!(
"Failed to optain file descriptor for drm device: {}",
"Failed to obtain file descriptor for drm device: {}",
path.display()
)
})?,
@ -755,23 +746,7 @@ impl Device {
let ReusableDevice {
leasing_global,
active_clients,
socket,
} = reuse.unwrap_or_else(|| {
let socket = match (!is_software)
.then(|| common.create_socket(dh, render_node, texture_formats.clone()))
.transpose()
{
Ok(socket) => socket,
Err(err) => {
warn!(
?err,
"Failed to initialize hardware-acceleration for clients on {}.",
render_node,
);
None
}
};
let leasing_global = match (!is_software)
.then(|| DrmLeaseState::new::<State>(dh, &dev_node))
.transpose()
@ -790,7 +765,6 @@ impl Device {
ReusableDevice {
leasing_global,
active_clients: HashSet::new(),
socket,
}
});
@ -831,7 +805,6 @@ impl Device {
texture_formats,
event_token: Some(token),
socket,
})
}
@ -892,7 +865,6 @@ impl Device {
let device = ReusableDevice {
leasing_global: self.inner.leasing_global,
active_clients: self.inner.active_clients,
socket: self.socket,
};
let state = OldDeviceState {
@ -1269,26 +1241,15 @@ fn populate_modes(
) -> Result<()> {
let conn_info = drm.get_connector(conn, false)?;
let max_bpc = drm_helpers::get_max_bpc(drm, conn)?.map(|(_val, range)| range.end.min(16));
let preferred = conn_info
let Some(mode) = conn_info
.modes()
.iter()
.find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
.copied()
.or(conn_info.modes().first().copied());
let Some(preferred) = preferred else {
.or(conn_info.modes().first().copied())
else {
anyhow::bail!("No mode found");
};
// Prefer the highest refresh rate at the native resolution so that
// high-refresh panels (120 Hz, 144 Hz, …) are used out-of-the-box
// without a pre-existing outputs.ron config.
let preferred_size = preferred.size();
let mode = conn_info
.modes()
.iter()
.filter(|m| m.size() == preferred_size)
.max_by_key(|m| drm_helpers::calculate_refresh_rate(**m))
.copied()
.unwrap_or(preferred);
let refresh_rate = drm_helpers::calculate_refresh_rate(mode);
let output_mode = OutputMode {

View file

@ -75,6 +75,7 @@ pub fn display_configuration(
// And then cleanup
if device.is_atomic() {
let mut req = AtomicModeReq::new();
let mut has_changes = false;
let plane_handles = device.plane_handles()?;
// We cannot just shortcut and use the legacy api for all cleanups because of this.
@ -95,10 +96,15 @@ pub fn display_configuration(
let fb_id = get_prop(device, plane, "FB_ID")?;
req.add_property(plane, crtc_id, property::Value::CRTC(None));
req.add_property(plane, fb_id, property::Value::Framebuffer(None));
has_changes = true;
}
}
}
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
// Skip an empty commit: a no-op modeset that also fails with EPERM
// without DRM master (e.g. a render-only secondary GPU).
if has_changes {
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
}
} else {
for crtc in res_handles.crtcs() {
#[allow(deprecated)]

View file

@ -4,12 +4,15 @@ use crate::{
config::{CompOutputConfig, ScreenFilter},
shell::Shell,
state::BackendData,
utils::{env::dev_var, prelude::*},
wayland::protocols::output_power::OutputPowerState,
utils::{env::dev_var, global::remove_global_with_timer, prelude::*},
wayland::protocols::{drm::WlDrmState, output_power::OutputPowerState},
};
use anyhow::{Context, Result};
use calloop::LoopSignal;
use anyhow::{self, Context, Result};
use calloop::{
LoopSignal,
timer::{TimeoutAction, Timer},
};
use cosmic_comp_config::output::comp::{AdaptiveSync, OutputState};
use indexmap::IndexMap;
use render::gles::GbmGlowBackend;
@ -29,14 +32,19 @@ use smithay::{
calloop::{Dispatcher, EventLoop, LoopHandle},
drm::{
Device as _,
control::{Device as _, connector::Interface, crtc},
control::{
Device as _,
connector::{Interface, State as ConnectorState},
crtc,
},
},
input::{self, Libinput},
wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags,
wayland_server::{Client, DisplayHandle},
},
utils::{Clock, DevPath, Monotonic, Size},
wayland::{
dmabuf::DmabufGlobal,
dmabuf::{DmabufFeedbackBuilder, DmabufGlobal},
drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd},
relative_pointer::RelativePointerManagerState,
},
@ -48,12 +56,12 @@ use std::{
collections::{HashMap, HashSet},
path::Path,
sync::{Arc, RwLock, atomic::AtomicBool},
time::Duration,
};
mod device;
mod drm_helpers;
pub mod render;
mod socket;
mod surface;
use device::*;
pub(crate) use surface::Surface;
@ -74,6 +82,7 @@ pub struct KmsState {
libinput: Libinput,
pub syncobj_state: Option<DrmSyncobjState>,
pub dmabuf_global: Option<DmabufGlobal>,
}
pub struct KmsGuard<'a> {
@ -131,6 +140,7 @@ pub fn init_backend(
libinput: libinput_context,
syncobj_state: None,
dmabuf_global: None,
});
// manually add already present gpus
@ -142,9 +152,10 @@ pub fn init_backend(
}
}
if let Err(err) = state.backend.kms().select_primary_gpu(dh) {
if let Err(err) = state.select_primary_gpu(dh) {
warn!("Failed to determine primary gpu: {}", err);
}
state.update_default_feedback();
if let Err(err) = state.refresh_output_config() {
info!(
@ -204,7 +215,7 @@ fn init_libinput(
state.backend.kms().input_devices.remove(&*device.name());
}
state.process_input_event(event);
state.process_input_event(event, crate::input::InputBackendId::Normal);
for output in state.common.shell.read().outputs() {
state.backend.kms().schedule_render(output);
@ -243,13 +254,18 @@ fn determine_primary_gpu(
// try to find builtin display
for dev in drm_devices.values() {
if dev.inner.surfaces.values().any(|s| {
if let Ok(conn_info) = dev.drm.device().get_connector(s.connector, false) {
let i = conn_info.interface();
i == Interface::EmbeddedDisplayPort || i == Interface::LVDS || i == Interface::DSI
} else {
false
}
let drm = dev.drm.device();
let res_handles = drm.resource_handles()?;
let connectors = res_handles.connectors();
if connectors.iter().any(|conn| {
let Ok(conn_info) = drm.get_connector(*conn, false) else {
return false;
};
let i = conn_info.interface();
conn_info.state() == ConnectorState::Connected
&& (i == Interface::EmbeddedDisplayPort
|| i == Interface::LVDS
|| i == Interface::DSI)
}) {
return Ok(Some(dev.inner.render_node));
}
@ -317,11 +333,13 @@ fn init_udev(
{
let backend = state.backend.kms();
if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. })
&& backend.primary_node.read().unwrap().is_none()
&& let Err(err) = state.backend.kms().select_primary_gpu(&dh)
{
warn!("Failed to determine a new primary gpu: {}", err);
if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. }) {
if backend.primary_node.read().unwrap().is_none()
&& let Err(err) = state.select_primary_gpu(&dh)
{
warn!("Failed to determine a new primary gpu: {}", err);
}
state.update_default_feedback();
}
}
@ -465,50 +483,118 @@ impl State {
device.drm.pause();
}
}
}
impl KmsState {
fn select_primary_gpu(&mut self, dh: &DisplayHandle) -> Result<()> {
// We don't have to check the allow/blocklist here,
// as any disallowed devices won't be in `self.drm_devices`.
let mut primary_node = self.primary_node.write().unwrap();
let kms = self.backend.kms();
let mut primary_node = kms.primary_node.write().unwrap();
let _ = primary_node.take(); // if we error don't leave an old node in place
*primary_node = determine_primary_gpu(&self.drm_devices, self.session.seat())?;
*primary_node = determine_primary_gpu(&kms.drm_devices, kms.session.seat())?;
if let Some(node) = *primary_node {
info!("Using {} as primary gpu for rendering.", node);
self.software_renderer.take();
} else if self.software_renderer.is_none() {
kms.software_renderer.take();
// setup minimal feedback. We will update it in `update_default_feedback`
let primary_formats = kms
.drm_devices
.values()
.find(|dev| dev.inner.render_node == node)
.unwrap()
.texture_formats
.clone();
let feedback = DmabufFeedbackBuilder::new(node.dev_id(), primary_formats.clone())
.build()
.unwrap();
if let Some(global) = kms.dmabuf_global.as_ref() {
self.common
.dmabuf_state
.set_default_feedback(global, &feedback);
} else {
let dmabuf_global = self
.common
.dmabuf_state
.create_global_with_default_feedback::<State>(dh, &feedback);
kms.dmabuf_global = Some(dmabuf_global);
};
let device_path = node
.dev_path_with_type(NodeType::Render)
.or_else(|| node.dev_path())
.ok_or(anyhow::anyhow!(
"Could not determine path for gpu node: {}",
node
))?;
if let Some(drm) = self.common.wl_drm_state.as_mut() {
drm.update_device(device_path, primary_formats);
} else {
self.common.wl_drm_state = Some(WlDrmState::new::<State>(
dh,
device_path,
primary_formats,
kms.dmabuf_global.as_ref().unwrap(),
));
}
} else if kms.software_renderer.is_none() {
info!("Failed to find a suitable gpu, using software renderingr");
self.software_renderer = match software_renderer() {
kms.software_renderer = match software_renderer() {
Ok(renderer) => Some(renderer),
Err(err) => {
error!(?err, "Failed to initialize software EGL renderer.");
None
}
};
if let Some(drm) = self.common.wl_drm_state.take() {
remove_global_with_timer(dh, &self.common.event_loop_handle, drm.global().clone());
}
if let Some(global) = kms.dmabuf_global.take() {
self.common
.dmabuf_state
.disable_global::<State>(dh, &global);
let source = Timer::from_duration(Duration::from_secs(5));
let res =
self.common
.event_loop_handle
.insert_source(source, move |_, _, state| {
state
.common
.dmabuf_state
.destroy_global::<State>(&state.common.display_handle, global);
TimeoutAction::Drop
});
if let Err(err) = res {
tracing::error!(
"failed to insert timer source to destroy output global: {}",
err
);
}
}
}
if !crate::utils::env::bool_var("COSMIC_DISABLE_SYNCOBJ").unwrap_or(false) {
if let Some(primary_node) = primary_node
.as_ref()
.and_then(|node| node.node_with_type(NodeType::Primary).and_then(|x| x.ok()))
&& let Some(device) = self.drm_devices.get(&primary_node)
&& let Some(device) = kms.drm_devices.get(&primary_node)
{
let import_device = device.drm.device().device_fd().clone();
if supports_syncobj_eventfd(&import_device) {
if let Some(state) = self.syncobj_state.as_mut() {
if let Some(state) = kms.syncobj_state.as_mut() {
state.update_device(import_device);
} else {
let syncobj_state = DrmSyncobjState::new::<State>(dh, import_device);
self.syncobj_state = Some(syncobj_state);
kms.syncobj_state = Some(syncobj_state);
}
return Ok(());
}
}
if let Some(old_state) = self.syncobj_state.take() {
if let Some(old_state) = kms.syncobj_state.take() {
dh.remove_global::<State>(old_state.into_global());
}
}
@ -516,6 +602,45 @@ impl KmsState {
Ok(())
}
fn update_default_feedback(&mut self) {
let kms = self.backend.kms();
let primary_node = kms.primary_node.read().unwrap();
if let Some(primary_node) = *primary_node {
let primary_formats = kms
.drm_devices
.values()
.find(|dev| dev.inner.render_node == primary_node)
.unwrap()
.texture_formats
.clone();
let mut feedback =
DmabufFeedbackBuilder::new(primary_node.dev_id(), primary_formats.clone());
for dev in kms
.drm_devices
.values()
.filter(|dev| dev.inner.render_node != primary_node)
{
feedback = feedback.add_preference_tranche(
dev.inner.render_node.dev_id(),
TrancheFlags::Sampling,
dev.texture_formats.iter().cloned(),
6..=6,
);
}
let default_feedback = feedback.build().unwrap();
self.common.dmabuf_state.set_default_feedback(
kms.dmabuf_global
.as_ref()
.expect("Primary node but no dmabuf global?"),
&default_feedback,
);
}
}
}
impl KmsState {
pub fn switch_vt(&mut self, num: i32) -> Result<(), anyhow::Error> {
self.session.change_vt(num).map_err(Into::into)
}
@ -523,29 +648,25 @@ impl KmsState {
pub fn dmabuf_imported(
&mut self,
client: Option<Client>,
global: &DmabufGlobal,
_global: &DmabufGlobal,
dmabuf: Dmabuf,
) -> Result<DrmNode> {
let device_node = dmabuf
.node()
.unwrap_or_else(|| self.primary_node.read().unwrap().unwrap());
let mut device = self
.drm_devices
.values_mut()
.find(|device| {
device
.socket
.as_ref()
.map(|s| &s.dmabuf_global == global)
.unwrap_or(false)
})
.context("Couldn't find gpu for dmabuf global")?;
.find(|dev| dev.inner.render_node == device_node)
.ok_or(anyhow::anyhow!(
"Unable to find device for node: {}",
device_node
))?;
// If device advertised to client doesn't support format/modifier, select
// first device that does. This is needed for image-copy from
// output/toplevel on a different node.
//
// TODO: After
// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/268,
// only try the device specified explicitly by the client, if set.
if !device.texture_formats.contains(&dmabuf.format()) {
if dmabuf.node().is_none() && !device.texture_formats.contains(&dmabuf.format()) {
device = self
.drm_devices
.values_mut()

View file

@ -1006,12 +1006,16 @@ impl SurfaceThreadState {
&self.shell.read(),
);
// Acquiring a renderer can fail transiently when the underlying DRM
// device is lost (e.g. after a GPU reset).
let mut renderer = if render_node != self.target_node {
self.api
.renderer(&render_node, &self.target_node, compositor.format())
.unwrap()
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
} else {
self.api.single_renderer(&self.target_node).unwrap()
self.api
.single_renderer(&self.target_node)
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
};
self.timings.start_render(&self.clock);
@ -1267,7 +1271,10 @@ impl SurfaceThreadState {
})
.context("Failed to draw to offscreen render target")?;
renderer = self.api.single_renderer(&self.target_node).unwrap();
renderer = self
.api
.single_renderer(&self.target_node)
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?;
elements = postprocess_elements(
&mut renderer,
@ -1522,7 +1529,7 @@ fn get_surface_dmabuf_feedback(
render_node: DrmNode,
target_node: DrmNode,
render_formats: FormatSet,
_target_formats: FormatSet,
target_formats: FormatSet,
primary_plane_formats: FormatSet,
overlay_plane_formats: Option<FormatSet>,
) -> SurfaceDmabufFeedback {
@ -1540,60 +1547,40 @@ fn get_surface_dmabuf_feedback(
.cloned()
.collect::<FormatSet>()
});
let builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats);
/*
// Sadly no implementation would pick this up as a preferred render tranche,
// where the combined formats would increase our chances of doing a dmabuf copy.
// .. So we should probably not advertise this on the off-chance it actually triggers bugs.
//
let mut builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats.clone());
let combined_formats = render_formats.intersection(&target_formats).cloned().collect::<FormatSet>();
if target_node != render_node.dev_id() && !combined_formats.is_empty() {
builder = builder.add_preference_tranche(
render_node.dev_id(),
None,
combined_formats,
);
};
// We also can't advertise scan out tranches for the actual display device,
// as e.g. the nvidia driver might then send us dmabufs, that makes e.g. the iris hangs on import...
if target_node != render_node.dev_id() && !combined_formats.is_empty() {
if target_node != render_node {
builder = builder.add_preference_tranche(
target_node.dev_id(),
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
combined_formats,
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Sampling,
target_formats,
6..=6,
);
};
// So no fun combinations, we gotta wait for dmabuf-v6
*/
let render_feedback = builder.clone().build().unwrap();
let primary_scanout_feedback = (target_node == render_node).then(|| {
let primary_scanout_feedback = builder
.clone()
.add_preference_tranche(
target_node.dev_id(),
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout,
primary_plane_formats,
4..=6,
)
.build()
.unwrap();
let overlay_scanout_feedback = overlay_plane_formats.map(|formats| {
builder
.clone()
.add_preference_tranche(
render_node.dev_id(),
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
primary_plane_formats,
target_node.dev_id(),
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout,
formats,
4..=6,
)
.build()
.unwrap()
});
let overlay_scanout_feedback = overlay_plane_formats
.filter(|_| target_node == render_node)
.map(|formats| {
builder
.add_preference_tranche(
render_node.dev_id(),
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
formats,
)
.build()
.unwrap()
});
SurfaceDmabufFeedback {
render_feedback,

View file

@ -8,6 +8,9 @@ use crate::{
utils::prelude::*,
wayland::handlers::compositor::FRAME_TIME_FILTER,
};
use keyframe::{ease, functions::EaseInOutCubic};
use resvg::{tiny_skia, usvg};
use serde::Deserialize;
use smithay::{
backend::{
allocator::Fourcc,
@ -38,7 +41,7 @@ use smithay::{
wayland::compositor::{get_role, with_states},
};
use std::{
collections::HashMap,
collections::{HashMap, VecDeque},
io::Read,
sync::Mutex,
time::{Duration, Instant},
@ -51,14 +54,43 @@ use xcursor::{
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba");
/// A single frame of a scalable SVG cursor: the parsed SVG plus its metadata.
#[derive(Debug, Clone)]
struct SvgFrame {
tree: usvg::Tree,
/// The nominal (logical) size the SVG is authored for.
nominal_size: f32,
/// Hotspot coordinates, in the SVG's own (nominal) coordinate space.
hotspot_x: f32,
hotspot_y: f32,
/// Delay to the next frame in milliseconds (0 for static cursors).
delay: u32,
}
#[derive(Debug, Clone)]
enum CursorKind {
/// Legacy raster XCursor frames.
Xcursor(Vec<Image>),
/// Scalable SVG frames, rasterized on demand.
Svg(Vec<SvgFrame>),
}
#[derive(Debug, Clone)]
pub struct Cursor {
icons: Vec<Image>,
kind: CursorKind,
size: u32,
}
impl Cursor {
pub fn load(theme: &CursorTheme, shape: CursorIcon, size: u32) -> Cursor {
// Prefer a scalable SVG cursor when the theme provides one.
if let Some(frames) = load_svg_icon(theme, shape) {
return Cursor {
kind: CursorKind::Svg(frames),
size,
};
}
let icons = load_icon(theme, shape)
.map_err(|err| warn!(?err, "Unable to load xcursor, using fallback cursor"))
.or_else(|_| load_icon(theme, CursorIcon::Default))
@ -75,44 +107,134 @@ impl Cursor {
}]
});
Cursor { icons, size }
Cursor {
kind: CursorKind::Xcursor(icons),
size,
}
}
pub fn get_image(&self, scale: u32, millis: u32) -> Image {
let size = self.size * scale;
frame(millis, size, &self.icons)
let idx = self.frame_index(size, millis);
self.render_frame(size, idx)
}
/// Selects the index of the frame to display at nominal size `size` (in px)
/// and elapsed `millis`.
fn frame_index(&self, size: u32, millis: u32) -> usize {
match &self.kind {
CursorKind::Xcursor(images) => xcursor_frame_index(millis, size, images),
CursorKind::Svg(frames) => svg_frame_index(millis, frames),
}
}
/// Produces the RGBA image for frame `idx`, rasterizing SVG cursors at the
/// requested nominal size `size` (in px).
fn render_frame(&self, size: u32, idx: usize) -> Image {
match &self.kind {
CursorKind::Xcursor(images) => images[idx].clone(),
CursorKind::Svg(frames) => rasterize_svg_frame(&frames[idx], size),
}
}
}
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
// Follow the nominal size of the cursor to choose the nearest
/// Rasterize a scalable cursor frame at nominal pixel size `size`. Per the KDE
/// SVG cursor format, the SVG canvas and hotspot are scaled by
/// `size / nominal_size`. The returned [`Image`] uses the same premultiplied
/// byte order (BGRA / little-endian ARGB) as xcursor images.
fn rasterize_svg_frame(frame: &SvgFrame, size: u32) -> Image {
let factor = size as f32 / frame.nominal_size;
let svg_size = frame.tree.size();
let width = ((svg_size.width() * factor).floor() as u32).max(1);
let height = ((svg_size.height() * factor).floor() as u32).max(1);
let mut image = Image {
size,
width,
height,
xhot: (frame.hotspot_x * factor).floor() as u32,
yhot: (frame.hotspot_y * factor).floor() as u32,
delay: frame.delay,
pixels_rgba: Vec::new(),
pixels_argb: Vec::new(), // unused
};
match tiny_skia::Pixmap::new(width, height) {
Some(mut pixmap) => {
resvg::render(
&frame.tree,
tiny_skia::Transform::from_scale(factor, factor),
&mut pixmap.as_mut(),
);
// tiny-skia produces premultiplied RGBA; xcursor images consume the
// raw little-endian ARGB byte order (premultiplied BGRA). Swap R<->B.
let mut pixels = pixmap.take();
for px in pixels.as_chunks_mut::<4>().0 {
px.swap(0, 2);
}
image.pixels_rgba = pixels;
}
None => {
warn!(width, height, "Failed to allocate cursor pixmap");
image.pixels_rgba = vec![0; (width as usize) * (height as usize) * 4];
}
}
image
}
/// Indices (into `images`) of all frames sharing the resolution nearest to `size`.
fn nearest_image_indices(size: u32, images: &[Image]) -> Vec<usize> {
// Follow the nominal size of the cursor to choose the nearest.
let nearest_image = images
.iter()
.min_by_key(|image| u32::abs_diff(size, image.size))
.unwrap();
let (width, height) = (nearest_image.width, nearest_image.height);
images.iter().filter(move |image| {
image.width == nearest_image.width && image.height == nearest_image.height
})
images
.iter()
.enumerate()
.filter(|(_, image)| image.width == width && image.height == height)
.map(|(i, _)| i)
.collect()
}
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
fn xcursor_frame_index(mut millis: u32, size: u32, images: &[Image]) -> usize {
let indices = nearest_image_indices(size, images);
let total: u32 = indices.iter().map(|&i| images[i].delay).sum();
if total == 0 {
millis = 0;
} else {
millis %= total;
return indices[0];
}
millis %= total;
for img in nearest_images(size, images) {
if millis <= img.delay {
return img.clone();
for &i in &indices {
if millis <= images[i].delay {
return i;
}
millis -= img.delay;
millis -= images[i].delay;
}
unreachable!()
*indices.last().unwrap()
}
fn svg_frame_index(mut millis: u32, frames: &[SvgFrame]) -> usize {
let total: u32 = frames.iter().map(|frame| frame.delay).sum();
if total == 0 {
return 0;
}
millis %= total;
for (i, frame) in frames.iter().enumerate() {
if millis <= frame.delay {
return i;
}
millis -= frame.delay;
}
frames.len() - 1
}
#[derive(thiserror::Error, Debug)]
@ -174,6 +296,79 @@ fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result<Vec<Image>, Error
Err(Error::NoDefaultCursor)
}
/// A frame entry in a `cursors_scalable/<shape>/metadata.json` file, per the
/// KDE SVG cursor format specification.
#[derive(Debug, Clone, Deserialize)]
struct SvgCursorMeta {
filename: String,
nominal_size: f32,
hotspot_x: f32,
hotspot_y: f32,
/// Only present for animated cursors; defaults to 0 for static ones.
#[serde(default)]
delay: u32,
}
/// Resolves a scalable (SVG) cursor for `shape`
fn load_svg_icon(theme: &CursorTheme, shape: CursorIcon) -> Option<Vec<SvgFrame>> {
let shape_name = shape.to_string();
let options = usvg::Options::default();
for name in cursor_aliases(&shape_name)
.iter()
.copied()
.chain(std::iter::once(shape_name.as_str()))
{
if let Some(dir) = theme.load_scalable(name)
&& let Some(frames) = parse_svg_dir(&dir, &options)
{
return Some(frames);
}
}
None
}
/// Reads a `cursors_scalable/<shape>` directory (its `metadata.json` and the
/// referenced SVG files) into a list of parsed frames.
fn parse_svg_dir(dir: &std::path::Path, options: &usvg::Options) -> Option<Vec<SvgFrame>> {
let metadata = std::fs::read(dir.join("metadata.json")).ok()?;
let metas: Vec<SvgCursorMeta> = match serde_json::from_slice(&metadata) {
Ok(metas) => metas,
Err(err) => {
warn!(?dir, ?err, "Malformed SVG cursor metadata");
return None;
}
};
let mut frames = Vec::with_capacity(metas.len());
for meta in metas {
let svg_path = dir.join(&meta.filename);
let svg_data = match std::fs::read(&svg_path) {
Ok(data) => data,
Err(err) => {
warn!(?svg_path, ?err, "Unable to read SVG cursor");
break;
}
};
let tree = match usvg::Tree::from_data(&svg_data, options) {
Ok(tree) => tree,
Err(err) => {
warn!(?svg_path, ?err, "Unable to parse SVG cursor");
break;
}
};
frames.push(SvgFrame {
tree,
nominal_size: meta.nominal_size,
hotspot_x: meta.hotspot_x,
hotspot_y: meta.hotspot_y,
delay: meta.delay,
});
}
(!frames.is_empty()).then_some(frames)
}
render_elements! {
pub CursorRenderElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
Static=MemoryRenderBufferRenderElement<R>,
@ -265,11 +460,62 @@ pub struct CursorStateInner {
cursors: HashMap<CursorIcon, Cursor>,
current_image: Option<Image>,
image_cache: Vec<(Image, MemoryRenderBuffer)>,
image_cache: Vec<CachedFrame>,
hidden: bool,
idle_timer: Option<RegistrationToken>,
last_armed: Option<Instant>,
// shake-to-find
shake_path: VecDeque<PathSample>,
shake_path_position: Point<f64, Logical>,
magnify_until: Option<Instant>,
magnify_target: f32,
magnification: f32,
anim_from: f32,
anim_start: Option<Instant>,
rest_started: Option<Instant>,
}
/// A rasterized cursor frame, keyed by `(shape, pixel size, frame index)`.
struct CachedFrame {
key: (CursorIcon, u32, usize),
image: Image,
buffer: MemoryRenderBuffer,
unmagnified: bool,
}
/// One sampled pointer position on the recent motion path.
#[derive(Clone, Copy)]
struct PathSample {
position: Point<f64, Logical>,
time: Instant,
}
/// How long everything must stay unmagnified before the enlarged frames go.
const MAGNIFIED_FRAME_GRACE: Duration = Duration::from_secs(10);
/// How far back the motion path is considered when looking for a shake.
const SHAKE_INTERVAL: Duration = Duration::from_millis(1000);
/// Path-length / bounding-box-diagonal ratio required to count as a shake.
const SHAKE_SENSITIVITY: f64 = 4.0;
/// Minimum bounding-box diagonal (logical px) before a shake is considered.
const SHAKE_DIAGONAL_MIN: f64 = 100.0;
/// Two deltas count as "the same direction" if both lie within this tolerance.
const SHAKE_SAME_SIGN_TOLERANCE: f64 = 1.0;
/// Keep the cursor enlarged for this long after the last detected shake.
const SHAKE_HOLD: Duration = Duration::from_millis(2000);
/// Extra magnification added by each shake, growing from the normal cursor size.
const OVER_MAGNIFICATION: f32 = 1.0;
/// Upper bound on the nominal size (in px) a cursor frame is rasterized at.
const MAX_RASTER_SIZE: u32 = 512;
/// Duration of the grow/shrink animation.
const MAGNIFICATION_ANIM: Duration = Duration::from_millis(200);
/// small movement is ignored and direction stays the same
fn same_direction(a: f64, b: f64) -> bool {
(a >= -SHAKE_SAME_SIGN_TOLERANCE && b >= -SHAKE_SAME_SIGN_TOLERANCE)
|| (a <= SHAKE_SAME_SIGN_TOLERANCE && b <= SHAKE_SAME_SIGN_TOLERANCE)
}
impl CursorStateInner {
@ -282,14 +528,141 @@ impl CursorStateInner {
}
pub fn get_named_cursor(&mut self, shape: CursorIcon) -> &Cursor {
let cursor_theme = &self.cursor_theme;
let cursor_size = self.cursor_size;
self.cursors
.entry(shape)
.or_insert_with(|| Cursor::load(&self.cursor_theme, shape, self.cursor_size))
.or_insert_with(|| Cursor::load(cursor_theme, shape, cursor_size))
}
pub fn size(&self) -> u32 {
self.cursor_size
}
/// Drop the rasterizations only a magnified cursor needed, once nothing has
/// magnified it for [`MAGNIFIED_FRAME_GRACE`].
pub fn drop_magnified_frames(&mut self, now: Instant, zoomed: bool) {
if zoomed || self.is_magnifying() {
self.rest_started = None;
return;
}
let rest_started = *self.rest_started.get_or_insert(now);
if now.duration_since(rest_started) < MAGNIFIED_FRAME_GRACE {
return;
}
self.image_cache.retain(|frame| frame.unmagnified);
}
/// Feed one relative-motion event into the shake detector.
pub fn detect_shake(&mut self, delta: Point<f64, Logical>, now: Instant) {
// Drop samples that have aged out of the time window.
while let Some(oldest) = self.shake_path.front() {
if now.duration_since(oldest.time) >= SHAKE_INTERVAL {
self.shake_path.pop_front();
} else {
break;
}
}
if delta.x != 0.0 || delta.y != 0.0 {
self.shake_path_position += delta;
let sample = PathSample {
position: self.shake_path_position,
time: now,
};
if self.shake_path.len() >= 2 {
let last = self.shake_path[self.shake_path.len() - 1].position;
let prev = self.shake_path[self.shake_path.len() - 2].position;
let last_delta = last - prev;
if same_direction(last_delta.x, delta.x) && same_direction(last_delta.y, delta.y) {
*self.shake_path.back_mut().unwrap() = sample;
} else {
self.shake_path.push_back(sample);
}
} else {
self.shake_path.push_back(sample);
}
}
if self.shake_path.len() < 2 {
return;
}
let first = self.shake_path[0].position;
let (mut left, mut top, mut right, mut bottom) = (first.x, first.y, first.x, first.y);
let mut path_length = 0.0;
for i in 1..self.shake_path.len() {
let p = self.shake_path[i].position;
left = left.min(p.x);
top = top.min(p.y);
right = right.max(p.x);
bottom = bottom.max(p.y);
let step = p - self.shake_path[i - 1].position;
path_length += step.x.hypot(step.y);
}
let diagonal = (right - left).hypot(bottom - top);
if diagonal < SHAKE_DIAGONAL_MIN {
return;
}
// Path noticeably longer than the diagonal => a shake gesture.
if path_length / diagonal > SHAKE_SENSITIVITY {
self.grow(now);
self.shake_path.clear();
}
}
/// grow the cursor by one more increment (unbounded)
fn grow(&mut self, now: Instant) {
self.animate_to(self.magnify_target + OVER_MAGNIFICATION, now);
self.magnify_until = Some(now + SHAKE_HOLD);
}
/// Start a 200ms `InOutCubic` tween from the current size to `target`.
fn animate_to(&mut self, target: f32, now: Instant) {
if (target - self.magnify_target).abs() < f32::EPSILON {
return;
}
self.anim_from = self.magnification;
self.anim_start = Some(now);
self.magnify_target = target;
}
/// Advance the magnification animation and return the current factor.
pub fn animated_magnification(&mut self, now: Instant) -> f32 {
// Begin shrinking back once the hold window elapses.
if let Some(until) = self.magnify_until
&& now >= until
{
self.magnify_until = None;
self.animate_to(1.0, now);
}
self.magnification = match self.anim_start {
Some(start) => {
// `ease` clamps the time to `0.0..=1.0` for us.
let t = now.duration_since(start).as_secs_f32() / MAGNIFICATION_ANIM.as_secs_f32();
if t >= 1.0 {
self.anim_start = None;
}
ease(EaseInOutCubic, self.anim_from, self.magnify_target, t)
}
None => self.magnify_target,
};
self.magnification
}
/// Whether the cursor is currently magnified or pending; drives continued redraws.
pub fn is_magnifying(&self) -> bool {
self.magnify_until.is_some()
|| self.anim_start.is_some()
|| self.magnification > 1.001
|| self.magnify_target > 1.001
}
}
pub fn load_cursor_env() -> (String, u32) {
@ -324,10 +697,34 @@ impl Default for CursorStateInner {
hidden: false,
idle_timer: None,
last_armed: None,
shake_path: VecDeque::new(),
shake_path_position: Point::from((0.0, 0.0)),
magnify_until: None,
magnify_target: 1.0,
magnification: 1.0,
anim_from: 1.0,
anim_start: None,
rest_started: None,
}
}
}
/// Pick the size a cursor frame is rasterized at, given the size the output wants
/// (`needed`) and the size it would want unmagnified (`base`).
///
/// Rasterizations are restricted to `base * 2^n`, rounded up, and clamped to
/// [`MAX_RASTER_SIZE`].
fn raster_size(needed: u32, base: u32) -> u32 {
let base = base.max(1);
let cap = MAX_RASTER_SIZE.max(base);
let mut rung = base;
while rung < needed && rung.saturating_mul(2) <= cap {
rung *= 2;
}
rung
}
#[profiling::function]
pub fn draw_cursor<R>(
renderer: &mut R,
@ -363,32 +760,53 @@ pub fn draw_cursor<R>(
return;
}
let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32;
let frame = state
.get_named_cursor(current_cursor)
.get_image(integer_scale, time.as_millis());
let actual_scale = (frame.size / state.size()).max(1);
let output_scale = scale.x.max(scale.y);
let integer_scale = (output_scale * buffer_scale).ceil() as u32;
let unmagnified_px = state.size() * (output_scale.ceil() as u32);
let size_px = raster_size(state.size() * integer_scale, unmagnified_px);
let pointer_images = &mut state.image_cache;
let maybe_image = pointer_images
.iter()
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None });
let pointer_image = match maybe_image {
Some(image) => image,
// Pick the frame to display without rasterizing, so a cache hit avoids
// any SVG rendering. The `&Cursor` borrow is scoped to this block.
let frame_idx = {
let cursor = state.get_named_cursor(current_cursor);
cursor.frame_index(size_px, time.as_millis())
};
let key = (current_cursor, size_px, frame_idx);
// Rasterize and upload this (shape, size, frame) only if not cached.
let index = match state.image_cache.iter().position(|frame| frame.key == key) {
Some(index) => index,
None => {
let image = {
let cursor = state.get_named_cursor(current_cursor);
cursor.render_frame(size_px, frame_idx)
};
let actual_scale = (image.size / state.size()).max(1);
let buffer = MemoryRenderBuffer::from_slice(
&frame.pixels_rgba,
&image.pixels_rgba,
Fourcc::Argb8888,
(frame.width as i32, frame.height as i32),
(image.width as i32, image.height as i32),
actual_scale as i32,
Transform::Normal,
None,
);
pointer_images.push((frame.clone(), buffer));
pointer_images.last().map(|(_, i)| i).unwrap()
state.image_cache.push(CachedFrame {
key,
image,
buffer,
unmagnified: size_px == unmagnified_px,
});
state.image_cache.len() - 1
}
};
let (frame, pointer_image) = {
let entry = &mut state.image_cache[index];
entry.unmagnified |= size_px == unmagnified_px;
(entry.image.clone(), entry.buffer.clone())
};
let actual_scale = (frame.size / state.size()).max(1);
let hotspot = Point::<i32, BufferCoords>::from((frame.xhot as i32, frame.yhot as i32))
.to_logical(
actual_scale as i32,
@ -402,7 +820,7 @@ pub fn draw_cursor<R>(
MemoryRenderBufferRenderElement::from_buffer(
renderer,
location.to_physical(scale),
pointer_image,
&pointer_image,
None,
None,
None,

View file

@ -41,7 +41,9 @@ where
Workspace(
RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>,
),
Cursor(RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>),
Cursor(
RescaleRenderElement<RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>>,
),
Dnd(SurfaceRenderElement<R>),
MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>),
Postprocess(

View file

@ -286,10 +286,10 @@ impl IndicatorShader {
Uniform::new(
"radius",
[
outer_radius[3] as f32,
outer_radius[1] as f32,
outer_radius[0] as f32,
outer_radius[1] as f32,
outer_radius[2] as f32,
outer_radius[3] as f32,
],
),
Uniform::new("scale", scale as f32),
@ -510,22 +510,35 @@ pub fn cursor_elements<'a, 'frame, R>(
};
let location = pointer.current_location() - output.current_location().to_f64();
// Shake-to-find magnification, applied around the pointer tip.
let cursor_magnification = seat
.user_data()
.get::<cursor::CursorState>()
.map_or(1.0, |s| {
s.lock().unwrap().animated_magnification(Instant::now())
});
let cursor_center = location.to_physical(scale).to_i32_round();
if mode != CursorMode::None {
cursor::draw_cursor(
renderer,
seat,
location,
scale.into(),
zoom_scale,
zoom_scale * cursor_magnification as f64,
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)),
Relocate::Relative,
RescaleRenderElement::from_element(
RelocateRenderElement::from_element(
elem,
Point::from((-hotspot.x, -hotspot.y)),
Relocate::Relative,
),
cursor_center,
cursor_magnification as f64,
),
focal_point
.as_logical()
@ -886,8 +899,7 @@ where
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))
surface_padding(states).and_then(|padding| pad_rect(geometry, &padding))
})
.unwrap_or(geometry);
let radii = with_states(layer.wl_surface(), |states| {

View file

@ -86,12 +86,6 @@ impl ShadowShader {
let offset = [0., 5.];
let color = [0., 0., 0., if dark_mode { 0.45 } else { 0.35 }];
let radius = radius.map(|r| ceil(r as f64));
let radius = [
radius[3], // top_left
radius[1], // top_right
radius[0], // bottom_right
radius[2], // bottom_left
];
let width = softness;
let sigma = width / 2.;

View file

@ -247,10 +247,10 @@ impl BlurElement {
Uniform::new(
"corner_radius",
[
radii[3] as f32,
radii[1] as f32,
radii[0] as f32,
radii[1] as f32,
radii[2] as f32,
radii[3] as f32,
],
),
Uniform::new(

View file

@ -99,10 +99,10 @@ where
Uniform::new(
"corner_radius",
[
radius[3] as f32,
radius[1] as f32,
radius[0] as f32,
radius[1] as f32,
radius[2] as f32,
radius[3] as f32,
],
),
Uniform::new(
@ -145,10 +145,10 @@ where
geo: Rectangle<f64, Logical>,
radius: [u8; 4],
) -> [Rectangle<f64, Logical>; 4] {
let top_left = radius[3] as f64;
let top_left = radius[0] as f64;
let top_right = radius[1] as f64;
let bottom_right = radius[0] as f64;
let bottom_left = radius[2] as f64;
let bottom_right = radius[2] as f64;
let bottom_left = radius[3] as f64;
[
Rectangle::new(geo.loc, Size::from((top_left, top_left))),

View file

@ -6,6 +6,7 @@ use crate::{
shell::{Devices, SeatExt},
state::{BackendData, Common},
utils::prelude::*,
wayland::protocols::drm::WlDrmState,
};
use anyhow::{Context, Result, anyhow};
use cosmic_comp_config::output::comp::{OutputConfig, TransformDef};
@ -273,7 +274,7 @@ fn init_egl_client_side(
.create_global_with_default_feedback::<State>(dh, &feedback);
let render_node = render_node.unwrap().unwrap();
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
dh,
render_node
.dev_path_with_type(NodeType::Render)
@ -284,7 +285,7 @@ fn init_egl_client_side(
))?,
dmabuf_formats,
&dmabuf_global,
);
));
info!("EGL hardware-acceleration enabled.");
}
@ -309,7 +310,9 @@ impl State {
WinitEvent::Focus(true) => {
for seat in self.common.shell.read().seats.iter() {
let devices = seat.user_data().get::<Devices>().unwrap();
if devices.has_device(&WinitVirtualDevice) {
if devices
.has_device(&WinitVirtualDevice, &crate::input::InputBackendId::Normal)
{
seat.set_active_output(&self.backend.winit().output);
break;
}
@ -339,7 +342,9 @@ impl State {
render_ping.ping();
}
WinitEvent::Redraw => render_ping.ping(),
WinitEvent::Input(event) => self.process_input_event(event),
WinitEvent::Input(event) => {
self.process_input_event(event, crate::input::InputBackendId::Normal)
}
WinitEvent::CloseRequested => {
self.common.should_stop = true;
}

View file

@ -6,6 +6,7 @@ use crate::{
shell::{Devices, SeatExt},
state::{BackendData, Common},
utils::prelude::*,
wayland::protocols::drm::WlDrmState,
};
use anyhow::{Context, Result, anyhow};
use cosmic_comp_config::output::comp::OutputConfig;
@ -497,7 +498,7 @@ where
.common
.dmabuf_state
.create_global_with_default_feedback::<State>(dh, &default_feedback);
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
dh,
render_node
.dev_path_with_type(NodeType::Render)
@ -508,7 +509,7 @@ where
))?,
renderer.dmabuf_formats(),
&dmabuf_global,
);
));
info!("EGL hardware-acceleration enabled.");
@ -533,14 +534,14 @@ impl State {
let device = event.device();
for seat in self.common.shell.read().seats.iter() {
let devices = seat.user_data().get::<Devices>().unwrap();
if devices.has_device(&device) {
if devices.has_device(&device, &crate::input::InputBackendId::Normal) {
seat.set_active_output(&output);
break;
}
}
};
self.process_input_event(event);
self.process_input_event(event, crate::input::InputBackendId::Normal);
// TODO actually figure out the output
for output in self.common.shell.read().outputs() {
self.backend.x11().schedule_render(output);

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
input::InputBackendId,
shell::Shell,
state::{BackendData, State},
utils::prelude::OutputExt,
@ -13,8 +14,11 @@ use cosmic_config::{ConfigGet, CosmicConfigEntry};
use cosmic_settings_config::window_rules::ApplicationException;
use cosmic_settings_config::{Shortcuts, shortcuts, window_rules};
use serde::{Deserialize, Serialize};
use smithay::utils::{Clock, Monotonic};
use smithay::wayland::xdg_activation::XdgActivationState;
use smithay::{
backend::input::InputTime,
utils::{Clock, Monotonic},
};
pub use smithay::{
backend::input::{self as smithay_input, KeyState},
input::keyboard::{Keysym, ModifiersState, keysyms as KeySyms},
@ -45,8 +49,8 @@ mod types;
use cosmic::config::CosmicTk;
pub use cosmic_comp_config::EdidProduct;
use cosmic_comp_config::{
ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig,
XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
ActivationPolicy, AppearanceConfig, CosmicCompConfig, DecorationPreference, KeyboardConfig,
TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
output::comp::{
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
@ -168,7 +172,7 @@ pub enum ColorFilter {
}
impl Config {
pub fn load(loop_handle: &LoopHandle<'_, State>) -> Config {
pub fn load(loop_handle: &LoopHandle<'_, State>, kiosk_mode: bool) -> Config {
let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
let source = cosmic_config::calloop::ConfigWatchSource::new(&config).unwrap();
loop_handle
@ -244,40 +248,48 @@ impl Config {
// Source key bindings from com.system76.CosmicSettings.Shortcuts
let settings_context = shortcuts::context().expect("Failed to load shortcuts config");
let system_actions = shortcuts::system_actions(&settings_context);
let shortcuts = shortcuts::shortcuts(&settings_context);
let mut system_actions = Default::default();
let mut shortcuts = Default::default();
// Kiosk mode disables shortcuts
if !kiosk_mode {
system_actions = shortcuts::system_actions(&settings_context);
shortcuts = shortcuts::shortcuts(&settings_context);
// Listen for updates to the keybindings config.
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
Ok(source) => {
if let Err(err) = loop_handle.insert_source(source, |(config, keys), (), state| {
for key in keys {
match key.as_str() {
// Reload the keyboard shortcuts config.
"custom" | "defaults" => {
state.common.config.shortcuts = shortcuts::shortcuts(&config);
// Listen for updates to the keybindings config.
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
Ok(source) => {
if let Err(err) =
loop_handle.insert_source(source, |(config, keys), (), state| {
for key in keys {
match key.as_str() {
// Reload the keyboard shortcuts config.
"custom" | "defaults" => {
state.common.config.shortcuts =
shortcuts::shortcuts(&config);
}
"system_actions" => {
state.common.config.system_actions =
shortcuts::system_actions(&config);
}
_ => (),
}
}
"system_actions" => {
state.common.config.system_actions =
shortcuts::system_actions(&config);
}
_ => (),
}
})
{
warn!(
?err,
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
);
}
}) {
warn!(
?err,
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
);
}
}
Err(err) => warn!(
?err,
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
),
};
Err(err) => warn!(
?err,
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
),
};
}
let window_rules_context =
window_rules::context().expect("Failed to load window rules config");
@ -537,7 +549,13 @@ impl Config {
primary.config_mut().xwayland_primary = true;
}
}
for output in outputs.iter().filter(|o| o.mirroring().is_none()) {
// sort by connector name for a deterministic layout independent of hotplug order
let mut sorted_outputs = outputs
.iter()
.filter(|o| o.mirroring().is_none())
.collect::<Vec<_>>();
sorted_outputs.sort_by_key(|o| o.name());
for output in sorted_outputs {
{
let mut config = output.config_mut();
config.position = (w, 0);
@ -769,13 +787,12 @@ pub fn change_modifier_state(
const X11_KEYCODE_OFFSET: u32 = 8;
let mut input = |key_state, scan_code| {
let time = state.common.clock.now().as_millis();
let _ = keyboard.input(
state,
smithay_input::Keycode::new(scan_code + X11_KEYCODE_OFFSET),
key_state,
SERIAL_COUNTER.next_serial(),
time,
InputTime::now(),
|_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
);
};
@ -820,6 +837,29 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
}
}
}
let ei_connections = state
.common
.ei_keyboard_source
.keys()
.cloned()
.collect::<Vec<_>>();
for conn in &ei_connections {
state.release_ei_keyboard(conn);
state.clear_input_source_state(&InputBackendId::Ei(conn.clone()));
}
for ei_seat in state.common.ei_seats.values() {
if let Err(err) =
ei_seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&value))
{
warn!(?err, "Failed to update libei keyboard keymap");
}
}
if !state.common.ei_seats.is_empty() {
let seat = state.common.shell.read().seats.last_active().clone();
if let Some(keyboard) = seat.get_keyboard() {
state.broadcast_ei_keyboard_modifiers(&keyboard);
}
}
state.common.config.cosmic_conf.xkb_config = value;
}
"keyboard_config" => {
@ -947,6 +987,10 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
}
}
}
"cursor_shake_to_find" => {
let new = get_config::<bool>(&config, "cursor_shake_to_find");
state.common.config.cosmic_conf.cursor_shake_to_find = new;
}
"cursor_hide_timeout" => {
let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout");
if new != state.common.config.cosmic_conf.cursor_hide_timeout {
@ -972,6 +1016,13 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
state.common.config.cosmic_conf.activation_policy = new;
}
}
"decoration_preference" => {
let new = get_config::<DecorationPreference>(&config, "decoration_preference");
if new != state.common.config.cosmic_conf.decoration_preference {
state.common.config.cosmic_conf.decoration_preference = new;
state.update_decorations();
}
}
_ => {}
}
}

View file

@ -150,9 +150,10 @@ impl A11yKeyboardMonitorState {
}
pub fn key_event(&self, modifiers: &ModifiersState, keysym: &KeysymHandle, state: KeyState) {
let has_key_grab = self.has_key_grab(modifiers, keysym.modified_sym());
let clients = self.clients.lock().unwrap();
for (unique_name, client) in clients.0.iter() {
if !client.watched && !self.has_key_grab(modifiers, keysym.modified_sym()) {
if !client.watched && !has_key_grab {
continue;
}

80
src/dbus/ei.rs Normal file
View file

@ -0,0 +1,80 @@
use std::{
os::unix::net::UnixStream,
sync::{Arc, Mutex},
};
use smithay::reexports::calloop;
use zbus::names::{UniqueName, WellKnownName};
use super::name_owners::NameOwners;
static ALLOWED_NAMES: &[WellKnownName] = &[
WellKnownName::from_static_str_unchecked("org.freedesktop.impl.portal.desktop.cosmic"),
WellKnownName::from_static_str_unchecked("com.system76.CosmicOSK"),
];
/// Channel for handing the EI socketpair (and requested device types)
/// It's `None` until the EI sender side has been set up
type EiSender = Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>;
struct Ei {
ei_sender: EiSender,
name_owners: NameOwners,
}
impl Ei {
async fn check_sender_allowed(&self, sender: &UniqueName<'_>) -> zbus::fdo::Result<()> {
if self.name_owners.check_owner(sender, ALLOWED_NAMES).await {
Ok(())
} else {
Err(zbus::fdo::Error::AccessDenied("Access denied".to_string()))
}
}
}
#[zbus::interface(name = "com.system76.CosmicComp.Ei")]
impl Ei {
/// Create a new EI sender context
async fn get_sender_socket(
&self,
device_types: u32,
#[zbus(header)] header: zbus::message::Header<'_>,
) -> zbus::fdo::Result<zbus::zvariant::OwnedFd> {
if let Some(sender) = header.sender() {
self.check_sender_allowed(sender).await?;
}
let (comp_stream, client_stream) = UnixStream::pair().map_err(|err| {
zbus::fdo::Error::Failed(format!("Failed to create socket pair: {err}"))
})?;
{
let guard = self.ei_sender.lock().unwrap();
let sender = guard
.as_ref()
.ok_or_else(|| zbus::fdo::Error::Failed("EI sender not available".to_string()))?;
sender.send((comp_stream, device_types)).map_err(|err| {
zbus::fdo::Error::Failed(format!("Failed to hand off EI socket: {err}"))
})?;
}
Ok(std::os::fd::OwnedFd::from(client_stream).into())
}
}
/// Register the `com.system76.CosmicComp.Ei` interface on the shared session connection.
pub async fn init(
conn: &zbus::Connection,
name_owners: &NameOwners,
ei_sender: EiSender,
) -> zbus::Result<()> {
let ei = Ei {
ei_sender,
name_owners: name_owners.clone(),
};
conn.object_server()
.at("/com/system76/CosmicComp/Ei", ei)
.await?;
conn.request_name("com.system76.CosmicComp").await?;
Ok(())
}

View file

@ -9,11 +9,13 @@ use std::{
cell::{RefCell, RefMut},
collections::HashMap,
rc::Rc,
sync::{Arc, Mutex},
};
use tracing::{error, warn};
pub mod a11y_keyboard_monitor;
use a11y_keyboard_monitor::A11yKeyboardMonitorState;
pub mod ei;
#[cfg(feature = "logind")]
pub mod logind;
mod name_owners;
@ -29,6 +31,7 @@ struct DBusStateInner {
session_conn: zbus::Result<zbus::Connection>,
system_conn: zbus::Result<zbus::Connection>,
a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>,
ei_sender: Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>,
}
impl DBusState {
@ -42,6 +45,7 @@ impl DBusState {
session_conn,
system_conn,
a11y_keyboard_monitor: RefCell::new(None),
ei_sender: Arc::new(Mutex::new(None)),
}));
evlh.insert_source(source, |_, _, _| {}).unwrap();
let state_clone = state.clone();
@ -65,6 +69,10 @@ impl DBusState {
RefMut::filter_map(self.0.a11y_keyboard_monitor.borrow_mut(), |x| x.as_mut()).ok()
}
pub fn set_ei_sender(&self, sender: calloop::channel::Sender<crate::libei::EiRequest>) {
*self.0.ei_sender.lock().unwrap() = Some(sender);
}
// TODO Lazy async init when we don't have anything blocking main thread
async fn session_conn(&self) -> zbus::Result<&zbus::Connection> {
self.0.session_conn.as_ref().map_err(|err| err.clone())
@ -85,6 +93,7 @@ async fn init_session(state: &DBusState) -> zbus::Result<()> {
let a11y_keyboard_monitor_state =
A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?;
*state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state);
ei::init(conn, &name_owners, state.0.ei_sender.clone()).await?;
Ok(())
}

View file

@ -2,6 +2,7 @@
use crate::{
config::{Action, PrivateAction},
input::InputBackendId,
shell::{
FocusResult, InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
focus::{FocusTarget, target::KeyboardFocusTarget},
@ -17,6 +18,7 @@ use cosmic_config::ConfigSet;
use cosmic_settings_config::shortcuts;
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection};
use smithay::{
backend::input::InputTime,
input::{Seat, pointer::MotionEvent},
utils::{Point, Serial},
};
@ -28,6 +30,8 @@ use std::{os::unix::process::CommandExt, thread};
use super::gestures;
const MAX_ZOOM: f64 = 256.0;
fn propagate_by_default(action: &shortcuts::Action) -> bool {
matches!(
action,
@ -39,9 +43,10 @@ impl State {
pub fn handle_action(
&mut self,
action: Action,
backend_id: &InputBackendId,
seat: &Seat<State>,
serial: Serial,
time: u32,
time: InputTime,
pattern: shortcuts::Binding,
direction: Option<Direction>,
) {
@ -69,7 +74,7 @@ impl State {
Action::Shortcut(action) => {
let propagate = propagate_by_default(&action);
self.handle_shortcut_action(
action, seat, serial, time, pattern, direction, propagate,
action, backend_id, seat, serial, time, pattern, direction, propagate,
)
}
Action::Private(PrivateAction::Escape) => {
@ -143,9 +148,10 @@ impl State {
pub fn handle_shortcut_action(
&mut self,
action: shortcuts::Action,
backend_id: &InputBackendId,
seat: &Seat<State>,
serial: Serial,
time: u32,
time: InputTime,
pattern: shortcuts::Binding,
direction: Option<Direction>,
propagate: bool,
@ -232,6 +238,7 @@ impl State {
{
self.handle_shortcut_action(
Action::SwitchOutput(inferred),
backend_id,
seat,
serial,
time,
@ -271,6 +278,7 @@ impl State {
{
self.handle_shortcut_action(
Action::SwitchOutput(inferred),
backend_id,
seat,
serial,
time,
@ -392,6 +400,7 @@ impl State {
} else {
Action::SendToOutput(inferred)
},
backend_id,
seat,
serial,
time,
@ -417,6 +426,7 @@ impl State {
} else {
Action::SendToWorkspace(1)
},
backend_id,
seat,
serial,
time,
@ -483,6 +493,7 @@ impl State {
} else {
Action::SendToOutput(inferred)
},
backend_id,
seat,
serial,
time,
@ -508,6 +519,7 @@ impl State {
} else {
Action::SendToLastWorkspace
},
backend_id,
seat,
serial,
time,
@ -532,7 +544,9 @@ impl State {
if propagate
&& let Some((serial, prev_output, prev_idx)) =
shell.previous_workspace_idx.take()
&& seat.last_modifier_change().is_some_and(|s| s == serial)
&& seat
.last_modifier_change_for(backend_id)
.is_some_and(|s| s == serial)
&& prev_output == current_output
{
let _ = shell.activate(
@ -705,6 +719,7 @@ impl State {
if res.is_ok() {
self.handle_shortcut_action(
Action::SwitchOutput(direction),
backend_id,
seat,
serial,
time,
@ -741,7 +756,8 @@ impl State {
};
if let Some(direction) = dir {
if let Some(last_mod_serial) = seat.last_modifier_change() {
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id)
{
let mut shell = self.common.shell.write();
if !shell
.previous_workspace_idx
@ -776,6 +792,7 @@ impl State {
self.handle_shortcut_action(
action,
backend_id,
seat,
serial,
time,
@ -800,7 +817,7 @@ impl State {
.move_current_element(direction, seat);
match res {
MoveResult::MoveFurther(_move_further) => {
if let Some(last_mod_serial) = seat.last_modifier_change() {
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id) {
let mut shell = self.common.shell.write();
if !shell
.previous_workspace_idx
@ -834,6 +851,7 @@ impl State {
self.handle_shortcut_action(
action,
backend_id,
seat,
serial,
time,
@ -1101,7 +1119,14 @@ impl State {
}
if zoom_seat == *seat {
let new_level = (current_level + change).max(1.0);
let factor = 1.0 + change.abs();
let new_level = if change < 0. {
current_level / factor
} else {
current_level * factor
}
.clamp(1.0, MAX_ZOOM);
let new_level = if new_level < 1.01 { 1.0 } else { new_level };
shell.trigger_zoom(
seat,
Some(&output),

File diff suppressed because it is too large Load diff

256
src/input/tablet_emu.rs Normal file
View file

@ -0,0 +1,256 @@
// SPDX-License-Identifier: GPL-3.0-only
//! Pointer Emulating TabletTool grab.
use crate::{shell::focus::target::PointerFocusTarget, state::State};
use smithay::{
backend::input::{ButtonState, InputTime, MouseButton},
input::{
Seat, pointer,
tablet::{
self,
tool::{
AxisFrame, ButtonEvent, DownEvent, GrabStartData, ProximityInEvent,
ProximityOutEvent, TabletToolGrab, TabletToolInnerHandle, UpEvent,
},
},
},
utils::{Logical, Point, SERIAL_COUNTER},
};
// from https://gitlab.freedesktop.org/libinput/libinput/-/blob/main/include/linux/linux/input-event-codes.h
const BTN_STYLUS: u32 = 0x14b;
const BTN_STYLUS_2: u32 = 0x14c;
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
const BTN_FORWARD: u32 = 0x115;
const BTN_BACK: u32 = 0x116;
pub struct PointerEmulationGrab {
start_data: GrabStartData<State>,
seat: Seat<State>,
above_source: bool,
tip_down: bool,
button_down: Vec<u32>,
}
impl PointerEmulationGrab {
pub fn new(start_data: GrabStartData<State>, seat: Seat<State>) -> Self {
Self {
start_data,
seat,
above_source: true,
tip_down: false,
button_down: Vec::new(),
}
}
fn button_to_mouse(&self, button: u32) -> Option<MouseButton> {
// FIXME: This should be configurable.
if button == BTN_STYLUS {
Some(MouseButton::Right)
} else if button == BTN_STYLUS_2 {
Some(MouseButton::Middle)
} else {
None
}
}
fn pointer_button(&self, data: &mut State, button: MouseButton, state: ButtonState) {
if let Some(pointer) = self.seat.get_pointer() {
let button = match button {
MouseButton::Left => BTN_LEFT,
MouseButton::Right => BTN_RIGHT,
MouseButton::Middle => BTN_MIDDLE,
MouseButton::Back => BTN_BACK,
MouseButton::Forward => BTN_FORWARD,
_ => return,
};
pointer.button(
data,
&pointer::ButtonEvent {
serial: SERIAL_COUNTER.next_serial(),
button,
state,
time: InputTime::now(),
},
);
pointer.frame(data);
}
}
}
type Type = tablet::tool::MotionEvent;
impl TabletToolGrab<State> for PointerEmulationGrab {
fn proximity_in(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &ProximityInEvent,
) {
handle.proximity_in(data, focus.clone(), event);
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
handle.unset_grab(
self,
data,
SERIAL_COUNTER.next_serial(),
InputTime::now(),
true,
);
} else if let Some(pointer) = self.seat.get_pointer() {
pointer.motion(
data,
focus,
&pointer::MotionEvent {
location: event.location,
serial: SERIAL_COUNTER.next_serial(),
time: InputTime::now(),
},
);
pointer.frame(data);
}
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
handle.proximity_out(data, event);
handle.unset_grab(self, data, event.serial, event.time, true);
}
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &DownEvent,
) {
handle.down(data, event);
self.tip_down = true;
self.pointer_button(data, MouseButton::Left, ButtonState::Pressed);
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &UpEvent,
) {
handle.up(data, event);
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
self.tip_down = false;
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
handle.unset_grab(self, data, event.serial, event.time, true);
}
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &Type,
) {
self.above_source = focus.as_ref().map(|(target, _)| target)
== self.start_data.focus.as_ref().map(|(target, _)| target);
handle.motion(data, self.start_data.focus.clone(), event);
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
handle.unset_grab(
self,
data,
SERIAL_COUNTER.next_serial(),
InputTime::now(),
true,
);
} else if let Some(pointer) = self.seat.get_pointer() {
pointer.motion(
data,
focus,
&pointer::MotionEvent {
location: event.location,
serial: SERIAL_COUNTER.next_serial(),
time: InputTime::now(),
},
);
pointer.frame(data);
}
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ButtonEvent,
) {
handle.button(data, event);
self.button_down.retain(|b| b != &event.button);
if matches!(event.state, ButtonState::Pressed) {
self.button_down.push(event.button);
}
if let Some(button) = self.button_to_mouse(event.button) {
self.pointer_button(data, button, event.state);
}
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
handle.unset_grab(
self,
data,
SERIAL_COUNTER.next_serial(),
InputTime::now(),
true,
);
}
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
frame: AxisFrame,
) {
handle.axis(data, frame);
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
time: InputTime,
) {
handle.frame(data, time);
}
fn unset(&mut self, data: &mut State) {
if self.tip_down {
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
}
let buttons = std::mem::take(&mut self.button_down);
for button in buttons.into_iter() {
if let Some(button) = self.button_to_mouse(button) {
self.pointer_button(data, button, ButtonState::Released);
}
}
}
fn start_data(&self) -> &GrabStartData<State> {
&self.start_data
}
}

View file

@ -16,7 +16,7 @@ use smithay::{
};
use anyhow::{Context, Result};
use state::{LastRefresh, State};
use state::{BackendData, LastRefresh, State};
use std::{
env,
ffi::OsString,
@ -43,6 +43,7 @@ pub mod dbus;
pub mod debug;
pub mod hooks;
pub mod input;
pub mod libei;
mod logger;
pub mod session;
pub mod shell;
@ -176,6 +177,10 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
with_xwayland,
kiosk_command,
);
// Set up the libei sender side before the backend spawns Xwayland.
let ei_sender = libei::setup_ei(&event_loop.handle());
state.common.dbus_state.set_ei_sender(ei_sender);
// init backend
backend::init_backend_auto(&display, &mut event_loop, &mut state)?;
@ -222,19 +227,17 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
// Kiosk child exited with status
Ok(Some(exit_status)) => {
info!("Command exited with status {:?}", exit_status);
match exit_status.code() {
// Exiting with the same status as the kiosk child
Some(code) => process::exit(code),
// The kiosk child exited with signal, exiting with error
None => process::exit(1),
}
// Stop cleanly so surface threads are joined before exit() (signal -> 1).
state.common.kiosk_exit_code = Some(exit_status.code().unwrap_or(1));
state.common.should_stop = true;
}
// Command still running
Ok(None) => {}
// Kiosk child disappeared, exiting with error
Err(err) => {
warn!(?err, "Failed to wait for command");
process::exit(1);
state.common.kiosk_exit_code = Some(1);
state.common.should_stop = true;
}
}
}
@ -245,10 +248,32 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
let _ = child.kill();
}
let kiosk_exit_code = state.common.kiosk_exit_code;
// Join surface threads before exit() so no thread is mid-eglCreateSync when
// Mesa's atexit handlers run and corrupt the heap (issue #2375). Safe here
// because the event loop has stopped; an unconditional join in Surface::Drop
// would instead deadlock against apply_config_for_outputs.
if let BackendData::Kms(kms) = &mut state.backend {
// Release master first so the surface drop path skips its blocking commit.
for device in kms.drm_devices.values_mut() {
device.drm.pause();
}
for device in kms.drm_devices.values_mut() {
for (_, surface) in device.inner.surfaces.drain() {
surface.drop_and_join();
}
}
}
// drop eventloop & state before logger
std::mem::drop(event_loop);
std::mem::drop(state);
if let Some(code) = kiosk_exit_code {
process::exit(code);
}
Ok(())
}
@ -264,7 +289,7 @@ Project home page: https://github.com/pop-os/cosmic-comp
Options:
-h, --help Show this message
--no-xwayland Run without Xwayland
-v, --version Show the version of cosmic-comp"#
-V, --version Show the version of cosmic-comp"#
);
}

230
src/libei.rs Normal file
View file

@ -0,0 +1,230 @@
use std::os::unix::net::UnixStream;
use reis::eis;
use smithay::reexports::reis;
use smithay::backend::libei::{EiInput, EiInputEvent, EiRegion};
use smithay::input::keyboard::Keysym;
use smithay::reexports::calloop;
use smithay::wayland::input_method::InputMethodSeat;
use smithay::wayland::text_input::TextInputSeat;
use crate::config::xkb_config_to_wl;
use crate::input::InputBackendId;
use crate::state::{BackendData, State};
use crate::utils::{geometry::RectGlobalExt, prelude::OutputExt};
// Requested device types for an EI connection, mirroring the XDG RemoteDesktop portal `DeviceType` bitmask
const DEVICE_TYPE_KEYBOARD: u32 = 1;
const DEVICE_TYPE_POINTER: u32 = 2;
const DEVICE_TYPE_TOUCHSCREEN: u32 = 4;
// Name of the EI absolute-pointer device. Shared so the connect path and the
// re-advertise-on-output-change path recreate the same device.
const ABSOLUTE_POINTER_NAME: &str = "virtual absolute pointer";
pub type EiRequest = (UnixStream, u32);
/// Build the regions advertised on the EI absolute devices (absolute pointer and touch) for the
/// output layout: one region per output, each at its **global logical** offset with
/// its logical size and scale.
pub fn absolute_regions(state: &State) -> Vec<EiRegion> {
let shell = state.common.shell.read();
shell
.outputs()
.map(|output| {
let scale = output.current_scale().fractional_scale();
EiRegion {
// Keep the signed logical rect
// the u32 clamp happens at the `ei_device.region` in smithay.
rect: output.geometry().as_logical(),
scale: scale as f32,
// Tie the region to its output so a client can correlate it with the
// matching screencast stream (the portal advertises the same name).
mapping_id: Some(output.name()),
}
})
.collect()
}
/// Re-advertise the coordinate regions on every active EI seat's absolute devices.
pub fn refresh_absolute_pointer_regions(state: &State) {
if state.common.ei_seats.is_empty() {
return;
}
let regions = absolute_regions(state);
for seat in state.common.ei_seats.values() {
seat.update_regions(&regions);
}
}
pub fn setup_ei(
handle: &calloop::LoopHandle<'static, State>,
) -> calloop::channel::Sender<EiRequest> {
let (sender, channel) = calloop::channel::channel::<EiRequest>();
let handle_clone = handle.clone();
handle
.insert_source(channel, move |event, _, _| {
let calloop::channel::Event::Msg((stream, device_types)) = event else {
return;
};
let context = match eis::Context::new(stream) {
Ok(context) => context,
Err(err) => {
tracing::error!("Failed to create EI context: {}", err);
return;
}
};
let source = EiInput::new(context);
if let Err(err) =
handle_clone.insert_source(source, move |event, connection, data| match event {
EiInputEvent::Connected => {
let conn = connection.eis_connection().clone();
let seat = connection.add_seat("default");
let wants_keyboard = device_types & DEVICE_TYPE_KEYBOARD != 0;
if wants_keyboard {
let conf = data.common.config.xkb_config();
// The ei_keyboard device is given the compositor keymap; its key
// events feed the shared seat like any other keyboard.
let _ = seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&conf));
// The text device lets clients inject keysyms/utf8 directly, delivered
seat.add_text("virtual text");
}
if device_types & DEVICE_TYPE_POINTER != 0 {
seat.add_pointer("virtual pointer");
let regions = absolute_regions(data);
seat.add_pointer_absolute(ABSOLUTE_POINTER_NAME, &regions);
}
if device_types & DEVICE_TYPE_TOUCHSCREEN != 0 {
let regions = absolute_regions(data);
seat.add_touch("virtual touch", &regions);
}
// Kb-capable connections get a shared-seat source so their `ei_keyboard`
// key events feed the seat keyboard tracking.
if wants_keyboard {
data.common.ei_keyboard_source.insert(
conn.clone(),
smithay::input::keyboard::KeyboardSource::new_auxiliary(),
);
}
// Track the seat for every connection
data.common.ei_seats.insert(conn, seat);
data.update_ei_input_method();
}
EiInputEvent::Disconnected => {
let conn = connection.eis_connection().clone();
let backend_id = InputBackendId::Ei(conn.clone());
// Release any keys/modifiers and pointer buttons this remote still holds
data.release_ei_keyboard(&conn);
data.release_ei_pointer(&conn);
data.clear_input_source_state(&backend_id);
data.common.ei_seats.remove(&conn);
data.common.ei_keyboard_source.remove(&conn);
data.common.ei_pointer_buttons.remove(&conn);
data.update_ei_input_method();
// Notify the remaining libei clients of the now-cleared modifier state
let seat = data.common.shell.read().seats.last_active().clone();
if let Some(keyboard) = seat.get_keyboard() {
data.broadcast_ei_keyboard_modifiers(&keyboard);
}
}
EiInputEvent::Event(event) => {
use smithay::backend::input::{InputEvent, KeyboardKeyEvent};
match event {
InputEvent::Keyboard { event } => {
data.inject_ei_key(
connection.eis_connection(),
event.key_code(),
event.state(),
);
}
other => {
let backend_id =
InputBackendId::Ei(connection.eis_connection().clone());
data.process_input_event(other, backend_id);
if matches!(data.backend, BackendData::Kms(_)) {
for output in data.common.shell.read().outputs() {
data.backend.kms().schedule_render(output);
}
}
}
}
}
EiInputEvent::TextKeysym { keysym, state } => {
data.inject_ei_text_keysym(connection.eis_connection(), keysym, state);
}
EiInputEvent::TextUtf8 { text } => {
data.inject_ei_text(&text);
}
})
{
tracing::error!("Failed to insert EI input source: {}", err);
}
})
.expect("Failed to insert EI channel source into the event loop");
sender
}
impl State {
/// Act as the input method for text injection while any text-capable EI connection is
/// active, so `ei_text` UTF-8 can be committed into the focused app even without a real
/// IME, but only when none is bound (a real IME always wins)
pub(crate) fn update_ei_input_method(&mut self) {
let active = !self.common.ei_keyboard_source.is_empty();
let seats = self
.common
.shell
.read()
.seats
.iter()
.cloned()
.collect::<Vec<_>>();
for seat in seats {
let has_ime = seat.input_method().has_instance();
let text_input = seat.text_input();
if active {
if !has_ime {
text_input.set_compositor_input_method(true);
}
} else {
text_input.set_compositor_input_method(false);
}
}
}
/// Inject UTF-8 text (from an EI `ei_text` device) into the focused client.
pub fn inject_ei_text(&mut self, text: &str) {
let seat = self.common.shell.read().seats.last_active().clone();
// Only commit through text-input when we're the active input method (no real IME)
if !seat.input_method().has_instance() {
let text_input = seat.text_input();
let mut injected = false;
text_input.with_active_text_input(|ti, _surface| {
ti.commit_string(Some(text.to_owned()));
injected = true;
});
if injected {
text_input.done(false);
return;
}
}
// Bind the whole chunk to spare keycodes in one temporary keymap per batch (delivered
// to just the focused client), so we change the keymap ~once per chunk instead of once
// per character. Leaves the seat's own keyboard state untouched.
let keysyms: Vec<Keysym> = text
.chars()
.map(Keysym::from_char)
.filter(|keysym| keysym.raw() != 0)
.collect();
let Some(keyboard) = seat.get_keyboard() else {
return;
};
// At most ~247 keysyms fit one spare keymap (keycodes 9..=255); leave margin.
const BATCH: usize = 240;
for batch in keysyms.chunks(BATCH) {
keyboard.inject_text_keysyms(self, batch);
}
}
}

View file

@ -1,15 +1,6 @@
// 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

@ -12,7 +12,7 @@ use id_tree::NodeId;
use smithay::{
backend::{
drm::DrmNode,
input::KeyState,
input::{InputTime, KeyState},
renderer::{
element::{
Element, Kind, RenderElement, UnderlyingStorage,
@ -1012,7 +1012,7 @@ impl KeyboardTarget<State> for CosmicMapped {
key: KeysymHandle<'_>,
state: KeyState,
serial: Serial,
time: u32,
time: InputTime,
) {
match &self.element {
CosmicMappedInternal::Stack(s) => {

View file

@ -11,7 +11,7 @@ use crate::{
shell::{
element::{CosmicMappedKey, CosmicMappedKeyInner},
focus::target::PointerFocusTarget,
grabs::{ReleaseMode, ResizeEdge},
grabs::{GrabType, ReleaseMode, ResizeEdge},
layout::tiling::NodeDesc,
},
state::State,
@ -38,7 +38,7 @@ use shortcuts::action::{Direction, FocusDirection};
use smithay::{
backend::{
drm::DrmNode,
input::KeyState,
input::{InputTime, KeyState, TabletToolDescriptor},
renderer::{
ImportAll, ImportMem, Renderer,
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
@ -52,14 +52,23 @@ use smithay::{
Seat,
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
pointer::{
AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorImageStatus,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
PointerTarget, RelativeMotionEvent,
},
tablet::{
Tablet, TabletSeatTrait,
tool::{
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
UpEvent as ToolUpEvent,
},
},
touch::{
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
},
},
output::Output,
@ -112,6 +121,7 @@ pub struct CosmicStackInternal {
previous_keyboard: AtomicUsize,
pointer_entered: AtomicU8,
touch_serial: AtomicU32,
tablet_serial: AtomicU32,
reenter: AtomicBool,
potential_drag: Mutex<Option<usize>>,
override_alive: AtomicBool,
@ -171,6 +181,7 @@ impl CosmicStack {
previous_keyboard: AtomicUsize::new(0),
pointer_entered: AtomicU8::new(0),
touch_serial: AtomicU32::new(0),
tablet_serial: AtomicU32::new(0),
reenter: AtomicBool::new(false),
potential_drag: Mutex::new(None),
override_alive: AtomicBool::new(true),
@ -831,7 +842,7 @@ impl CosmicStack {
)));
};
let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]);
let radii = radii.map(|[_, _, c, d]| [0, 0, c, d]);
windows[active].push_render_elements(
renderer,
window_loc,
@ -909,12 +920,19 @@ impl CosmicStack {
false,
);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer()
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => seat
.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
.set_grab(state, grab, serial, focus),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus),
}
}
});
@ -997,17 +1015,17 @@ impl CosmicStack {
.corner_radius(geometry_size)
.unwrap_or([default_radius; 4]);
corners[0] = 0;
corners[1] = 0;
corners[3] = 0;
corners
} else {
let mut corners = active_window.corner_radius(geometry_size).unwrap_or(radii);
corners[0] = radii[0].max(corners[0]);
corners[0] = radii[0];
corners[1] = radii[1];
corners[2] = radii[2].max(corners[2]);
corners[3] = radii[3];
corners[3] = radii[3].max(corners[3]);
corners
}
@ -1089,12 +1107,19 @@ impl Program for CosmicStackInternal {
false,
);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer()
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => seat
.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
.set_grab(state, grab, serial, focus),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus),
}
}
});
@ -1547,7 +1572,7 @@ impl KeyboardTarget<State> for CosmicStack {
key: KeysymHandle<'_>,
state: KeyState,
serial: Serial,
time: u32,
time: InputTime,
) {
let active = self.keyboard_leave_if_previous(seat, data, serial);
self.0.with_program(|p| {
@ -1587,7 +1612,7 @@ impl KeyboardTarget<State> for CosmicStack {
}
impl PointerTarget<State> for CosmicStack {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
let mut event = event.clone();
self.0.with_program(|p| {
let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
@ -1615,7 +1640,7 @@ impl PointerTarget<State> for CosmicStack {
PointerTarget::enter(&self.0, seat, data, &event)
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
let mut event = event.clone();
self.0.with_program(|p| {
let active = p.active.load(Ordering::SeqCst);
@ -1658,7 +1683,7 @@ impl PointerTarget<State> for CosmicStack {
) {
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
match self.0.with_program(|p| p.current_focus()) {
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
Some(x) => {
@ -1689,14 +1714,12 @@ impl PointerTarget<State> for CosmicStack {
state.common.config.cosmic_conf.edge_snap_threshold,
false,
);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
}
if let Some((grab, focus)) = res
&& let GrabType::Pointer = grab.grab_type()
{
seat.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
}
});
}
@ -1704,7 +1727,7 @@ impl PointerTarget<State> for CosmicStack {
}
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
PointerTarget::axis(&self.0, seat, data, frame)
}
@ -1716,7 +1739,7 @@ impl PointerTarget<State> for CosmicStack {
}
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
self.0.with_program(|p| {
let mut cursor_state = seat
.user_data()
@ -1752,14 +1775,12 @@ impl PointerTarget<State> for CosmicStack {
&state.common.event_loop_handle,
false,
);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
}
if let Some((grab, focus)) = res
&& let GrabType::Pointer = grab.grab_type()
{
seat.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
}
});
}
@ -1832,7 +1853,7 @@ impl PointerTarget<State> for CosmicStack {
}
impl TouchTarget<State> for CosmicStack {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
let mut event = event.clone();
let active_window_geo = self.0.with_program(|p| {
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry()
@ -1843,7 +1864,7 @@ impl TouchTarget<State> for CosmicStack {
TouchTarget::down(&self.0, seat, data, &event)
}
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
TouchTarget::up(&self.0, seat, data, event)
}
@ -1891,6 +1912,185 @@ impl TouchTarget<State> for CosmicStack {
}
}
impl TabletToolTarget<State> for CosmicStack {
fn proximity_in(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
tablet: &Tablet,
serial: Serial,
) {
TabletToolTarget::proximity_in(&self.0, seat, data, tool_descriptor, tablet, serial)
}
fn proximity_out(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
) {
self.0.with_program(|p| {
let mut cursor_state = seat
.user_data()
.get::<CursorState>()
.unwrap()
.lock()
.unwrap();
cursor_state.unset_shape();
let _previous = p.swap_focus(None);
});
TabletToolTarget::proximity_out(&self.0, seat, data, tool_descriptor);
}
fn down(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolDownEvent,
) {
self.0.with_program(|p| {
p.tablet_serial
.store(event.serial.into(), Ordering::Release)
});
match self.0.with_program(|p| p.current_focus()) {
Some(Focus::Header) => {
TabletToolTarget::down(&self.0, seat, data, tool_descriptor, event)
}
Some(x) => {
let serial = self
.0
.with_program(|p| p.tablet_serial.load(Ordering::Acquire))
.into();
let seat = seat.clone();
let Some(surface) = self.0.with_program(|p| {
let window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
window.wl_surface().map(Cow::into_owned)
}) else {
return;
};
self.0.loop_handle().insert_idle(move |state| {
let res = state.common.shell.write().resize_request(
&surface,
&seat,
serial,
match x {
Focus::ResizeTop => ResizeEdge::TOP,
Focus::ResizeTopLeft => ResizeEdge::TOP_LEFT,
Focus::ResizeTopRight => ResizeEdge::TOP_RIGHT,
Focus::ResizeBottom => ResizeEdge::BOTTOM,
Focus::ResizeBottomLeft => ResizeEdge::BOTTOM_LEFT,
Focus::ResizeBottomRight => ResizeEdge::BOTTOM_RIGHT,
Focus::ResizeLeft => ResizeEdge::LEFT,
Focus::ResizeRight => ResizeEdge::RIGHT,
Focus::Header => unreachable!(),
},
state.common.config.cosmic_conf.edge_snap_threshold,
false,
);
if let Some((grab, focus)) = res
&& let GrabType::TabletTool = grab.grab_type()
{
seat.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus);
}
});
}
None => {}
}
}
fn up(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolUpEvent,
) {
TabletToolTarget::up(&self.0, seat, data, tool_descriptor, event)
}
fn motion(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolMotionEvent,
) {
let mut event = event.clone();
self.0.with_program(|p| {
let active = p.active.load(Ordering::SeqCst);
let active_window = &p.windows.lock().unwrap()[active];
let Some(next) = Focus::under(active_window, TAB_HEIGHT, event.location) else {
return;
};
let _previous = p.swap_focus(Some(next));
let mut cursor_state = seat
.user_data()
.get::<CursorState>()
.unwrap()
.lock()
.unwrap();
cursor_state.set_shape(next.cursor_shape());
seat.set_cursor_image_status(CursorImageStatus::default_named());
});
let active_window_geo = self.0.with_program(|p| {
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry()
});
event.location -= active_window_geo.loc.to_f64();
TabletToolTarget::motion(&self.0, seat, data, tool_descriptor, &event);
if event.location.y < 0.0
|| event.location.y > TAB_HEIGHT as f64
|| event.location.x < 64.0
|| event.location.x > (active_window_geo.size.w as f64 - 64.0)
{
self.start_drag(
data,
seat,
self.0
.with_program(|p| p.tablet_serial.load(Ordering::Acquire))
.into(),
);
}
}
fn axis(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
frame: ToolAxisFrame,
) {
TabletToolTarget::axis(&self.0, seat, data, tool_descriptor, frame)
}
fn button(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolButtonEvent,
) {
TabletToolTarget::button(&self.0, seat, data, tool_descriptor, event)
}
fn frame(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
time: InputTime,
) {
TabletToolTarget::frame(&self.0, seat, data, tool_descriptor, time)
}
}
pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> {
Header(IcedRenderElement<R>),
Shadow(PixelShaderElement),

View file

@ -8,6 +8,7 @@ use cosmic::{
layout::{Layout, Limits, Node},
mouse, overlay, renderer,
text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping},
touch,
widget::{Id, Widget, operation::Operation, tree::Tree},
},
widget::{self, container::draw_background, rule::FillMode, scrollable::AbsoluteOffset},
@ -370,6 +371,7 @@ where
if matches!(
event,
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| event::Event::Touch(touch::Event::FingerPressed { .. })
) && let Some(message) = self.press_message.clone()
{
shell.publish(message);
@ -388,6 +390,7 @@ where
if matches!(
event,
event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| event::Event::Touch(touch::Event::FingerLifted { .. })
) {
shell.publish(Message::activate(self.idx));
shell.capture_event();

View file

@ -31,7 +31,7 @@ use keyframe::{
functions::{EaseInOutCubic, EaseOutCubic},
};
use std::{
collections::{HashMap, HashSet, VecDeque},
collections::{HashMap, VecDeque},
time::{Duration, Instant},
};
@ -757,12 +757,10 @@ where
state.last_state = Some(current_state.clone());
}
let last_state = state.last_state.as_mut().unwrap();
let unknown_keys = current_state
.keys()
.collect::<HashSet<_>>()
.symmetric_difference(&last_state.keys().collect::<HashSet<_>>())
.next()
.is_some();
let unknown_keys = current_state.len() != last_state.len()
|| current_state
.keys()
.any(|key| !last_state.contains_key(key));
enum Difference {
NewOrRemoved,

View file

@ -21,6 +21,7 @@ use std::{
use smithay::{
backend::{
drm::DrmNode,
input::InputTime,
renderer::{
ImportAll, Renderer, buffer_has_alpha,
element::{Kind, RenderElementStates, surface::KindEvaluation},
@ -838,10 +839,7 @@ impl CosmicSurface {
self.0
.send_dmabuf_feedback(output, primary_scan_out_output, |_, data| {
if is_fullscreen {
feedback
.primary_scanout_feedback
.as_ref()
.unwrap_or(&feedback.render_feedback)
&feedback.primary_scanout_feedback
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
feedback
.overlay_scanout_feedback
@ -1087,7 +1085,7 @@ impl KeyboardTarget<State> for CosmicSurface {
key: KeysymHandle<'_>,
state: smithay::backend::input::KeyState,
serial: smithay::utils::Serial,
time: u32,
time: InputTime,
) {
match self.0.underlying_surface() {
WindowSurface::Wayland(toplevel) => {

View file

@ -7,7 +7,7 @@ use crate::{
shell::{
element::{CosmicMappedKey, CosmicMappedKeyInner},
focus::target::PointerFocusTarget,
grabs::{ReleaseMode, ResizeEdge},
grabs::{GrabType, ReleaseMode, ResizeEdge},
},
state::State,
utils::{
@ -21,7 +21,7 @@ use cosmic_comp_config::AppearanceConfig;
use smithay::{
backend::{
drm::DrmNode,
input::KeyState,
input::{InputTime, KeyState, TabletToolDescriptor},
renderer::{
ImportAll, ImportMem, Renderer,
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
@ -35,14 +35,23 @@ use smithay::{
Seat,
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
pointer::{
AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, GestureHoldBeginEvent,
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
PointerTarget, RelativeMotionEvent,
},
tablet::{
Tablet, TabletSeatTrait,
tool::{
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
UpEvent as ToolUpEvent,
},
},
touch::{
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
},
},
output::Output,
@ -429,12 +438,12 @@ impl CosmicWindow {
.map(|x| (x * scale as f32).round() as u8);
if has_ssd && !clip {
// bottom corners
radii[0] = 0;
radii[2] = 0;
radii[3] = 0;
if is_tiled {
// top corners
radii[0] = 0;
radii[1] = 0;
radii[3] = 0;
}
}
@ -501,12 +510,12 @@ impl CosmicWindow {
&& !is_maximized;
if has_ssd && !clip {
// bottom corners
radii[0] = 0;
radii[2] = 0;
radii[3] = 0;
if is_tiled {
// top corners
radii[0] = 0;
radii[1] = 0;
radii[3] = 0;
}
}
@ -551,8 +560,8 @@ impl CosmicWindow {
self.0.with_program(|p| {
let mut radii = radii;
if has_ssd {
radii[0] = 0;
radii[1] = 0;
radii[3] = 0;
}
let theme = p.theme.lock().unwrap();
let frosted = if theme.cosmic().frosted_windows {
@ -576,8 +585,8 @@ impl CosmicWindow {
});
if has_ssd {
radii[0] = 0;
radii[2] = 0;
radii[3] = 0;
let ssd_loc = location
+ self
.0
@ -682,26 +691,26 @@ impl CosmicWindow {
(has_ssd, true) => {
let mut corners = p.window.corner_radius(geometry_size).unwrap_or(radii);
corners[0] = radii[0].max(corners[0]);
corners[0] = if has_ssd {
radii[0]
} else {
radii[0].max(corners[0])
};
corners[1] = if has_ssd {
radii[1]
} else {
radii[1].max(corners[1])
};
corners[2] = radii[2].max(corners[2]);
corners[3] = if has_ssd {
radii[3]
} else {
radii[3].max(corners[3])
};
corners[3] = radii[3].max(corners[3]);
corners
}
(true, false) => p
.window
.corner_radius(geometry_size)
.map(|[a, _, c, _]| [a, radii[1], c, radii[3]])
.unwrap_or([default_radius, radii[1], default_radius, radii[3]]),
.map(|[_, _, c, d]| [radii[0], radii[1], c, d])
.unwrap_or([radii[0], radii[1], default_radius, default_radius]),
(false, false) => p
.window
.corner_radius(geometry_size)
@ -746,12 +755,19 @@ impl Program for CosmicWindowInternal {
false,
);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer()
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => seat
.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
.set_grab(state, grab, serial, focus),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus),
}
}
});
@ -981,7 +997,7 @@ impl KeyboardTarget<State> for CosmicWindow {
key: KeysymHandle<'_>,
state: KeyState,
serial: Serial,
time: u32,
time: InputTime,
) {
self.0
.with_program(|p| KeyboardTarget::key(&p.window, seat, data, key, state, serial, time))
@ -999,7 +1015,7 @@ impl KeyboardTarget<State> for CosmicWindow {
}
impl PointerTarget<State> for CosmicWindow {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
let mut event = event.clone();
self.0.with_program(|p| {
let has_ssd = p.has_ssd(false);
@ -1012,8 +1028,7 @@ impl PointerTarget<State> for CosmicWindow {
return;
};
let old_focus = p.swap_focus(Some(next));
assert_eq!(old_focus, None);
let _ = p.swap_focus(Some(next));
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
cursor_state.lock().unwrap().set_shape(next.cursor_shape());
@ -1025,7 +1040,7 @@ impl PointerTarget<State> for CosmicWindow {
PointerTarget::enter(&self.0, seat, data, &event)
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
let mut event = event.clone();
self.0.with_program(|p| {
let has_ssd = p.has_ssd(false);
@ -1057,7 +1072,7 @@ impl PointerTarget<State> for CosmicWindow {
) {
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
match self.0.with_program(|p| p.current_focus()) {
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
Some(x) => {
@ -1093,14 +1108,12 @@ impl PointerTarget<State> for CosmicWindow {
false,
);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus);
}
if let Some((grab, focus)) = res
&& let GrabType::Pointer = grab.grab_type()
{
seat.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus)
}
});
}
@ -1108,7 +1121,7 @@ impl PointerTarget<State> for CosmicWindow {
}
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
PointerTarget::axis(&self.0, seat, data, frame)
}
@ -1120,7 +1133,7 @@ impl PointerTarget<State> for CosmicWindow {
}
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
self.0.with_program(|p| {
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
cursor_state.lock().unwrap().unset_shape();
@ -1195,7 +1208,7 @@ impl PointerTarget<State> for CosmicWindow {
}
impl TouchTarget<State> for CosmicWindow {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
let mut event = event.clone();
self.0.with_program(|p| {
event.location -= p.window.geometry().loc.to_f64();
@ -1203,7 +1216,7 @@ impl TouchTarget<State> for CosmicWindow {
TouchTarget::down(&self.0, seat, data, &event)
}
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
TouchTarget::up(&self.0, seat, data, event)
}
@ -1234,6 +1247,155 @@ impl TouchTarget<State> for CosmicWindow {
}
}
impl TabletToolTarget<State> for CosmicWindow {
fn proximity_in(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
tablet: &Tablet,
serial: Serial,
) {
TabletToolTarget::proximity_in(&self.0, seat, data, tool_descriptor, tablet, serial)
}
fn proximity_out(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
) {
self.0.with_program(|p| {
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
cursor_state.lock().unwrap().unset_shape();
let _previous = p.swap_focus(None);
});
TabletToolTarget::proximity_out(&self.0, seat, data, tool_descriptor)
}
fn down(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolDownEvent,
) {
match self.0.with_program(|p| p.current_focus()) {
Some(Focus::Header) => {
TabletToolTarget::down(&self.0, seat, data, tool_descriptor, event)
}
Some(x) => {
let serial = event.serial;
let seat = seat.clone();
let Some(surface) = self.wl_surface().map(Cow::into_owned) else {
return;
};
self.0.loop_handle().insert_idle(move |state| {
let res = state.common.shell.write().resize_request(
&surface,
&seat,
serial,
match x {
Focus::ResizeTop => ResizeEdge::TOP,
Focus::ResizeTopLeft => ResizeEdge::TOP_LEFT,
Focus::ResizeTopRight => ResizeEdge::TOP_RIGHT,
Focus::ResizeBottom => ResizeEdge::BOTTOM,
Focus::ResizeBottomLeft => ResizeEdge::BOTTOM_LEFT,
Focus::ResizeBottomRight => ResizeEdge::BOTTOM_RIGHT,
Focus::ResizeLeft => ResizeEdge::LEFT,
Focus::ResizeRight => ResizeEdge::RIGHT,
Focus::Header => unreachable!(),
},
state.common.config.cosmic_conf.edge_snap_threshold,
false,
);
if let Some((grab, focus)) = res
&& let GrabType::TabletTool = grab.grab_type()
{
seat.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus)
}
});
}
None => {}
}
}
fn up(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolUpEvent,
) {
TabletToolTarget::up(&self.0, seat, data, tool_descriptor, event)
}
fn motion(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolMotionEvent,
) {
let mut event = event.clone();
self.0.with_program(|p| {
let has_ssd = p.has_ssd(false);
if has_ssd || p.has_tiled_state() {
let Some(next) = Focus::under(
&p.window,
if has_ssd { SSD_HEIGHT } else { 0 },
event.location,
) else {
return;
};
let _previous = p.swap_focus(Some(next));
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
cursor_state.lock().unwrap().set_shape(next.cursor_shape());
seat.set_cursor_image_status(CursorImageStatus::default_named());
}
});
event.location -= self.0.with_program(|p| p.window.geometry().loc.to_f64());
TabletToolTarget::motion(&self.0, seat, data, tool_descriptor, &event)
}
fn axis(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
frame: ToolAxisFrame,
) {
TabletToolTarget::axis(&self.0, seat, data, tool_descriptor, frame)
}
fn button(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolButtonEvent,
) {
TabletToolTarget::button(&self.0, seat, data, tool_descriptor, event)
}
fn frame(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
time: InputTime,
) {
TabletToolTarget::frame(&self.0, seat, data, tool_descriptor, time)
}
}
impl WaylandFocus for CosmicWindow {
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
self.0.with_program(|p| {

View file

@ -2,11 +2,18 @@ use crate::{
shell::{CosmicSurface, MinimizedWindow, Shell, Trigger, element::CosmicMapped},
state::{Common, State},
utils::prelude::*,
wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
wayland::{
handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
protocols::session_lock_layer::layer_show_on_lock,
},
};
use indexmap::IndexSet;
use smithay::{
desktop::{PopupUngrabStrategy, layer_map_for_output},
backend::input::InputTime,
desktop::{
LayerSurface, PopupUngrabStrategy, WindowSurfaceType, find_popup_root_surface,
layer_map_for_output,
},
input::{Seat, pointer::MotionEvent},
output::Output,
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
@ -212,6 +219,29 @@ impl Shell {
state.common.shell.write().update_active();
}
// We suppress Element(X) to Fullscreen(X) transition to avoid
// loss of focus by the X window when having a transition to fullscreen
// but in the case of X11 unmap/map the leave/enter needs to happen for the X11
// internal state to be focused on the window
pub fn set_focus_on_x11_map(
state: &mut State,
target: &KeyboardFocusTarget,
seat: &Seat<State>,
update_cursor: bool,
) {
let need_reset = seat
.get_keyboard()
.and_then(|keyboard| keyboard.current_focus())
.and_then(|current| current.x11_surface())
.is_some_and(|current| Some(current) == target.x11_surface());
if need_reset {
update_focus_state(seat, None, state, None, false);
}
Shell::set_focus(state, Some(target), seat, None, update_cursor);
}
pub fn append_focus_stack(&mut self, target: impl Into<FocusTarget>, seat: &Seat<State>) {
let target = target.into();
if target.is_minimized() {
@ -396,7 +426,7 @@ fn update_focus_state(
&MotionEvent {
location: new_pos.as_logical(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
time: InputTime::now(),
},
);
}
@ -603,19 +633,42 @@ fn focus_target_is_valid(
output: &Output,
target: KeyboardFocusTarget,
) -> bool {
// If a session lock is active, only lock surfaces can be focused
// If a session lock is active, only lock surfaces and lock layers can be focused
if shell.session_lock.is_some() {
if let KeyboardFocusTarget::LayerSurface(layer) = &target {
return layer_show_on_lock(layer.wl_surface());
} else if let KeyboardFocusTarget::Popup(popup) = &target
&& let Ok(root) = find_popup_root_surface(popup)
{
return layer_show_on_lock(&root);
}
return matches!(target, KeyboardFocusTarget::LockSurface(_));
}
// If an exclusive layer shell surface exists (on any output), only exclusive
// shell surfaces can have focus, on the highest layer with exclusive surfaces.
// Popups are judged by their root surface, so an exclusive surface can
// still open grabbing popups (menus, dropdowns, context menus).
if let Some(layer) = exclusive_layer_surface_layer(shell) {
return if let KeyboardFocusTarget::LayerSurface(layer_surface) = target {
let is_exclusive_on_layer = |layer_surface: &LayerSurface| {
let data = layer_surface.cached_state();
(data.keyboard_interactivity, data.layer) == (KeyboardInteractivity::Exclusive, layer)
} else {
false
};
return match target {
KeyboardFocusTarget::LayerSurface(layer_surface) => {
is_exclusive_on_layer(&layer_surface)
}
KeyboardFocusTarget::Popup(popup) => find_popup_root_surface(&popup)
.ok()
.and_then(|root| {
shell.outputs().find_map(|o| {
layer_map_for_output(o)
.layer_for_surface(&root, WindowSurfaceType::ALL)
.map(&is_exclusive_on_layer)
})
})
.unwrap_or(false),
_ => false,
};
}
@ -745,7 +798,7 @@ fn update_pointer_focus(state: &mut State, seat: &Seat<State>) {
&MotionEvent {
location: pointer.current_location(),
serial: SERIAL_COUNTER.next_serial(),
time: state.common.clock.now().as_millis(),
time: InputTime::now(),
},
);
}

View file

@ -22,7 +22,7 @@ use crate::{
prelude::OutputExt,
quirks::{WORKSPACE_OVERVIEW_NAMESPACE, workspace_overview_is_open},
},
wayland::protocols::workspace::WorkspaceHandle,
wayland::protocols::{session_lock_layer::layer_show_on_lock, workspace::WorkspaceHandle},
};
pub enum Stage<'a> {
@ -77,6 +77,8 @@ fn render_input_order_internal<R: 'static>(
element_filter: ElementFilter,
mut callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
) -> ControlFlow<Result<R, OutputNoMode>, ()> {
// NOTE: Keep in sync with other surface iteration functions
if shell
.zoom_state
.as_ref()
@ -87,7 +89,92 @@ fn render_input_order_internal<R: 'static>(
// Session Lock
if let Some(session_lock) = &shell.session_lock {
return callback(Stage::SessionLock(session_lock.surfaces.get(output)));
for (layer, popup, location) in layer_popups(output, Layer::Overlay, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
workspace_idx: current.1,
})?;
}
for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
}
for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
workspace_idx: current.1,
})?;
}
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
}
callback(Stage::SessionLock(session_lock.surfaces.get(output)))?;
for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
workspace_idx: current.1,
})?;
}
for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
workspace_idx: current.1,
})?;
}
for (layer, location) in layer_surfaces(output, Layer::Bottom, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
}
for (layer, location) in layer_surfaces(output, Layer::Background, element_filter) {
if !layer_show_on_lock(layer.wl_surface()) {
continue;
}
callback(Stage::LayerSurface {
layer,
location,
workspace_idx: current.1,
})?;
}
return ControlFlow::Continue(());
}
// Overlay-level layer shell

View file

@ -16,21 +16,30 @@ use crate::{
};
use id_tree::NodeId;
use smithay::{
backend::input::KeyState,
backend::input::{InputTime, KeyState, TabletToolDescriptor},
desktop::{LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, space::SpaceElement},
input::{
Seat,
dnd::{DndFocus, OfferData, Source},
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
PointerTarget, RelativeMotionEvent,
},
tablet::{
Tablet,
tool::{
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolHandle,
TabletToolTarget, UpEvent as ToolUpEvent,
},
},
touch::{
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
},
},
reexports::wayland_server::{
@ -164,6 +173,17 @@ impl PointerFocusTarget {
}
}
fn inner_tablet_tool_target(&self) -> &dyn TabletToolTarget<State> {
match self {
PointerFocusTarget::WlSurface { surface, .. } => surface,
PointerFocusTarget::X11Surface { surface, .. } => surface,
PointerFocusTarget::StackUI(u) => u,
PointerFocusTarget::WindowUI(u) => u,
PointerFocusTarget::ResizeFork(f) => f,
PointerFocusTarget::ZoomUI(e) => e,
}
}
pub fn under_surface<P: Into<Point<f64, Logical>>>(
surface: &CosmicSurface,
point: P,
@ -272,9 +292,10 @@ impl PointerFocusTarget {
None
};
let cursor_hotspot = if let Some(CursorGeometry { hotspot, .. }) =
seat.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into())
{
let cursor_hotspot = if let Some(CursorGeometry { hotspot, .. }) = seat.cursor_geometry(
(0.0, 0.0),
Duration::from_millis(event.time.millis() as u64).into(),
) {
hotspot
} else {
Point::from((0, 0))
@ -285,6 +306,16 @@ impl PointerFocusTarget {
session.set_cursor_hotspot(cursor_hotspot);
}
}
pub fn supports_tool(&self, tool_handle: &TabletToolHandle<State>) -> bool {
match self {
Self::WlSurface { surface, .. } if surface.client().is_some() => tool_handle
.client_tools(&surface.client().unwrap())
.next()
.is_some(),
_ => true,
}
}
}
impl KeyboardFocusTarget {
@ -326,7 +357,7 @@ impl KeyboardFocusTarget {
}
}
fn x11_surface(&self) -> Option<X11Surface> {
pub fn x11_surface(&self) -> Option<X11Surface> {
match self {
KeyboardFocusTarget::Element(mapped) => mapped.active_window().x11_surface().cloned(),
KeyboardFocusTarget::Fullscreen(surface) => surface.x11_surface().cloned(),
@ -408,16 +439,16 @@ impl PointerTarget<State> for PointerFocusTarget {
self.inner_pointer_target()
.relative_motion(seat, data, event);
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
self.inner_pointer_target().button(seat, data, event);
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
self.inner_pointer_target().axis(seat, data, frame);
}
fn frame(&self, seat: &Seat<State>, data: &mut State) {
self.inner_pointer_target().frame(seat, data);
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
let toplevel = self.toplevel(&data.common.shell.read());
if let Some(element) = toplevel {
for session in element.cursor_sessions() {
@ -497,12 +528,102 @@ impl PointerTarget<State> for PointerFocusTarget {
}
}
impl TabletToolTarget<State> for PointerFocusTarget {
fn proximity_in(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
tablet: &Tablet,
serial: Serial,
) {
self.inner_tablet_tool_target()
.proximity_in(seat, data, tool_descriptor, tablet, serial);
}
fn proximity_out(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
) {
self.inner_tablet_tool_target()
.proximity_out(seat, data, tool_descriptor);
}
fn down(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolDownEvent,
) {
self.inner_tablet_tool_target()
.down(seat, data, tool_descriptor, event);
}
fn up(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolUpEvent,
) {
self.inner_tablet_tool_target()
.up(seat, data, tool_descriptor, event);
}
fn motion(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolMotionEvent,
) {
self.inner_tablet_tool_target()
.motion(seat, data, tool_descriptor, event);
}
fn axis(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
frame: ToolAxisFrame,
) {
self.inner_tablet_tool_target()
.axis(seat, data, tool_descriptor, frame);
}
fn button(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &ToolButtonEvent,
) {
self.inner_tablet_tool_target()
.button(seat, data, tool_descriptor, event);
}
fn frame(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
time: InputTime,
) {
self.inner_tablet_tool_target()
.frame(seat, data, tool_descriptor, time);
}
}
impl TouchTarget<State> for PointerFocusTarget {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
self.inner_touch_target().down(seat, data, event);
}
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
self.inner_touch_target().up(seat, data, event);
}
@ -593,7 +714,7 @@ impl DndFocus<State> for PointerFocusTarget {
offer: Option<&mut CosmicOfferData<S>>,
seat: &Seat<State>,
location: Point<f64, Logical>,
time: u32,
time: InputTime,
) {
match self {
PointerFocusTarget::WlSurface { surface, .. } => {
@ -695,7 +816,7 @@ impl KeyboardTarget<State> for KeyboardFocusTarget {
key: KeysymHandle<'_>,
state: KeyState,
serial: Serial,
time: u32,
time: InputTime,
) {
if let Some(inner) = self.inner_keyboard_target() {
inner.key(seat, data, key, state, serial, time);

View file

@ -1,22 +1,34 @@
use smithay::{
backend::input::{InputTime, TabletToolDescriptor},
input::{
Seat, SeatHandler,
pointer::{
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
GrabStartData as PointerGrabStartData, MotionEvent as PointerMotionEvent, PointerGrab,
PointerInnerHandle, RelativeMotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, Focus,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
RelativeMotionEvent,
},
tablet::{
TabletSeatHandler, TabletSeatTrait,
tool::{
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
},
},
touch::{
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
TouchInnerHandle, UpEvent as TouchUpEvent,
},
},
utils::{Logical, Point, SERIAL_COUNTER, Serial},
};
use crate::state::State;
use crate::{shell::grabs::GrabType, state::State};
use super::GrabStartData;
@ -44,10 +56,15 @@ impl<G> DelayGrab<G> {
}
}
pub fn is_touch_grab(&self) -> bool {
match self.start_data {
GrabStartData::Touch(_) => true,
GrabStartData::Pointer(_) => false,
pub fn grab_type(&self) -> GrabType {
self.start_data.type_()
}
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
Some(tool)
} else {
None
}
}
}
@ -92,7 +109,7 @@ impl<G: PointerGrab<State>> PointerGrab<State> for DelayGrab<G> {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
handle.button(data, event);
if handle.current_pressed().is_empty() {
@ -104,7 +121,7 @@ impl<G: PointerGrab<State>> PointerGrab<State> for DelayGrab<G> {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
details: PointerAxisFrame,
) {
handle.axis(data, details);
}
@ -201,12 +218,17 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
event: &DownEvent,
event: &TouchDownEvent,
) {
handle.down(data, focus, event);
}
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &TouchUpEvent,
) {
handle.up(data, event);
if event.slot == TouchGrab::start_data(self).slot {
@ -273,3 +295,110 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
fn unset(&mut self, _data: &mut State) {}
}
impl<G: TabletToolGrab<State>> TabletToolGrab<State> for DelayGrab<G> {
fn start_data(&self) -> &TabletGrabStartData<State> {
match &self.start_data {
GrabStartData::TabletTool { data, .. } => data,
_ => unreachable!(),
}
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
handle.proximity_out(data, event);
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &TabletMotionEvent,
) {
handle.motion(data, focus, event);
let distance = self.start_data.distance(event.location);
if distance >= 1.
&& let Some(factory) = self.grab_factory.take()
{
let seat = self.seat.clone();
let serial = self.serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial());
let time = InputTime::now();
let Some(tool) = self.tool().cloned() else {
handle.unset_grab(self, data, serial, time, true);
return;
};
data.common.event_loop_handle.insert_idle(move |data| {
if let Some((grab, focus)) = factory(data) {
seat.tablet_seat()
.get_tool(&tool)
.unwrap()
.set_grab(data, grab, time, serial, focus);
}
});
}
}
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletDownEvent,
) {
handle.down(data, event)
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletUpEvent,
) {
handle.up(data, event);
handle.unset_grab(self, data, event.serial, event.time, false);
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletButtonEvent,
) {
handle.button(data, event);
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
frame: TabletAxisFrame,
) {
handle.axis(data, frame)
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
time: InputTime,
) {
handle.frame(data, time)
}
fn unset(&mut self, _data: &mut State) {}
fn proximity_in(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &ProximityInEvent,
) {
handle.proximity_in(data, focus, event);
}
}

View file

@ -1,7 +1,10 @@
use cosmic_settings_config::shortcuts::Action;
use smithay::{
input::pointer::MotionEvent, reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::SERIAL_COUNTER, wayland::seat::WaylandFocus,
backend::input::InputTime,
input::{pointer::MotionEvent, tablet::TabletSeatTrait},
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::SERIAL_COUNTER,
wayland::seat::WaylandFocus,
};
use crate::{
@ -10,7 +13,7 @@ use crate::{
shell::{
CosmicSurface, PointGlobalExt, Shell,
element::{CosmicMapped, CosmicWindow},
grabs::ReleaseMode,
grabs::{GrabType, ReleaseMode},
},
state::State,
utils::{prelude::SeatExt, screenshot::screenshot_window},
@ -352,19 +355,20 @@ pub fn window_items(
std::mem::drop(shell);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(
state,
grab,
SERIAL_COUNTER.next_serial(),
)
} else {
seat.get_pointer().unwrap().set_grab(
state,
grab,
SERIAL_COUNTER.next_serial(),
focus,
);
let serial = SERIAL_COUNTER.next_serial();
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => seat
.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus),
}
}
}
@ -388,21 +392,29 @@ pub fn window_items(
std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res {
let serial = SERIAL_COUNTER.next_serial();
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: state.common.clock.now().as_millis(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus),
GrabType::Pointer => {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: InputTime::now(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
}
}
}
});
@ -423,21 +435,30 @@ pub fn window_items(
std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res {
let serial = SERIAL_COUNTER.next_serial();
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: state.common.clock.now().as_millis(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: InputTime::now(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
}
GrabType::TabletTool => {
seat.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus);
}
}
}
});
@ -458,21 +479,30 @@ pub fn window_items(
std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res {
let serial = SERIAL_COUNTER.next_serial();
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: state.common.clock.now().as_millis(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: InputTime::now(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
}
GrabType::TabletTool => {
seat.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus);
}
}
}
});
@ -493,21 +523,29 @@ pub fn window_items(
std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res {
let serial = SERIAL_COUNTER.next_serial();
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: state.common.clock.now().as_millis(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => {
let pointer = seat.get_pointer().unwrap();
pointer.motion(
state,
target,
&MotionEvent {
location: loc.as_logical().to_f64(),
serial,
time: InputTime::now(),
},
);
pointer.frame(state);
pointer.set_grab(state, grab, serial, focus);
}
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus),
}
}
});
@ -627,19 +665,20 @@ pub fn fullscreen_items(window: &CosmicSurface, config: &Config) -> impl Iterato
std::mem::drop(shell);
if let Some((grab, focus)) = res {
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(
state,
grab,
SERIAL_COUNTER.next_serial(),
)
} else {
seat.get_pointer().unwrap().set_grab(
state,
grab,
SERIAL_COUNTER.next_serial(),
focus,
);
let serial = SERIAL_COUNTER.next_serial();
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => seat
.get_pointer()
.unwrap()
.set_grab(state, grab, serial, focus),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(state, grab, InputTime::now(), serial, focus),
}
}
}

View file

@ -19,22 +19,33 @@ use cosmic::{
};
use smithay::{
backend::{
input::{ButtonState, TouchSlot},
input::{ButtonState, InputTime, TabletToolDescriptor, TouchSlot},
renderer::ImportMem,
},
desktop::space::SpaceElement,
input::{
Seat,
pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
GrabStartData as PointerGrabStartData, MotionEvent as PointerMotionEvent, PointerGrab,
PointerInnerHandle, PointerTarget, RelativeMotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle, PointerTarget,
RelativeMotionEvent,
},
tablet::{
TabletSeatHandler,
tool::{
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
TabletToolGrab, TabletToolInnerHandle, TabletToolTarget, UpEvent as TabletUpEvent,
},
},
touch::{
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
TouchGrab, TouchInnerHandle, TouchTarget, UpEvent,
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
MotionEvent as TouchMotionEvent, TouchGrab, TouchInnerHandle, TouchTarget,
UpEvent as TouchUpEvent,
},
},
output::Output,
@ -43,7 +54,7 @@ use smithay::{
use crate::{
backend::render::element::AsGlowRenderer,
shell::{SeatExt, focus::target::PointerFocusTarget},
shell::{SeatExt, focus::target::PointerFocusTarget, grabs::GrabType},
state::State,
utils::{
iced::{IcedElement, IcedRenderElement, Program},
@ -350,6 +361,7 @@ impl Program for ContextMenu {
position,
pointer_entered: false,
touch_entered: None,
tablet_entered: None,
})
}
});
@ -509,6 +521,7 @@ pub struct Element {
position: Point<i32, Global>,
pointer_entered: bool,
touch_entered: Option<TouchSlot>,
tablet_entered: Option<TabletToolDescriptor>,
}
pub struct MenuGrab {
@ -517,6 +530,7 @@ pub struct MenuGrab {
seat: Seat<State>,
screen_space_relative: Option<Output>,
scale: Arc<Mutex<f64>>,
last_tablet_idx: Option<usize>,
}
impl PointerGrab<State> for MenuGrab {
@ -599,7 +613,7 @@ impl PointerGrab<State> for MenuGrab {
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
let any_entered = self
.elements
@ -633,7 +647,7 @@ impl PointerGrab<State> for MenuGrab {
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
details: PointerAxisFrame,
) {
handle.axis(state, details);
}
@ -730,7 +744,7 @@ impl TouchGrab<State> for MenuGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
event: &TouchDownEvent,
) {
{
let mut guard = self.elements.lock().unwrap();
@ -758,7 +772,7 @@ impl TouchGrab<State> for MenuGrab {
}) {
let element = &mut elements[i];
let new_event = DownEvent {
let new_event = TouchDownEvent {
slot: event.slot,
location: event_location - element.position.as_logical().to_f64(),
serial: event.serial,
@ -773,7 +787,12 @@ impl TouchGrab<State> for MenuGrab {
handle.down(data, None, event);
}
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &TouchUpEvent,
) {
{
let elements = self.elements.lock().unwrap();
for element in elements.iter().filter(|elem| {
@ -849,6 +868,248 @@ impl TouchGrab<State> for MenuGrab {
fn unset(&mut self, _data: &mut State) {}
}
impl TabletToolGrab<State> for MenuGrab {
fn start_data(&self) -> &TabletGrabStartData<State> {
match &self.start_data {
GrabStartData::TabletTool { data, .. } => data,
_ => unreachable!(),
}
}
fn proximity_in(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &ProximityInEvent,
) {
{
let location = if let Some(output) = self.screen_space_relative.as_ref() {
if data.common.shell.read().zoom_state().is_some() {
event
.location
.as_global()
.to_zoomed(output)
.to_global(output)
.as_logical()
} else {
event.location
}
} else {
event.location
};
let mut elements = self.elements.lock().unwrap();
if let Some(i) = elements.iter().position(|elem| {
let mut bbox = elem.iced.bbox();
bbox.loc = elem.position.as_logical();
bbox.contains(location.to_i32_floor())
}) {
let element = &mut elements[i];
let new_event = TabletMotionEvent {
location: location - element.position.as_logical().to_f64(),
serial: event.serial,
time: event.time,
};
TabletToolTarget::proximity_in(
&element.iced,
&self.seat,
data,
handle.descriptor(),
&handle.current_tablet(),
event.serial,
);
element.tablet_entered = Some(handle.descriptor().clone());
TabletToolTarget::motion(
&element.iced,
&self.seat,
data,
handle.descriptor(),
&new_event,
);
self.last_tablet_idx = Some(i);
}
}
handle.proximity_in(data, None, event);
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
{
let mut elements = self.elements.lock().unwrap();
for element in elements.iter_mut().filter(|elem| {
elem.tablet_entered
.as_ref()
.is_some_and(|tool| tool == handle.descriptor())
}) {
TabletToolTarget::proximity_out(
&element.iced,
&self.seat,
data,
handle.descriptor(),
);
element.tablet_entered.take();
}
self.last_tablet_idx.take();
}
handle.unset_grab(self, data, event.serial, event.time, true);
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &TabletMotionEvent,
) {
{
let location = if let Some(output) = self.screen_space_relative.as_ref() {
if data.common.shell.read().zoom_state().is_some() {
event
.location
.as_global()
.to_zoomed(output)
.to_global(output)
.as_logical()
} else {
event.location
}
} else {
event.location
};
let mut elements = self.elements.lock().unwrap();
if let Some(i) = elements.iter().position(|elem| {
let mut bbox = elem.iced.bbox();
bbox.loc = elem.position.as_logical();
bbox.contains(location.to_i32_floor())
}) {
let element = &mut elements[i];
let new_event = TabletMotionEvent {
location: location - element.position.as_logical().to_f64(),
serial: event.serial,
time: event.time,
};
if element.tablet_entered.is_none() {
TabletToolTarget::proximity_in(
&element.iced,
&self.seat,
data,
handle.descriptor(),
&handle.current_tablet(),
event.serial,
);
element.tablet_entered = Some(handle.descriptor().clone());
}
TabletToolTarget::motion(
&element.iced,
&self.seat,
data,
handle.descriptor(),
&new_event,
);
self.last_tablet_idx = Some(i);
} else {
elements
.iter_mut()
.filter(|element| element.tablet_entered.is_some())
.skip(1)
.for_each(|element| {
TabletToolTarget::proximity_out(
&element.iced,
&self.seat,
data,
handle.descriptor(),
);
});
self.last_tablet_idx.take();
}
}
handle.motion(data, None, event);
}
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletDownEvent,
) {
let mut guard = self.elements.lock().unwrap();
let elements = &mut *guard;
if let Some(elem) = self.last_tablet_idx.and_then(|i| elements.get_mut(i))
&& elem
.tablet_entered
.as_ref()
.is_some_and(|desc| desc == handle.descriptor())
{
TabletToolTarget::down(&elem.iced, &self.seat, data, handle.descriptor(), event);
} else {
handle.down(data, event);
}
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletUpEvent,
) {
let mut guard = self.elements.lock().unwrap();
let elements = &mut *guard;
if let Some(elem) = self.last_tablet_idx.and_then(|i| elements.get_mut(i))
&& elem
.tablet_entered
.as_ref()
.is_some_and(|desc| desc == handle.descriptor())
{
TabletToolTarget::up(&elem.iced, &self.seat, data, handle.descriptor(), event);
} else {
handle.up(data, event);
}
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletButtonEvent,
) {
handle.button(data, event);
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
frame: TabletAxisFrame,
) {
handle.axis(data, frame);
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
time: InputTime,
) {
handle.frame(data, time);
}
fn unset(&mut self, _data: &mut State) {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MenuAlignment {
pub x: AxisAlignment,
@ -1052,6 +1313,7 @@ impl MenuGrab {
position,
pointer_entered: false,
touch_entered: None,
tablet_entered: None,
}]));
let scale = Arc::new(Mutex::new(screen_space_relative.unwrap_or(1.)));
@ -1076,6 +1338,7 @@ impl MenuGrab {
seat: seat.clone(),
screen_space_relative,
scale,
last_tablet_idx: None,
}
}
@ -1086,10 +1349,15 @@ impl MenuGrab {
}
}
pub fn is_touch_grab(&self) -> bool {
match self.start_data {
GrabStartData::Touch(_) => true,
GrabStartData::Pointer(_) => false,
pub fn grab_type(&self) -> GrabType {
self.start_data.type_()
}
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
Some(tool)
} else {
None
}
}
}

View file

@ -1,18 +1,26 @@
use calloop::LoopHandle;
use cosmic_settings_config::shortcuts;
use smithay::{
backend::input::{InputTime, TabletToolDescriptor},
input::{
Seat,
pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
RelativeMotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
},
tablet::tool::{
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
MotionEvent as TabletMotionEvent, ProximityOutEvent, TabletToolGrab,
TabletToolInnerHandle, UpEvent as TabletUpEvent,
},
touch::{
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
TouchInnerHandle, UpEvent as TouchUpEvent,
},
},
output::Output,
@ -39,6 +47,17 @@ use super::{
pub enum GrabStartData {
Touch(TouchGrabStartData<State>),
Pointer(PointerGrabStartData<State>),
TabletTool {
tool: TabletToolDescriptor,
data: TabletGrabStartData<State>,
},
}
#[derive(Debug, Clone)]
pub enum GrabType {
Touch,
Pointer,
TabletTool,
}
impl GrabStartData {
@ -46,6 +65,7 @@ impl GrabStartData {
match self {
Self::Touch(touch) => touch.focus.as_ref(),
Self::Pointer(pointer) => pointer.focus.as_ref(),
Self::TabletTool { data, .. } => data.focus.as_ref(),
}
}
@ -53,6 +73,7 @@ impl GrabStartData {
match self {
Self::Touch(touch) => touch.focus = focus,
Self::Pointer(pointer) => pointer.focus = focus,
Self::TabletTool { data, .. } => data.focus = focus,
}
}
@ -60,6 +81,7 @@ impl GrabStartData {
match self {
Self::Touch(touch) => touch.location,
Self::Pointer(pointer) => pointer.location,
Self::TabletTool { data, .. } => data.location,
}
}
@ -67,6 +89,7 @@ impl GrabStartData {
match self {
Self::Touch(touch) => touch.location = location,
Self::Pointer(pointer) => pointer.location = location,
Self::TabletTool { data, .. } => data.location = location,
}
}
@ -76,6 +99,14 @@ impl GrabStartData {
((new.x - old.x).powi(2) + (new.y - old.y).powi(2)).sqrt()
}
pub fn type_(&self) -> GrabType {
match self {
Self::Pointer(_) => GrabType::Pointer,
Self::Touch(_) => GrabType::Touch,
Self::TabletTool { .. } => GrabType::TabletTool,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -203,10 +234,17 @@ impl From<ResizeForkGrab> for ResizeGrab {
}
impl ResizeGrab {
pub fn is_touch_grab(&self) -> bool {
pub fn grab_type(&self) -> GrabType {
match self {
ResizeGrab::Floating(grab) => grab.is_touch_grab(),
ResizeGrab::Tiling(grab) => grab.is_touch_grab(),
ResizeGrab::Floating(grab) => grab.grab_type(),
ResizeGrab::Tiling(grab) => grab.grab_type(),
}
}
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
match self {
ResizeGrab::Floating(grab) => grab.tool(),
ResizeGrab::Tiling(grab) => grab.tool(),
}
}
}
@ -242,11 +280,11 @@ impl PointerGrab<State> for ResizeGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
match self {
ResizeGrab::Floating(grab) => grab.button(data, handle, event),
ResizeGrab::Tiling(grab) => grab.button(data, handle, event),
ResizeGrab::Floating(grab) => PointerGrab::button(grab, data, handle, event),
ResizeGrab::Tiling(grab) => PointerGrab::button(grab, data, handle, event),
}
}
@ -254,11 +292,11 @@ impl PointerGrab<State> for ResizeGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
details: PointerAxisFrame,
) {
match self {
ResizeGrab::Floating(grab) => grab.axis(data, handle, details),
ResizeGrab::Tiling(grab) => grab.axis(data, handle, details),
ResizeGrab::Floating(grab) => PointerGrab::axis(grab, data, handle, details),
ResizeGrab::Tiling(grab) => PointerGrab::axis(grab, data, handle, details),
}
}
@ -386,7 +424,7 @@ impl TouchGrab<State> for ResizeGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
event: &TouchDownEvent,
) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event),
@ -394,7 +432,12 @@ impl TouchGrab<State> for ResizeGrab {
}
}
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &TouchUpEvent,
) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event),
@ -467,6 +510,107 @@ impl TouchGrab<State> for ResizeGrab {
}
}
impl TabletToolGrab<State> for ResizeGrab {
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletDownEvent,
) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::down(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TabletToolGrab::down(grab, data, handle, event),
}
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletUpEvent,
) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::up(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TabletToolGrab::up(grab, data, handle, event),
}
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &TabletMotionEvent,
) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
ResizeGrab::Tiling(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
}
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
serial: InputTime,
) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::frame(grab, data, handle, serial),
ResizeGrab::Tiling(grab) => TabletToolGrab::frame(grab, data, handle, serial),
}
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletButtonEvent,
) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::button(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TabletToolGrab::button(grab, data, handle, event),
}
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
details: TabletAxisFrame,
) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::axis(grab, data, handle, details),
ResizeGrab::Tiling(grab) => TabletToolGrab::axis(grab, data, handle, details),
}
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
}
}
fn start_data(&self) -> &TabletGrabStartData<State> {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::start_data(grab),
ResizeGrab::Tiling(grab) => TabletToolGrab::start_data(grab),
}
}
fn unset(&mut self, data: &mut State) {
match self {
ResizeGrab::Floating(grab) => TabletToolGrab::unset(grab, data),
ResizeGrab::Tiling(grab) => TabletToolGrab::unset(grab, data),
}
}
}
pub enum MoveGrab {
Move(moving::MoveGrab),
Delayed(delay::DelayGrab<MoveGrab>),
@ -536,10 +680,17 @@ impl MoveGrab {
}
}
pub fn is_touch_grab(&self) -> bool {
pub fn grab_type(&self) -> GrabType {
match self {
MoveGrab::Move(m) => m.is_touch_grab(),
MoveGrab::Delayed(d) => d.is_touch_grab(),
MoveGrab::Move(m) => m.grab_type(),
MoveGrab::Delayed(d) => d.grab_type(),
}
}
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
match self {
MoveGrab::Move(m) => m.tool(),
MoveGrab::Delayed(d) => d.tool(),
}
}
}
@ -574,11 +725,11 @@ impl PointerGrab<State> for MoveGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
match self {
MoveGrab::Move(grab) => grab.button(data, handle, event),
MoveGrab::Delayed(grab) => grab.button(data, handle, event),
MoveGrab::Move(grab) => PointerGrab::button(grab, data, handle, event),
MoveGrab::Delayed(grab) => PointerGrab::button(grab, data, handle, event),
}
}
@ -586,11 +737,11 @@ impl PointerGrab<State> for MoveGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
details: PointerAxisFrame,
) {
match self {
MoveGrab::Move(grab) => grab.axis(data, handle, details),
MoveGrab::Delayed(grab) => grab.axis(data, handle, details),
MoveGrab::Move(grab) => PointerGrab::axis(grab, data, handle, details),
MoveGrab::Delayed(grab) => PointerGrab::axis(grab, data, handle, details),
}
}
@ -718,7 +869,7 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
event: &TouchDownEvent,
) {
match self {
MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event),
@ -726,7 +877,12 @@ impl TouchGrab<State> for MoveGrab {
}
}
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &TouchUpEvent,
) {
match self {
MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event),
MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event),
@ -798,3 +954,104 @@ impl TouchGrab<State> for MoveGrab {
}
}
}
impl TabletToolGrab<State> for MoveGrab {
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletDownEvent,
) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::down(grab, data, handle, event),
MoveGrab::Delayed(grab) => TabletToolGrab::down(grab, data, handle, event),
}
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletUpEvent,
) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::up(grab, data, handle, event),
MoveGrab::Delayed(grab) => TabletToolGrab::up(grab, data, handle, event),
}
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &TabletMotionEvent,
) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
MoveGrab::Delayed(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
}
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
serial: InputTime,
) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::frame(grab, data, handle, serial),
MoveGrab::Delayed(grab) => TabletToolGrab::frame(grab, data, handle, serial),
}
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletButtonEvent,
) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::button(grab, data, handle, event),
MoveGrab::Delayed(grab) => TabletToolGrab::button(grab, data, handle, event),
}
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
details: TabletAxisFrame,
) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::axis(grab, data, handle, details),
MoveGrab::Delayed(grab) => TabletToolGrab::axis(grab, data, handle, details),
}
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
MoveGrab::Delayed(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
}
}
fn start_data(&self) -> &TabletGrabStartData<State> {
match self {
MoveGrab::Move(grab) => TabletToolGrab::start_data(grab),
MoveGrab::Delayed(grab) => TabletToolGrab::start_data(grab),
}
}
fn unset(&mut self, data: &mut State) {
match self {
MoveGrab::Move(grab) => TabletToolGrab::unset(grab, data),
MoveGrab::Delayed(grab) => TabletToolGrab::unset(grab, data),
}
}
}

View file

@ -8,6 +8,7 @@ use crate::{
CosmicMapped, CosmicSurface, Direction, ManagedLayer,
element::{CosmicMappedRenderElement, stack_hover::StackHover},
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
grabs::GrabType,
layout::floating::TiledCorners,
},
utils::prelude::*,
@ -20,7 +21,7 @@ use smallvec::SmallVec;
use smithay::{
backend::{
drm::DrmNode,
input::ButtonState,
input::{ButtonState, InputTime, TabletToolDescriptor},
renderer::{
ImportAll, ImportMem,
element::{RenderElement, utils::RescaleRenderElement},
@ -30,13 +31,27 @@ use smithay::{
input::{
Seat,
pointer::{
AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
RelativeMotionEvent,
},
touch::{self, GrabStartData as TouchGrabStartData, TouchGrab, TouchInnerHandle},
tablet::{
TabletSeatHandler,
tool::{
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
},
},
touch::{
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
TouchInnerHandle, UpEvent as TouchUpEvent,
},
},
output::Output,
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale},
@ -503,7 +518,7 @@ impl PointerGrab<State> for MoveGrab {
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &MotionEvent,
event: &PointerMotionEvent,
) {
self.update_location(state, event.location);
@ -529,7 +544,7 @@ impl PointerGrab<State> for MoveGrab {
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
handle.button(state, event);
match self.release {
@ -550,7 +565,7 @@ impl PointerGrab<State> for MoveGrab {
&mut self,
state: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
details: PointerAxisFrame,
) {
handle.axis(state, details);
}
@ -647,7 +662,7 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &touch::DownEvent,
event: &TouchDownEvent,
) {
handle.down(data, None, event)
}
@ -656,7 +671,7 @@ impl TouchGrab<State> for MoveGrab {
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &touch::UpEvent,
event: &TouchUpEvent,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
handle.unset_grab(self, data);
@ -670,7 +685,7 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &touch::MotionEvent,
event: &TouchMotionEvent,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
self.update_location(data, event.location);
@ -691,7 +706,7 @@ impl TouchGrab<State> for MoveGrab {
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &touch::ShapeEvent,
event: &ShapeEvent,
) {
handle.shape(data, event)
}
@ -700,7 +715,7 @@ impl TouchGrab<State> for MoveGrab {
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &touch::OrientationEvent,
event: &OrientationEvent,
) {
handle.orientation(data, event)
}
@ -715,6 +730,98 @@ impl TouchGrab<State> for MoveGrab {
fn unset(&mut self, _data: &mut State) {}
}
impl TabletToolGrab<State> for MoveGrab {
fn start_data(&self) -> &TabletGrabStartData<State> {
match &self.start_data {
GrabStartData::TabletTool { data, .. } => data,
_ => unreachable!(),
}
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
handle.proximity_out(data, event);
handle.unset_grab(self, data, event.serial, event.time, false);
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &TabletMotionEvent,
) {
handle.motion(data, None, event);
self.update_location(data, event.location);
if !self.window.alive() {
handle.unset_grab(self, data, event.serial, event.time, true);
}
}
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletDownEvent,
) {
handle.down(data, event)
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletUpEvent,
) {
handle.up(data, event);
handle.unset_grab(self, data, event.serial, event.time, false);
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletButtonEvent,
) {
handle.button(data, event)
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
frame: TabletAxisFrame,
) {
handle.axis(data, frame)
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
time: InputTime,
) {
handle.frame(data, time)
}
fn proximity_in(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &ProximityInEvent,
) {
handle.proximity_in(data, focus, event);
}
fn unset(&mut self, _data: &mut State) {}
}
impl MoveGrab {
pub fn new(
start_data: GrabStartData,
@ -778,10 +885,15 @@ impl MoveGrab {
self.previous == ManagedLayer::Tiling
}
pub fn is_touch_grab(&self) -> bool {
match self.start_data {
GrabStartData::Touch(_) => true,
GrabStartData::Pointer(_) => false,
pub fn grab_type(&self) -> GrabType {
self.start_data.type_()
}
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
Some(tool)
} else {
None
}
}
}
@ -793,7 +905,7 @@ impl Drop for MoveGrab {
let seat = self.seat.clone();
// false-positive: `Output`s hash is based on it's inner ptr
#[allow(clippy::mutable_key_type)]
let window_outputs = self.window_outputs.drain().collect::<HashSet<_>>();
let window_outputs = std::mem::take(&mut self.window_outputs);
let previous = self.previous;
let window = self.window.clone();
let is_touch_grab = matches!(self.start_data, GrabStartData::Touch(_));
@ -953,10 +1065,10 @@ impl Drop for MoveGrab {
position.as_logical().to_f64() - window.geometry().loc.to_f64()
+ offset,
)),
&MotionEvent {
&PointerMotionEvent {
location: pointer.current_location(),
serial,
time: state.common.clock.now().as_millis(),
time: InputTime::now(),
},
);
}

View file

@ -7,25 +7,36 @@ use crate::{
shell::{
element::CosmicMapped,
focus::target::PointerFocusTarget,
grabs::{GrabStartData, ReleaseMode, ResizeEdge},
grabs::{GrabStartData, GrabType, ReleaseMode, ResizeEdge},
},
utils::prelude::*,
};
use smithay::{
backend::input::ButtonState,
backend::input::{ButtonState, InputTime, TabletToolDescriptor},
desktop::{WindowSurface, space::SpaceElement},
input::{
Seat,
pointer::{
AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
RelativeMotionEvent,
},
tablet::{
TabletSeatHandler,
tool::{
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
},
},
touch::{
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
TouchInnerHandle, UpEvent as TouchUpEvent,
},
},
output::Output,
@ -170,10 +181,19 @@ impl ResizeSurfaceGrab {
false
}
pub fn is_touch_grab(&self) -> bool {
pub fn grab_type(&self) -> GrabType {
match self.start_data {
GrabStartData::Touch(_) => true,
GrabStartData::Pointer(_) => false,
GrabStartData::Pointer(_) => GrabType::Pointer,
GrabStartData::Touch(_) => GrabType::Touch,
GrabStartData::TabletTool { .. } => GrabType::TabletTool,
}
}
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
Some(tool)
} else {
None
}
}
}
@ -184,7 +204,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &MotionEvent,
event: &PointerMotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
@ -209,7 +229,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
handle.button(data, event);
match self.release {
@ -230,7 +250,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
details: PointerAxisFrame,
) {
handle.axis(data, details)
}
@ -329,12 +349,17 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
event: &TouchDownEvent,
) {
handle.down(data, None, event)
}
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &TouchUpEvent,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
handle.unset_grab(self, data);
}
@ -396,6 +421,99 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
}
}
impl TabletToolGrab<State> for ResizeSurfaceGrab {
fn start_data(&self) -> &TabletGrabStartData<State> {
match &self.start_data {
GrabStartData::TabletTool { data, .. } => data,
_ => unreachable!(),
}
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
handle.unset_grab(self, data, event.serial, event.time, true);
handle.proximity_out(data, event);
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &TabletMotionEvent,
) {
if self.update_location(event.location.as_global()) {
handle.unset_grab(self, data, event.serial, event.time, true);
}
handle.motion(data, None, event);
}
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletDownEvent,
) {
handle.down(data, event)
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletUpEvent,
) {
handle.unset_grab(self, data, event.serial, event.time, true);
handle.up(data, event);
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletButtonEvent,
) {
handle.button(data, event)
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
frame: TabletAxisFrame,
) {
handle.axis(data, frame)
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
time: InputTime,
) {
handle.frame(data, time)
}
fn unset(&mut self, _data: &mut State) {
self.ungrab()
}
fn proximity_in(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &ProximityInEvent,
) {
handle.proximity_in(data, None, event);
}
}
pub struct ResizeGrabMarker(AtomicBool);
impl ResizeGrabMarker {

View file

@ -4,31 +4,42 @@ use crate::{
backend::render::cursor::CursorState,
shell::{
focus::target::PointerFocusTarget,
grabs::{GrabStartData, ReleaseMode},
grabs::{GrabStartData, GrabType, ReleaseMode},
layout::Orientation,
},
utils::prelude::*,
};
use id_tree::{NodeId, Tree};
use smithay::{
backend::input::ButtonState,
backend::input::{ButtonState, InputTime, TabletToolDescriptor},
input::{
Seat,
pointer::{
AxisFrame, ButtonEvent, CursorIcon, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
PointerTarget, RelativeMotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon, Focus,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle, PointerTarget,
RelativeMotionEvent,
},
tablet::{
Tablet, TabletSeatHandler, TabletSeatTrait,
tool::{
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
DownEvent as TabletDownEvent, GrabStartData as TabletToolGrabStartData,
GrabTrigger as TabletToolGrabTrigger, MotionEvent as TabletMotionEvent,
ProximityInEvent, ProximityOutEvent, TabletToolGrab, TabletToolInnerHandle,
TabletToolTarget, UpEvent as TabletUpEvent,
},
},
touch::{
DownEvent, FrameMarker, GrabStartData as TouchGrabStartData,
DownEvent as TouchDownEvent, FrameMarker, GrabStartData as TouchGrabStartData,
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
TouchInnerHandle, TouchTarget, UpEvent,
TouchInnerHandle, TouchTarget, UpEvent as TouchUpEvent,
},
},
output::WeakOutput,
utils::{IsAlive, Logical, Point},
utils::{IsAlive, Logical, Point, Serial},
};
use super::super::{Data, TilingLayout};
@ -48,7 +59,7 @@ impl IsAlive for ResizeForkTarget {
}
impl PointerTarget<State> for ResizeForkTarget {
fn enter(&self, seat: &Seat<State>, _data: &mut State, _event: &MotionEvent) {
fn enter(&self, seat: &Seat<State>, _data: &mut State, _event: &PointerMotionEvent) {
let user_data = seat.user_data();
let cursor_state = user_data.get::<CursorState>().unwrap();
cursor_state
@ -60,19 +71,13 @@ impl PointerTarget<State> for ResizeForkTarget {
});
}
fn leave(
&self,
seat: &Seat<State>,
_data: &mut State,
_serial: smithay::utils::Serial,
_time: u32,
) {
fn leave(&self, seat: &Seat<State>, _data: &mut State, _serial: Serial, _time: InputTime) {
let user_data = seat.user_data();
let cursor_state = user_data.get::<CursorState>().unwrap();
cursor_state.lock().unwrap().unset_shape();
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
if event.button == 0x110 && event.state == ButtonState::Pressed {
let seat = seat.clone();
let node = self.node.clone();
@ -106,7 +111,7 @@ impl PointerTarget<State> for ResizeForkTarget {
}
}
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &MotionEvent) {}
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &PointerMotionEvent) {}
fn relative_motion(
&self,
_seat: &Seat<State>,
@ -114,7 +119,7 @@ impl PointerTarget<State> for ResizeForkTarget {
_event: &RelativeMotionEvent,
) {
}
fn axis(&self, _seat: &Seat<State>, _data: &mut State, _frame: AxisFrame) {}
fn axis(&self, _seat: &Seat<State>, _data: &mut State, _frame: PointerAxisFrame) {}
fn frame(&self, _seat: &Seat<State>, _data: &mut State) {}
fn gesture_swipe_begin(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeBeginEvent) {}
fn gesture_swipe_update(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeUpdateEvent) {}
@ -127,7 +132,7 @@ impl PointerTarget<State> for ResizeForkTarget {
}
impl TouchTarget<State> for ResizeForkTarget {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
let seat = seat.clone();
let node = self.node.clone();
let output = self.output.clone();
@ -158,7 +163,7 @@ impl TouchTarget<State> for ResizeForkTarget {
});
}
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &UpEvent) {}
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &TouchUpEvent) {}
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &TouchMotionEvent) {}
fn frame(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
fn cancel(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
@ -169,6 +174,121 @@ impl TouchTarget<State> for ResizeForkTarget {
}
}
impl TabletToolTarget<State> for ResizeForkTarget {
fn down(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &TabletDownEvent,
) {
let seat = seat.clone();
let node = self.node.clone();
let output = self.output.clone();
let left_up_idx = self.left_up_idx;
let orientation = self.orientation;
let serial = event.serial;
let time = event.time;
let tool = tool_descriptor.clone();
data.common.event_loop_handle.insert_idle(move |state| {
let pointer = seat.get_pointer().unwrap();
let location = pointer.current_location();
let tablet = seat.tablet_seat().get_tool(&tool).unwrap();
tablet.set_grab(
state,
ResizeForkGrab::new(
GrabStartData::TabletTool {
tool,
data: TabletToolGrabStartData {
focus: None,
location,
trigger: TabletToolGrabTrigger::Tip,
},
},
location.as_global(),
node,
left_up_idx,
orientation,
output,
ReleaseMode::NoMouseButtons,
),
time,
serial,
Focus::Keep,
)
});
}
fn motion(
&self,
_seat: &Seat<State>,
_data: &mut State,
_tool_descriptor: &TabletToolDescriptor,
_event: &TabletMotionEvent,
) {
}
fn proximity_out(
&self,
seat: &Seat<State>,
_data: &mut State,
_tool_descriptor: &TabletToolDescriptor,
) {
let user_data = seat.user_data();
let cursor_state = user_data.get::<CursorState>().unwrap();
cursor_state.lock().unwrap().unset_shape();
}
fn proximity_in(
&self,
seat: &Seat<State>,
_data: &mut State,
_tool_descriptor: &TabletToolDescriptor,
_tablet: &Tablet,
_serial: Serial,
) {
let user_data = seat.user_data();
let cursor_state = user_data.get::<CursorState>().unwrap();
cursor_state
.lock()
.unwrap()
.set_shape(match self.orientation {
Orientation::Horizontal => CursorIcon::RowResize,
Orientation::Vertical => CursorIcon::ColResize,
});
}
fn up(
&self,
_seat: &Seat<State>,
_data: &mut State,
_tool_descriptor: &TabletToolDescriptor,
_event: &TabletUpEvent,
) {
}
fn axis(
&self,
_seat: &Seat<State>,
_data: &mut State,
_tool_descriptor: &TabletToolDescriptor,
_frame: TabletAxisFrame,
) {
}
fn button(
&self,
_seat: &Seat<State>,
_data: &mut State,
_tool_descriptor: &TabletToolDescriptor,
_event: &TabletButtonEvent,
) {
}
fn frame(
&self,
_seat: &Seat<State>,
_data: &mut State,
_tool_descriptor: &TabletToolDescriptor,
_time: InputTime,
) {
}
}
pub struct ResizeForkGrab {
start_data: GrabStartData,
last_loc: Point<f64, Global>,
@ -338,10 +458,19 @@ impl ResizeForkGrab {
false
}
pub fn is_touch_grab(&self) -> bool {
pub fn grab_type(&self) -> GrabType {
match self.start_data {
GrabStartData::Touch(_) => true,
GrabStartData::Pointer(_) => false,
GrabStartData::Pointer(_) => GrabType::Pointer,
GrabStartData::Touch(_) => GrabType::Touch,
GrabStartData::TabletTool { .. } => GrabType::TabletTool,
}
}
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
Some(tool)
} else {
None
}
}
}
@ -352,7 +481,7 @@ impl PointerGrab<State> for ResizeForkGrab {
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &MotionEvent,
event: &PointerMotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
@ -377,7 +506,7 @@ impl PointerGrab<State> for ResizeForkGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
handle.button(data, event);
match self.release {
@ -398,7 +527,7 @@ impl PointerGrab<State> for ResizeForkGrab {
&mut self,
data: &mut State,
handle: &mut PointerInnerHandle<'_, State>,
details: AxisFrame,
details: PointerAxisFrame,
) {
handle.axis(data, details)
}
@ -497,12 +626,17 @@ impl TouchGrab<State> for ResizeForkGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
event: &TouchDownEvent,
) {
handle.down(data, None, event)
}
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &TouchUpEvent,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
handle.unset_grab(self, data);
}
@ -563,3 +697,96 @@ impl TouchGrab<State> for ResizeForkGrab {
self.update_location(data, self.last_loc.as_logical(), true);
}
}
impl TabletToolGrab<State> for ResizeForkGrab {
fn start_data(&self) -> &TabletToolGrabStartData<State> {
match &self.start_data {
GrabStartData::TabletTool { data, .. } => data,
_ => unreachable!(),
}
}
fn proximity_out(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &ProximityOutEvent,
) {
handle.proximity_out(data, event);
handle.unset_grab(self, data, event.serial, event.time, true);
}
fn motion(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &TabletMotionEvent,
) {
handle.motion(data, None, event);
if self.update_location(data, event.location, false) {
handle.unset_grab(self, data, event.serial, event.time, true);
}
}
fn down(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletDownEvent,
) {
handle.down(data, event);
}
fn up(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletUpEvent,
) {
handle.up(data, event);
handle.unset_grab(self, data, event.serial, event.time, true);
}
fn button(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
event: &TabletButtonEvent,
) {
handle.button(data, event)
}
fn axis(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
frame: TabletAxisFrame,
) {
handle.axis(data, frame)
}
fn frame(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
time: InputTime,
) {
handle.frame(data, time)
}
fn unset(&mut self, data: &mut State) {
self.update_location(data, self.last_loc.as_logical(), true);
}
fn proximity_in(
&mut self,
data: &mut State,
handle: &mut TabletToolInnerHandle<'_, State>,
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
event: &ProximityInEvent,
) {
handle.proximity_in(data, focus, event);
}
}

View file

@ -1,6 +1,6 @@
use cosmic_settings_config::shortcuts;
use smithay::{
backend::input::{KeyState, Keycode},
backend::input::{InputTime, KeyState, Keycode},
input::{
Seat, SeatHandler,
keyboard::{
@ -13,6 +13,7 @@ use smithay::{
use crate::{
config::key_bindings::cosmic_modifiers_from_smithay,
input::InputBackendId,
shell::{Trigger, layout::tiling::NodeDesc},
state::State,
};
@ -37,7 +38,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
state: KeyState,
modifiers: Option<ModifiersState>,
serial: Serial,
time: u32,
time: InputTime,
) {
if !matches!(&data.common.shell.read().overview_mode.active_trigger(), Some(Trigger::KeyboardSwap(_, d)) if d == &self.desc)
{
@ -73,6 +74,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
data.handle_shortcut_action(
shortcuts::Action::Focus(direction),
&InputBackendId::Normal,
&self.seat,
serial,
time,

View file

@ -3381,7 +3381,7 @@ impl TilingLayout {
let Some(root) = tree.root_node_id() else {
if matches!(
overview.active_trigger(),
Some(Trigger::Pointer(_) | Trigger::Touch(_))
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
) && location_f64.is_some()
{
let mut tree = tree.copy_clone();
@ -3414,7 +3414,7 @@ impl TilingLayout {
if matches!(
overview.active_trigger(),
Some(Trigger::Pointer(_) | Trigger::Touch(_))
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
) {
let non_exclusive_zone = layer_map_for_output(&self.output)
.non_exclusive_zone()
@ -4062,8 +4062,11 @@ impl TilingLayout {
let draw_groups = overview.0.alpha();
let is_overview = !matches!(overview.0, OverviewMode::None);
let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_))))
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
let is_mouse_tiling = (matches!(
overview.0.trigger(),
Some(Trigger::Pointer(_) | Trigger::Tool(_, _))
))
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
Some(desc.clone())
} else {
@ -4216,8 +4219,11 @@ impl TilingLayout {
};
let draw_groups = overview.0.alpha();
let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_))))
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
let is_mouse_tiling = (matches!(
overview.0.trigger(),
Some(Trigger::Pointer(_) | Trigger::Tool(_, _))
))
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
Some(desc.clone())
} else {

View file

@ -19,7 +19,10 @@ use crate::{
utils,
wayland::{
handlers::data_device::{self, get_dnd_icon},
protocols::workspace::{State as WState, WorkspaceCapabilities},
protocols::{
session_lock_layer::layer_show_on_lock,
workspace::{State as WState, WorkspaceCapabilities},
},
},
};
use cosmic_comp_config::{
@ -32,7 +35,10 @@ use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection, Resiz
use cosmic_settings_config::{shortcuts, window_rules::ApplicationException};
use keyframe::{ease, functions::EaseInOutCubic};
use smithay::{
backend::{input::TouchSlot, renderer::element::RenderElementStates},
backend::{
input::{TabletToolDescriptor, TouchSlot},
renderer::element::RenderElementStates,
},
desktop::{
LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, layer_map_for_output,
space::SpaceElement,
@ -46,6 +52,7 @@ use smithay::{
pointer::{
CursorImageStatus, CursorImageSurfaceData, Focus, GrabStartData as PointerGrabStartData,
},
tablet::{TabletSeatTrait, tool::GrabTrigger as TabletGrabTrigger},
},
output::{Output, WeakOutput},
reexports::{
@ -130,6 +137,7 @@ pub enum Trigger {
KeyboardMove(shortcuts::Modifiers),
Pointer(u32),
Touch(TouchSlot),
Tool(TabletToolDescriptor, TabletGrabTrigger),
}
#[derive(Debug, Clone)]
@ -254,6 +262,7 @@ pub struct PendingWindow {
pub surface: CosmicSurface,
pub seat: Seat<State>,
pub fullscreen: Option<Output>,
pub minimized: bool,
pub maximized: bool,
pub sticky: bool,
}
@ -1614,6 +1623,29 @@ impl Common {
a11y_keyboard_monitor.refresh();
}
self.image_copy_capture_state.cleanup();
self.cleanup_cursor_images();
}
/// Release the enlarged cursor frames a finished shake or zoom left behind.
fn cleanup_cursor_images(&mut self) {
let shell = self.shell.read();
let zoomed = shell.zoom_state.as_ref().is_some_and(|zoom_state| {
shell
.outputs()
.any(|output| zoom_state.animating_level(output) > 1.0)
});
let now = Instant::now();
for seat in shell.seats.iter() {
if let Some(cursor_state) = seat
.user_data()
.get::<crate::backend::render::cursor::CursorState>()
{
cursor_state
.lock()
.unwrap()
.drop_magnified_frames(now, zoomed);
}
}
}
pub fn refresh_idle_inhibit(&mut self) {
@ -1731,7 +1763,7 @@ impl Shell {
if let Some(set) = self.workspaces.sets.get_mut(output) {
if matches!(
self.overview_mode.active_trigger(),
Some(Trigger::Pointer(_) | Trigger::Touch(_))
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
) {
set.workspaces[set.active].tiling_layer.cleanup_drag();
}
@ -1782,7 +1814,7 @@ impl Shell {
if let Some(set) = self.workspaces.sets.get_mut(output) {
if matches!(
self.overview_mode.active_trigger(),
Some(Trigger::Pointer(_) | Trigger::Touch(_))
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
) {
set.workspaces[set.active].tiling_layer.cleanup_drag();
}
@ -2017,12 +2049,27 @@ impl Shell {
}
pub fn visible_output_for_surface(&self, surface: &WlSurface) -> Option<&Output> {
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
if let Some(session_lock) = &self.session_lock {
return session_lock
if let Some((output, _)) = session_lock
.surfaces
.iter()
.find(|(_, v)| v.wl_surface() == surface)
.map(|(k, _)| k);
{
return Some(output);
}
for o in self.outputs() {
let map = layer_map_for_output(o);
if let Some(layer_surface) = map.layer_for_surface(surface, WindowSurfaceType::ALL)
{
if layer_show_on_lock(layer_surface.wl_surface()) {
return Some(o);
} else {
return None;
}
}
}
}
self.outputs()
@ -2286,6 +2333,11 @@ impl Shell {
.is_some_and(|state| state.lock().unwrap().is_animating())
})
})
|| self.seats.iter().any(|seat| {
seat.user_data()
.get::<crate::backend::render::cursor::CursorState>()
.is_some_and(|state| state.lock().unwrap().is_magnifying())
})
}
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
@ -2828,6 +2880,7 @@ impl Shell {
surface: window,
seat,
fullscreen: output,
minimized: should_be_minimized,
maximized: should_be_maximized,
sticky: mut should_be_sticky,
} = self.pending_windows.remove(pos);
@ -2911,6 +2964,7 @@ impl Shell {
if let Some(FocusTarget::Window(focused)) = maybe_focused
&& let Some(stack) = focused.stack_ref()
&& !is_dialog
&& !should_be_minimized
&& !should_be_maximized
&& !(workspace.is_tiled(&focused.active_window()) && floating_exception)
{
@ -2960,8 +3014,13 @@ impl Shell {
self.maximize_request(&mapped, &seat, false, loop_handle);
}
let new_target = if (workspace_output == seat.active_output()
&& active_handle == workspace_handle)
if should_be_minimized {
self.minimize_request(&window);
}
let new_target = if should_be_minimized {
None
} else if (workspace_output == seat.active_output() && active_handle == workspace_handle)
|| should_be_sticky
{
// TODO: enforce focus stealing prevention by also checking the same rules as for the else case.
@ -3085,6 +3144,7 @@ impl Shell {
surface,
seat: seat.clone(),
fullscreen: None,
minimized: false,
maximized: false,
sticky: false,
});
@ -3803,6 +3863,7 @@ impl Shell {
let trigger = match &start_data {
GrabStartData::Pointer(start_data) => Trigger::Pointer(start_data.button),
GrabStartData::Touch(start_data) => Trigger::Touch(start_data.slot),
GrabStartData::TabletTool { tool, data } => Trigger::Tool(tool.clone(), data.trigger),
};
let active_hint = if config.cosmic_conf.active_hint {
self.theme.cosmic().active_hint as u8
@ -5024,6 +5085,20 @@ impl Shell {
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
if let Some(active) = self.active_space(output) {
for fs in active.get_fullscreen_surfaces() {
fs.surface.take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(
surface,
None,
render_element_states,
)
},
);
}
active.mapped().for_each(|mapped| {
mapped.active_window().take_presentation_feedback(
&mut output_presentation_feedback,
@ -5107,10 +5182,19 @@ pub fn check_grab_preconditions(
let pointer = seat.get_pointer().unwrap();
let touch = seat.get_touch().unwrap();
let tablet = seat.tablet_seat();
let tools = tablet.get_tools();
let start_data =
if serial.is_some_and(|serial| touch.has_grab(serial)) {
GrabStartData::Touch(touch.grab_start_data().unwrap())
} else if let Some((desc, tool)) =
serial.and_then(|serial| tools.iter().find(|(_, tool)| tool.has_grab(serial)))
{
GrabStartData::TabletTool {
tool: desc.clone(),
data: tool.grab_start_data().unwrap(),
}
} else {
GrabStartData::Pointer(pointer.grab_start_data().unwrap_or_else(|| {
PointerGrabStartData {
@ -5124,8 +5208,16 @@ pub fn check_grab_preconditions(
if let Some(surface) = client_initiated {
// Check that this surface has a click or touch down grab.
if !match serial {
Some(serial) => pointer.has_grab(serial) || touch.has_grab(serial),
None => pointer.is_grabbed() | touch.is_grabbed(),
Some(serial) => {
pointer.has_grab(serial)
|| touch.has_grab(serial)
|| tools.values().any(|tool| tool.has_grab(serial))
}
None => {
pointer.is_grabbed()
|| touch.is_grabbed()
|| tools.values().any(|tool| tool.is_grabbed())
}
} {
return None;
}

View file

@ -5,7 +5,7 @@ use std::{any::Any, cell::RefCell, collections::HashMap, sync::Mutex};
use crate::{
backend::render::cursor::CursorState,
config::{Config, xkb_config_to_wl},
input::{ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
input::{InputBackendId, ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
state::State,
};
use smithay::{
@ -82,12 +82,25 @@ impl Seats {
self.last_active = Some(seat.clone());
}
pub fn for_device<D: Device>(&self, device: &D) -> Option<&Seat<State>> {
self.iter().find(|seat| {
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
devices.has_device(device)
})
pub fn for_device<D: Device>(
&self,
device: &D,
backend_id: &InputBackendId,
) -> Option<&Seat<State>> {
self.iter()
.find(|seat| {
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
devices.has_device(device, backend_id)
})
.or_else(|| {
// EI devices can be transiently unregistered while the compositor recreates
// the absolute-pointer device (e.g. on a scale/geometry change), which would
// otherwise drop all pointer/touch input until the client re-binds it. EI is
// single-seat, so fall back to the active seat here, matching the EI keyboard
// path, which always targets the active seat.
matches!(backend_id, InputBackendId::Ei(_)).then(|| self.last_active())
})
}
}
@ -96,6 +109,7 @@ impl Devices {
&self,
device: &D,
led_state: LedState,
backend_id: &InputBackendId,
) -> Vec<DeviceCapability> {
let id = device.id();
let mut map = self.capabilities.borrow_mut();
@ -113,7 +127,7 @@ impl Devices {
.cloned()
.filter(|c| map.values().flatten().all(|has| *c != *has))
.collect::<Vec<_>>();
map.insert(id, caps);
map.insert((backend_id.clone(), id), caps);
if device.has_capability(DeviceCapability::Keyboard)
&& let Some(device) = <dyn Any>::downcast_ref::<InputDevice>(device)
@ -126,11 +140,18 @@ impl Devices {
new_caps
}
pub fn has_device<D: Device>(&self, device: &D) -> bool {
self.capabilities.borrow().contains_key(&device.id())
/// Whether the given backend's device with this id is registered on the seat.
pub fn has_device<D: Device>(&self, device: &D, backend_id: &InputBackendId) -> bool {
self.capabilities
.borrow()
.contains_key(&(backend_id.clone(), device.id()))
}
pub fn remove_device<D: Device>(&self, device: &D) -> Vec<DeviceCapability> {
pub fn remove_device<D: Device>(
&self,
device: &D,
backend_id: &InputBackendId,
) -> Vec<DeviceCapability> {
let id = device.id();
let mut keyboards = self.keyboards.borrow_mut();
@ -139,7 +160,7 @@ impl Devices {
}
let mut map = self.capabilities.borrow_mut();
map.remove(&id)
map.remove(&(backend_id.clone(), id))
.unwrap_or_default()
.into_iter()
.filter(|c| map.values().flatten().all(|has| *c != *has))
@ -155,7 +176,8 @@ impl Devices {
#[derive(Default)]
pub struct Devices {
capabilities: RefCell<HashMap<String, Vec<DeviceCapability>>>,
// Keyed by `(backend, device_id)`
capabilities: RefCell<HashMap<(InputBackendId, String), Vec<DeviceCapability>>>,
// Used for updating keyboard leds on kms backend
keyboards: RefCell<Vec<InputDevice>>,
}
@ -185,7 +207,7 @@ struct FocusedOutput(pub Mutex<Option<Output>>);
pub struct PointerConstraintHint(pub Mutex<Option<(WlSurface, Point<f64, Logical>)>>);
#[derive(Default)]
pub struct LastModifierChange(pub Mutex<Option<Serial>>);
pub struct LastModifierChange(pub Mutex<(HashMap<InputBackendId, Serial>, Option<Serial>)>);
pub fn create_seat(
dh: &DisplayHandle,
@ -265,6 +287,9 @@ pub trait SeatExt {
fn supressed_buttons(&self) -> &SupressedButtons;
fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue;
fn last_modifier_change(&self) -> Option<Serial>;
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial>;
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial);
fn clear_last_modifier_change(&self, backend_id: &InputBackendId);
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)>;
fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point<f64, Logical>)>);
@ -343,13 +368,48 @@ impl SeatExt for Seat<State> {
}
fn last_modifier_change(&self) -> Option<Serial> {
*self
.user_data()
self.user_data()
.get::<LastModifierChange>()
.unwrap()
.0
.lock()
.unwrap()
.1
}
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial> {
self.user_data()
.get::<LastModifierChange>()
.unwrap()
.0
.lock()
.unwrap()
.0
.get(backend_id)
.copied()
}
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial) {
let mut guard = self
.user_data()
.get::<LastModifierChange>()
.unwrap()
.0
.lock()
.unwrap();
guard.0.insert(backend_id.clone(), serial);
guard.1 = Some(serial);
}
fn clear_last_modifier_change(&self, backend_id: &InputBackendId) {
self.user_data()
.get::<LastModifierChange>()
.unwrap()
.0
.lock()
.unwrap()
.0
.remove(backend_id);
}
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)> {

View file

@ -7,23 +7,35 @@ use cosmic::{
theme,
widget::{self, icon::Named},
};
use cosmic_comp_config::ZoomMovement;
use cosmic_comp_config::{ZoomConfig, ZoomMovement};
use cosmic_config::ConfigSet;
use keyframe::{ease, functions::Linear};
use smithay::{
backend::renderer::ImportMem,
backend::{
input::{InputTime, TabletToolDescriptor},
renderer::ImportMem,
},
desktop::space::SpaceElement,
input::{
Seat,
pointer::{
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, Focus,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
PointerTarget, RelativeMotionEvent,
},
tablet::{
Tablet, TabletSeatTrait,
tool::{
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
DownEvent as TabletDownEvent, MotionEvent as TabletMotionEvent, TabletToolTarget,
UpEvent as TabletUpEvent,
},
},
touch::{
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
},
},
output::Output,
@ -33,6 +45,7 @@ use tracing::error;
use crate::{
backend::render::element::AsGlowRenderer,
shell::grabs::GrabType,
state::State,
utils::{
iced::{IcedElement, IcedRenderElement, Program},
@ -447,7 +460,7 @@ pub enum MenuMessage {
impl ZoomProgram {
pub fn new(level: f64, movement: ZoomMovement, increment: u32) -> Self {
let mut increments = vec![25, 50, 100, 150, 200];
let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec();
if !increments.contains(&increment) {
increments.push(increment);
}
@ -699,15 +712,27 @@ impl Program for ZoomProgram {
);
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer().unwrap().set_grab(
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
state,
grab,
serial,
Focus::Clear,
);
),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(
state,
grab,
InputTime::now(),
serial,
Focus::Clear,
),
}
}
}
@ -786,15 +811,27 @@ impl Program for ZoomProgram {
);
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer().unwrap().set_grab(
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch().unwrap().set_grab(state, grab, serial)
}
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
state,
grab,
serial,
Focus::Clear,
);
),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(
state,
grab,
InputTime::now(),
serial,
Focus::Clear,
),
}
}
}
@ -829,7 +866,7 @@ impl Program for ZoomProgram {
if let Some(pos) = self.increments.iter().position(|val| *val == increment) {
self.increment_idx = pos;
} else {
let mut increments = vec![25, 50, 100, 150, 200];
let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec();
if !increments.contains(&increment) {
increments.push(increment);
}
@ -884,14 +921,14 @@ impl PointerTarget<State> for ZoomFocusTarget {
}
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
match self {
ZoomFocusTarget::Main(elem) => PointerTarget::button(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => PointerTarget::button(elem, seat, data, event),
}
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
match self {
ZoomFocusTarget::Main(elem) => PointerTarget::axis(elem, seat, data, frame),
ZoomFocusTarget::Menu(elem) => PointerTarget::axis(elem, seat, data, frame),
@ -1024,7 +1061,7 @@ impl PointerTarget<State> for ZoomFocusTarget {
}
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
match self {
ZoomFocusTarget::Main(elem) => PointerTarget::leave(elem, seat, data, serial, time),
ZoomFocusTarget::Menu(elem) => PointerTarget::leave(elem, seat, data, serial, time),
@ -1033,14 +1070,14 @@ impl PointerTarget<State> for ZoomFocusTarget {
}
impl TouchTarget<State> for ZoomFocusTarget {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event),
}
}
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event),
@ -1090,6 +1127,144 @@ impl TouchTarget<State> for ZoomFocusTarget {
}
}
impl TabletToolTarget<State> for ZoomFocusTarget {
fn proximity_in(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
tablet: &Tablet,
serial: Serial,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::proximity_in(elem, seat, data, tool_descriptor, tablet, serial)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::proximity_in(elem, seat, data, tool_descriptor, tablet, serial)
}
}
}
fn proximity_out(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::proximity_out(elem, seat, data, tool_descriptor)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::proximity_out(elem, seat, data, tool_descriptor)
}
}
}
fn down(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &TabletDownEvent,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::down(elem, seat, data, tool_descriptor, event)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::down(elem, seat, data, tool_descriptor, event)
}
}
}
fn up(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &TabletUpEvent,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::up(elem, seat, data, tool_descriptor, event)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::up(elem, seat, data, tool_descriptor, event)
}
}
}
fn motion(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &TabletMotionEvent,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::motion(elem, seat, data, tool_descriptor, event)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::motion(elem, seat, data, tool_descriptor, event)
}
}
}
fn axis(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
frame: TabletAxisFrame,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::axis(elem, seat, data, tool_descriptor, frame)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::axis(elem, seat, data, tool_descriptor, frame)
}
}
}
fn button(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
event: &TabletButtonEvent,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::button(elem, seat, data, tool_descriptor, event)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::button(elem, seat, data, tool_descriptor, event)
}
}
}
fn frame(
&self,
seat: &Seat<State>,
data: &mut State,
tool_descriptor: &TabletToolDescriptor,
time: InputTime,
) {
match self {
ZoomFocusTarget::Main(elem) => {
TabletToolTarget::frame(elem, seat, data, tool_descriptor, time)
}
ZoomFocusTarget::Menu(elem) => {
TabletToolTarget::frame(elem, seat, data, tool_descriptor, time)
}
}
}
}
impl IsAlive for ZoomFocusTarget {
fn alive(&self) -> bool {
match self {

View file

@ -23,6 +23,7 @@ use crate::{
output_configuration::OutputConfigurationState,
output_power::OutputPowerState,
overlap_notify::OverlapNotifyState,
session_lock_layer::SessionLockLayerState,
toplevel_info::ToplevelInfoState,
toplevel_management::{ManagementCapabilities, ToplevelManagementState},
workspace::{WorkspaceState, WorkspaceUpdateGuard},
@ -244,9 +245,32 @@ pub struct Common {
pub clock: Clock<Monotonic>,
pub startup_done: Arc<AtomicBool>,
pub should_stop: bool,
pub kiosk_exit_code: Option<i32>,
pub gesture_state: Option<GestureState>,
/// Active libei sender seats, keyed by their `eis` connection. Tracked so their virtual
/// keyboards can be re-created when the keyboard configuration changes at runtime.
pub ei_seats: std::collections::HashMap<
smithay::reexports::reis::eis::Connection,
smithay::backend::libei::EiInputSeat,
>,
/// The shared-seat [`KeyboardSource`] assigned to each libei connection, so its
/// `ei_keyboard` key events feed the seat keyboard with independent per-source hold
/// tracking (and can be released together on disconnect). Keyed by connection.
pub ei_keyboard_source: std::collections::HashMap<
smithay::reexports::reis::eis::Connection,
smithay::input::keyboard::KeyboardSource,
>,
/// Pointer buttons currently held by each libei connection, so they can be released when the
/// connection drops
pub ei_pointer_buttons: std::collections::HashMap<
smithay::reexports::reis::eis::Connection,
std::collections::HashSet<u32>,
>,
pub kiosk_child: Option<Child>,
pub theme: cosmic::Theme,
@ -275,7 +299,7 @@ pub struct Common {
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
pub shm_state: ShmState,
pub cursor_shape_manager_state: CursorShapeManagerState,
pub wl_drm_state: WlDrmState<Option<DrmNode>>,
pub wl_drm_state: Option<WlDrmState<Option<DrmNode>>>,
pub viewporter_state: ViewporterState,
pub kde_decoration_state: KdeDecorationState,
pub xdg_decoration_state: XdgDecorationState,
@ -297,6 +321,7 @@ pub struct Common {
pub xwayland_state: Option<XWaylandState>,
pub xwayland_shell_state: XWaylandShellState,
pub pointer_focus_state: Option<PointerFocusState>,
pub session_lock_layer_state: SessionLockLayerState,
#[cfg(feature = "logind")]
pub inhibit_lid_fd: Option<OwnedFd>,
@ -325,7 +350,7 @@ pub enum LockedBackend<'a> {
pub struct SurfaceDmabufFeedback {
pub render_feedback: DmabufFeedback,
pub overlay_scanout_feedback: Option<DmabufFeedback>,
pub primary_scanout_feedback: Option<DmabufFeedback>,
pub primary_scanout_feedback: DmabufFeedback,
}
#[derive(Debug)]
@ -642,7 +667,7 @@ impl State {
.unwrap();
let clock = Clock::new();
let config = Config::load(&handle);
let config = Config::load(&handle, kiosk_command.is_some());
let compositor_state = CompositorState::new::<Self>(dh);
let corner_radius_state = CornerRadiusState::new::<Self>(dh);
let data_device_state = DataDeviceState::new::<Self>(dh);
@ -670,7 +695,7 @@ impl State {
let cursor_shape_manager_state = CursorShapeManagerState::new::<State>(dh);
let seat_state = SeatState::<Self>::new();
let viewporter_state = ViewporterState::new::<Self>(dh);
let wl_drm_state = WlDrmState::<Option<DrmNode>>::default();
let wl_drm_state = None;
let kde_decoration_state = KdeDecorationState::new::<Self>(dh, Mode::Client);
let xdg_decoration_state = XdgDecorationState::new::<Self>(dh);
let session_lock_manager_state =
@ -740,6 +765,9 @@ impl State {
let dbus_state = DBusState::init(&handle);
let session_lock_layer_state =
SessionLockLayerState::new::<State, _>(dh, client_not_sandboxed);
State {
common: Common {
config,
@ -754,7 +782,11 @@ impl State {
clock,
startup_done: Arc::new(AtomicBool::new(false)),
should_stop: false,
kiosk_exit_code: None,
gesture_state: None,
ei_seats: std::collections::HashMap::new(),
ei_keyboard_source: std::collections::HashMap::new(),
ei_pointer_buttons: std::collections::HashMap::new(),
kiosk_child: None,
theme: cosmic::theme::system_preference(),
@ -803,6 +835,7 @@ impl State {
pointer_focus_state: None,
dbus_state,
keyboard_layout_state,
session_lock_layer_state,
#[cfg(feature = "logind")]
inhibit_lid_fd: None,
@ -941,6 +974,8 @@ impl Common {
output: &Output,
render_element_states: &RenderElementStates,
) {
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
let shell = self.shell.read();
let processor = |namespace: Option<usize>| {
move |surface: &WlSurface, states: &SurfaceData| {
@ -954,8 +989,11 @@ impl Common {
);
if let Some(output) = primary_scanout_output {
with_fractional_scale(states, |fraction_scale| {
fraction_scale
.set_preferred_scale(output.current_scale().fractional_scale());
// The 1.0 clamp is a workaround for Chromium
// TODO: remove if Chromium ever gets fixed
fraction_scale.set_preferred_scale(
output.current_scale().fractional_scale().max(1.0),
);
});
}
}
@ -1044,6 +1082,8 @@ impl Common {
render_element_states: &RenderElementStates,
mut dmabuf_feedback: impl FnMut(DrmNode) -> Option<SurfaceDmabufFeedback>,
) {
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
let shell = self.shell.read();
if let Some(session_lock) = shell.session_lock.as_ref()
@ -1061,10 +1101,7 @@ impl Common {
surface,
render_element_states,
&feedback.render_feedback,
feedback
.primary_scanout_feedback
.as_ref()
.unwrap_or(&feedback.render_feedback),
&feedback.primary_scanout_feedback,
)
},
)
@ -1260,6 +1297,8 @@ impl Common {
#[profiling::function]
pub fn send_frames(&self, output: &Output, sequence: Option<usize>) {
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
let time = self.clock.now();
let should_send = |surface: &WlSurface, states: &SurfaceData| {
// Do the standard primary scanout output check. For pointer surfaces it deduplicates
@ -1301,8 +1340,12 @@ impl Common {
const THROTTLE: Option<Duration> = Some(Duration::from_millis(995));
const SCREENCOPY_THROTTLE: Option<Duration> = Some(Duration::from_nanos(16_666_666));
fn throttle(session_holder: &impl SessionHolder) -> Option<Duration> {
if session_holder.sessions().is_empty() && session_holder.cursor_sessions().is_empty() {
fn throttle(session_holder: &impl SessionHolder, is_xwayland: bool) -> Option<Duration> {
if is_xwayland {
Some(Duration::ZERO)
} else if session_holder.sessions().is_empty()
&& session_holder.cursor_sessions().is_empty()
{
THROTTLE
} else {
SCREENCOPY_THROTTLE
@ -1338,7 +1381,8 @@ impl Common {
&& let Some(grab_state) = move_grab.lock().unwrap().as_ref()
{
for (window, _) in grab_state.element().windows() {
window.send_frame(output, time, throttle(&window), should_send);
let throttle = throttle(&window, window.x11_surface().is_some());
window.send_frame(output, time, throttle, should_send);
}
}
@ -1362,25 +1406,28 @@ impl Common {
.mapped()
.for_each(|mapped| {
for (window, _) in mapped.windows() {
window.send_frame(output, time, throttle(&window), should_send);
let throttle = throttle(&window, window.x11_surface().is_some());
window.send_frame(output, time, throttle, should_send);
}
});
if let Some(active) = shell.active_space(output) {
if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) {
fs.surface
.send_frame(output, time, throttle(&fs.surface), should_send);
let throttle = throttle(&fs.surface, fs.surface.x11_surface().is_some());
fs.surface.send_frame(output, time, throttle, should_send);
}
active.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() {
window.send_frame(output, time, throttle(&window), should_send);
let throttle = throttle(&window, window.x11_surface().is_some());
window.send_frame(output, time, throttle, should_send);
}
});
// other (throttled) windows
active.minimized_windows.iter().for_each(|m| {
for window in m.windows() {
window.send_frame(output, time, throttle(&window), |_, _| None);
let throttle = throttle(&window, window.x11_surface().is_some());
window.send_frame(output, time, throttle, |_, _| None);
}
});
@ -1390,18 +1437,25 @@ impl Common {
.filter(|w| w.handle != active.handle)
{
if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
let throttle = min(throttle(space), throttle(&fs.surface));
let throttle = min(
throttle(space, false),
throttle(&fs.surface, fs.surface.x11_surface().is_some()),
);
fs.surface.send_frame(output, time, throttle, |_, _| None);
}
space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() {
let throttle = min(throttle(space), throttle(&window));
let throttle = min(
throttle(space, false),
throttle(&window, window.x11_surface().is_some()),
);
window.send_frame(output, time, throttle, |_, _| None);
}
});
space.minimized_windows.iter().for_each(|m| {
for window in m.windows() {
window.send_frame(output, time, throttle(&window), |_, _| None);
let throttle = throttle(&window, window.x11_surface().is_some());
window.send_frame(output, time, throttle, |_, _| None);
}
})
}

28
src/utils/global.rs Normal file
View file

@ -0,0 +1,28 @@
use std::time::Duration;
use calloop::{
LoopHandle,
timer::{TimeoutAction, Timer},
};
use smithay::reexports::wayland_server::DisplayHandle;
use wayland_backend::server::GlobalId;
pub fn remove_global_with_timer<D: 'static>(
dh: &DisplayHandle,
event_loop_handle: &LoopHandle<D>,
id: GlobalId,
) {
dh.disable_global::<D>(id.clone());
let source = Timer::from_duration(Duration::from_secs(5));
let dh = dh.clone();
let res = event_loop_handle.insert_source(source, move |_, _, _state| {
dh.remove_global::<D>(id.clone());
TimeoutAction::Drop
});
if let Err(err) = res {
tracing::error!(
"failed to insert timer source to destroy output global: {}",
err
);
}
}

View file

@ -31,7 +31,7 @@ use ordered_float::OrderedFloat;
use smithay::{
backend::{
allocator::Fourcc,
input::{ButtonState, KeyState},
input::{ButtonState, InputTime, KeyState, TabletToolDescriptor},
renderer::{
ImportMem,
element::{
@ -45,14 +45,23 @@ use smithay::{
Seat,
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
PointerTarget, RelativeMotionEvent,
},
tablet::{
Tablet,
tool::{
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
UpEvent as ToolUpEvent,
},
},
touch::{
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
},
},
output::Output,
@ -189,6 +198,7 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
touch_map: HashMap<Finger, IcedPoint>,
last_touch_frame: Option<FrameMarker>,
last_touch_serial: Option<Serial>,
last_tablet_serial: Option<Serial>,
// iced
theme: Theme,
@ -240,6 +250,7 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
touch_map: self.touch_map.clone(),
last_touch_frame: None,
last_touch_serial: None,
last_tablet_serial: None,
theme: self.theme.clone(),
renderer,
state,
@ -267,6 +278,7 @@ impl<P: Program + Send + 'static> fmt::Debug for IcedElementInternal<P> {
.field("touch_map", &self.touch_map)
.field("last_touch_frame", &self.last_touch_frame)
.field("last_touch_serial", &self.last_touch_serial)
.field("last_tablet_serial", &self.last_tablet_serial)
.field("theme", &"...")
.field("renderer", &"...")
.field("state", &"...")
@ -326,6 +338,7 @@ impl<P: Program + Send + 'static> IcedElement<P> {
touch_map: HashMap::new(),
last_touch_frame: None,
last_touch_serial: None,
last_tablet_serial: None,
theme,
renderer,
state,
@ -481,12 +494,143 @@ impl<P: Program + Send + 'static> IcedElementInternal<P> {
}
}
impl<P: Program + Send + 'static> TabletToolTarget<crate::state::State> for IcedElement<P> {
fn proximity_in(
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
_tablet: &Tablet,
serial: Serial,
) {
let mut internal = self.0.lock().unwrap();
internal
.state
.queue_event(Event::Mouse(MouseEvent::CursorEntered));
internal.last_tablet_serial = Some(serial);
*internal.last_seat.lock().unwrap() = Some((seat.clone(), serial));
internal.update(false);
}
fn proximity_out(
&self,
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
) {
let mut internal = self.0.lock().unwrap();
internal
.state
.queue_event(Event::Mouse(MouseEvent::CursorLeft));
internal.update(false);
}
fn down(
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
event: &ToolDownEvent,
) {
let mut internal = self.0.lock().unwrap();
let id = Finger(0);
let Some(event_location) = internal.cursor_pos else {
return;
};
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
internal
.state
.queue_event(Event::Touch(TouchEvent::FingerPressed { id, position }));
internal.last_tablet_serial = Some(event.serial);
*internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial));
internal.update(false);
}
fn up(
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
_event: &ToolUpEvent,
) {
let mut internal = self.0.lock().unwrap();
let id = Finger(0);
if let Some(event_location) = internal.cursor_pos {
*internal.last_seat.lock().unwrap() =
Some((seat.clone(), internal.last_tablet_serial.unwrap()));
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
internal
.state
.queue_event(Event::Touch(TouchEvent::FingerLifted { id, position }));
internal.update(false);
}
}
fn motion(
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
event: &ToolMotionEvent,
) {
let mut internal = self.0.lock().unwrap();
let event_location = event.location.downscale(internal.additional_scale);
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
internal
.state
.queue_event(Event::Mouse(MouseEvent::CursorMoved { position }));
internal.cursor_pos = Some(event_location);
*internal.last_seat.lock().unwrap() =
Some((seat.clone(), internal.last_tablet_serial.unwrap()));
internal.update(false);
}
fn axis(
&self,
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
_frame: ToolAxisFrame,
) {
}
fn button(
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
event: &ToolButtonEvent,
) {
let mut internal = self.0.lock().unwrap();
let button = match event.button {
0x14b => MouseButton::Right,
0x14c => MouseButton::Middle,
_ => return,
};
internal.state.queue_event(Event::Mouse(match event.state {
ButtonState::Pressed => MouseEvent::ButtonPressed(button),
ButtonState::Released => MouseEvent::ButtonReleased(button),
}));
*internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial));
internal.update(false);
}
fn frame(
&self,
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_tool_descriptor: &TabletToolDescriptor,
_time: InputTime,
) {
}
}
impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedElement<P> {
fn enter(
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &MotionEvent,
event: &PointerMotionEvent,
) {
let mut internal = self.0.lock().unwrap();
internal
@ -507,7 +651,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &MotionEvent,
event: &PointerMotionEvent,
) {
let mut internal = self.0.lock().unwrap();
let event_location = event.location.downscale(internal.additional_scale);
@ -532,7 +676,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &ButtonEvent,
event: &PointerButtonEvent,
) {
let mut internal = self.0.lock().unwrap();
let button = match event.button {
@ -553,7 +697,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
&self,
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
frame: AxisFrame,
frame: PointerAxisFrame,
) {
let mut internal = self.0.lock().unwrap();
internal
@ -581,7 +725,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_serial: Serial,
_time: u32,
_time: InputTime,
) {
let mut internal = self.0.lock().unwrap();
internal
@ -653,7 +797,7 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &DownEvent,
event: &TouchDownEvent,
) {
let mut internal = self.0.lock().unwrap();
let id = Finger(i32::from(event.slot) as u64);
@ -673,7 +817,7 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
&self,
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &UpEvent,
event: &TouchUpEvent,
) {
let mut internal = self.0.lock().unwrap();
let id = Finger(i32::from(event.slot) as u64);
@ -784,7 +928,7 @@ impl<P: Program + Send + 'static> KeyboardTarget<crate::state::State> for IcedEl
_key: KeysymHandle<'_>,
_state: KeyState,
_serial: Serial,
_time: u32,
_time: InputTime,
) {
// TODO convert keys
}

View file

@ -4,6 +4,7 @@ pub mod env;
mod ids;
pub(crate) use self::ids::id_gen;
pub mod geometry;
pub mod global;
pub mod iced;
pub mod prelude;
pub mod quirks;

View file

@ -3,9 +3,12 @@
use crate::{shell::grabs::SeatMoveGrabState, state::ClientState, utils::prelude::*};
use calloop::Interest;
use smithay::{
backend::renderer::{
element::{Kind, surface::KindEvaluation},
utils::{on_commit_buffer_handler, with_renderer_surface_state},
backend::{
input::InputTime,
renderer::{
element::{Kind, surface::KindEvaluation},
utils::{on_commit_buffer_handler, with_renderer_surface_state},
},
},
desktop::{LayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output},
reexports::wayland_server::{Client, Resource, protocol::wl_surface::WlSurface},
@ -325,9 +328,11 @@ impl CompositorHandler for State {
}
} else {
std::mem::drop(shell);
seat.get_pointer()
.unwrap()
.unset_grab(self, SERIAL_COUNTER.next_serial(), 0);
seat.get_pointer().unwrap().unset_grab(
self,
SERIAL_COUNTER.next_serial(),
InputTime::now(),
);
return;
}
}

View file

@ -45,18 +45,11 @@ pub fn surface_corners(states: &SurfaceData, size: Size<i32, Logical>) -> Option
])
}
pub fn surface_padding(states: &SurfaceData, size: Size<i32, Logical>) -> Option<[i32; 4]> {
pub fn surface_padding(states: &SurfaceData) -> Option<[i32; 4]> {
let mut guard = states.cached_state.get::<CacheablePadding>();
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),
])
Some([padding.top, padding.right, padding.bottom, padding.left])
}
pub fn pad_rect(

View file

@ -1,5 +1,6 @@
use std::{cell::RefCell, sync::Mutex};
use cosmic_comp_config::DecorationPreference;
use smithay::{
desktop::Window,
reexports::{
@ -20,7 +21,7 @@ use smithay::{
};
use wayland_backend::protocol::WEnum;
use crate::state::State;
use crate::{shell::CosmicMapped, state::State};
pub struct PreferredDecorationMode(RefCell<Option<XdgMode>>);
@ -29,7 +30,7 @@ impl PreferredDecorationMode {
window
.user_data()
.get::<PreferredDecorationMode>()
.is_none()
.is_none_or(|mode| mode.0.borrow().is_none())
}
pub fn mode(window: &Window) -> Option<XdgMode> {
@ -53,6 +54,72 @@ impl PreferredDecorationMode {
}
}
impl State {
pub fn default_decoration(&self) -> DecorationPreference {
self.common.config.cosmic_conf.decoration_preference
}
pub fn update_decorations(&self) {
let shell = self.common.shell.read();
let update = |mapped: &CosmicMapped| {
let mode = if mapped.is_stack() {
XdgMode::ServerSide
} else {
XdgMode::from_preference(self.default_decoration())
};
for (window, _) in mapped.windows() {
if PreferredDecorationMode::is_unset(&window.0)
&& let Some(toplevel) = window.0.toplevel()
&& toplevel.with_committed_state(|state| {
state.is_some_and(|state| state.decoration_mode.is_some())
})
{
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(mode);
});
toplevel.send_configure();
}
}
};
for set in shell.workspaces.sets.values() {
set.sticky_layer.mapped().for_each(update);
}
for space in shell.workspaces.spaces() {
space.mapped().for_each(update);
space
.minimized_windows
.iter()
.filter_map(|m| m.mapped())
.for_each(update);
}
}
}
trait FromDecorationPreference {
fn from_preference(preference: DecorationPreference) -> Self;
}
impl FromDecorationPreference for XdgMode {
fn from_preference(preference: DecorationPreference) -> Self {
match preference {
DecorationPreference::ClientSide => XdgMode::ClientSide,
DecorationPreference::ServerSide => XdgMode::ServerSide,
}
}
}
impl FromDecorationPreference for KdeMode {
fn from_preference(preference: DecorationPreference) -> Self {
match preference {
DecorationPreference::ClientSide => KdeMode::Client,
DecorationPreference::ServerSide => KdeMode::Server,
}
}
}
pub type KdeDecorationData = Mutex<KdeDecorationSurfaceState>;
#[derive(Debug, Default)]
pub struct KdeDecorationSurfaceState {
@ -67,7 +134,7 @@ impl XdgDecorationHandler for State {
let mode = if mapped.is_stack() {
XdgMode::ServerSide
} else {
XdgMode::ClientSide
XdgMode::from_preference(self.default_decoration())
};
if let Some((window, _)) = mapped
@ -80,6 +147,10 @@ impl XdgDecorationHandler for State {
});
toplevel.send_configure();
}
} else {
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
})
}
}
@ -99,6 +170,13 @@ impl XdgDecorationHandler for State {
}
} else {
toplevel.with_pending_state(|state| state.decoration_mode = Some(mode));
if let Some(pending) = shell
.pending_windows
.iter()
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
{
PreferredDecorationMode::update(&pending.surface.0, Some(mode));
}
}
}
@ -110,11 +188,28 @@ impl XdgDecorationHandler for State {
.find(|(window, _)| window.wl_surface().as_deref() == Some(toplevel.wl_surface()))
&& let Some(toplevel) = window.0.toplevel()
{
let mode = if mapped.is_stack() {
XdgMode::ServerSide
} else {
XdgMode::from_preference(self.default_decoration())
};
PreferredDecorationMode::update(&window.0, None);
toplevel.with_pending_state(|state| {
state.decoration_mode = None;
state.decoration_mode = Some(mode);
});
toplevel.send_configure();
} else {
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
});
if let Some(pending) = shell
.pending_windows
.iter()
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
{
PreferredDecorationMode::update(&pending.surface.0, None);
}
}
}
}
@ -129,10 +224,10 @@ impl KdeDecorationHandler for State {
if mapped.is_stack() {
KdeMode::Server
} else {
KdeMode::Client
KdeMode::from_preference(self.default_decoration())
}
} else {
KdeMode::Client
KdeMode::from_preference(self.default_decoration())
};
with_states(surface, |states| {

View file

@ -40,7 +40,7 @@ impl DmabufHandler for State {
fn new_surface_feedback(
&mut self,
surface: &WlSurface,
global: &DmabufGlobal,
_global: &DmabufGlobal,
) -> Option<DmabufFeedback> {
let BackendData::Kms(kms) = &self.backend else {
return None;
@ -57,18 +57,7 @@ impl DmabufHandler for State {
f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all())
});
let node = kms
.drm_devices
.values()
.find(|device| {
device
.socket
.as_ref()
.map(|s| &s.dmabuf_global == global)
.unwrap_or(false)
})?
.inner
.render_node;
let node = kms.primary_node.read().unwrap().unwrap();
let kms_surface = kms
.drm_devices
.values()
@ -77,9 +66,7 @@ impl DmabufHandler for State {
Some(with_states(surface, |data| {
if is_fullscreen {
feedback
.primary_scanout_feedback
.unwrap_or(feedback.render_feedback)
feedback.primary_scanout_feedback
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
feedback
.overlay_scanout_feedback

View file

@ -49,7 +49,10 @@ impl FractionalScaleHandler for State {
with_states(&surface, |states| {
with_fractional_scale(states, |fractional_scale| {
fractional_scale.set_preferred_scale(output.current_scale().fractional_scale());
// The 1.0 clamp is a workaround for Chromium
// TODO: remove if Chromium ever gets fixed
fractional_scale
.set_preferred_scale(output.current_scale().fractional_scale().max(1.0));
});
});
}

View file

@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::input::keyboard::KeyboardHandle;
use crate::state::State;
use crate::wayland::protocols::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState};
@ -7,4 +9,8 @@ impl KeyboardLayoutHandler for State {
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState {
&mut self.common.keyboard_layout_state
}
fn group_changed(&mut self, keyboard: &KeyboardHandle<Self>) {
self.broadcast_ei_keyboard_modifiers(keyboard);
}
}

View file

@ -230,6 +230,16 @@ impl State {
state.common.output_configuration_state.update();
});
// Output scale or geometry may have changed. EI absolute-pointer regions
// are immutable per device, so any connected EI client (e.g. an RDP server)
// keeps mapping with the old scale until it reconnects. Recreate the
// device with the updated region so the mapping tracks the change live.
// (drop the backend lock first: refresh borrows `self` immutably.)
drop(backend);
if !test_only {
crate::libei::refresh_absolute_pointer_regions(self);
}
true
}
}

View file

@ -5,7 +5,10 @@ use smithay::{
input::pointer::PointerHandle,
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Logical, Point},
wayland::{pointer_constraints::PointerConstraintsHandler, seat::WaylandFocus},
wayland::{
pointer_constraints::{ConstraintRemove, PointerConstraintsHandler},
seat::WaylandFocus,
},
};
pub use smithay::wayland::pointer_constraints::{PointerConstraintRef, with_pointer_constraint};
@ -76,23 +79,37 @@ impl PointerConstraintsHandler for State {
}
}
fn remove_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
if with_pointer_constraint(surface, pointer, |constraint| constraint.is_none()) {
let seat = self
.common
.shell
.read()
.seats
.iter()
.find(|s| s.get_pointer().as_ref() == Some(pointer))
.cloned();
fn remove_constraint(
&mut self,
surface: &WlSurface,
pointer: &PointerHandle<Self>,
constraint_remove: ConstraintRemove,
) {
match constraint_remove {
ConstraintRemove::PointerLeave(_) => {
// If the constraint was broken by the pointer forcibly leaving the surface, then it doesn't
// make much sense to warp it.
}
ConstraintRemove::Destroyed(constraint) => {
let Some(seat) = self
.common
.shell
.read()
.seats
.iter()
.find(|s| s.get_pointer().as_ref() == Some(pointer))
.cloned()
else {
return;
};
let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint() else {
return;
};
if let Some(seat) = seat
&& let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint()
&& hint_surface == *surface
{
self.apply_cursor_hint(surface, pointer, hint_location);
seat.set_pointer_constraint_hint(None);
if hint_surface == *surface {
self.apply_cursor_hint(surface, pointer, hint_location, Some(&constraint));
seat.set_pointer_constraint_hint(None);
}
}
}
}

View file

@ -38,7 +38,7 @@ impl PointerWarpHandler for State {
drop(shell);
if let Some(pointer_handle) = pointer_handle {
self.apply_cursor_hint(&surface, &pointer_handle, pos);
self.apply_cursor_hint(&surface, &pointer_handle, pos, None);
}
}
}

View file

@ -46,6 +46,11 @@ impl SessionLockHandler for State {
let mut shell = self.common.shell.write();
shell.session_lock = None;
let seats = shell.seats.iter().cloned().collect::<Vec<_>>();
for seat in &seats {
self.common.idle_notifier_state.notify_activity(seat);
}
for output in shell.outputs() {
self.backend.schedule_render(output);
}

View file

@ -1,12 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::State;
use crate::{shell::focus::target::PointerFocusTarget, state::State};
use smithay::{
backend::input::TabletToolDescriptor, input::pointer::CursorImageStatus,
wayland::tablet_manager::TabletSeatHandler,
input::tablet::TabletSeatHandler,
};
impl TabletSeatHandler for State {
type ToolFocus = PointerFocusTarget;
fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, _image: CursorImageStatus) {
// TODO display cursor for each tablet tool
}

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::{
backend::input::InputTime,
desktop::{WindowSurfaceType, layer_map_for_output},
input::{Seat, pointer::MotionEvent},
output::Output,
@ -124,7 +125,7 @@ impl ToplevelManagementHandler for State {
&MotionEvent {
location: new_pos.to_f64().as_logical(),
serial,
time: self.common.clock.now().as_millis(),
time: InputTime::now(),
},
);
ptr.frame(self);

View file

@ -243,7 +243,7 @@ impl State {
{
for mapped in current_workspace
.mapped()
.filter(|m| m.maximized_state.lock().unwrap().is_some())
.filter(|m| m.maximized_state.lock().unwrap().is_some() && *m != &element)
.cloned()
.collect::<Vec<_>>()
.into_iter()

View file

@ -1,11 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
shell::{CosmicSurface, PendingWindow, focus::target::KeyboardFocusTarget, grabs::ReleaseMode},
shell::{
CosmicSurface, PendingWindow,
focus::target::KeyboardFocusTarget,
grabs::{GrabType, ReleaseMode},
},
utils::prelude::*,
};
use smithay::desktop::layer_map_for_output;
use smithay::{
backend::input::InputTime,
desktop::{
PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy,
WindowSurfaceType, find_popup_root_surface,
@ -26,6 +30,7 @@ use smithay::{
},
},
};
use smithay::{desktop::layer_map_for_output, input::tablet::TabletSeatTrait};
use std::cell::Cell;
use tracing::warn;
@ -50,6 +55,7 @@ impl XdgShellHandler for State {
surface,
seat,
fullscreen: None,
minimized: false,
maximized: false,
sticky: false,
})
@ -189,12 +195,17 @@ impl XdgShellHandler for State {
true,
) {
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(self, grab, serial);
} else {
seat.get_pointer()
match grab.grab_type() {
GrabType::Touch => seat.get_touch().unwrap().set_grab(self, grab, serial),
GrabType::Pointer => seat
.get_pointer()
.unwrap()
.set_grab(self, grab, serial, focus)
.set_grab(self, grab, serial, focus),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(self, grab, InputTime::now(), serial, focus),
}
}
}
@ -217,12 +228,17 @@ impl XdgShellHandler for State {
true,
) {
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(self, grab, serial)
} else {
seat.get_pointer()
match grab.grab_type() {
GrabType::Touch => seat.get_touch().unwrap().set_grab(self, grab, serial),
GrabType::Pointer => seat
.get_pointer()
.unwrap()
.set_grab(self, grab, serial, focus)
.set_grab(self, grab, serial, focus),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(self, grab, InputTime::now(), serial, focus),
}
}
}
@ -333,17 +349,15 @@ impl XdgShellHandler for State {
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;
}
}
} else if let Some(target) = grab.current_grab()
&& let Some(wl_surface) = target.wl_surface()
&& (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 {

View file

@ -5,17 +5,19 @@ use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v
use cosmic_protocols::corner_radius::v1::server::{
cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1,
};
use smithay::desktop::utils::bbox_from_surface_tree;
use smithay::backend::renderer::buffer_dimensions;
use smithay::backend::renderer::utils::RendererSurfaceStateUserData;
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::utils::{HookId, Logical, Rectangle};
use smithay::wayland::compositor::add_pre_commit_hook;
use smithay::wayland::compositor::with_states;
use smithay::wayland::compositor::{BufferAssignment, Cacheable, SurfaceAttributes};
use smithay::wayland::shell::wlr_layer::WlrLayerShellHandler;
use smithay::wayland::shell::xdg::{SurfaceCachedState, XdgShellSurfaceUserData};
use smithay::wayland::viewporter::ViewportCachedState;
use smithay::{
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
@ -190,7 +192,7 @@ where
let radius_exists = with_states(surface.wl_surface(), |surface_data| {
let hook_id = surface_data
.data_map
.get_or_insert_threadsafe(|| ToplevelHookId::new(None));
.get_or_insert_threadsafe(|| LayerHookId::new(None));
let guard = hook_id.lock().unwrap();
guard.as_ref().map(|(_, t)| t.upgrade().is_ok())
});
@ -655,7 +657,6 @@ fn pad_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
@ -665,8 +666,40 @@ fn layer_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &
.cached_state
.get::<CacheablePadding>()
.pending();
// The wl_surface size after this commit is:
// - viewport destination if set
// - else the logical size of the buffer that will be current attached now
// - else existing
let size = surface_data
.cached_state
.get::<ViewportCachedState>()
.pending()
.size()
.or_else(|| {
let mut guard = surface_data.cached_state.get::<SurfaceAttributes>();
let attrs = guard.pending();
match &attrs.buffer {
Some(BufferAssignment::NewBuffer(buffer)) => buffer_dimensions(buffer)
.map(|d| d.to_logical(attrs.buffer_scale, attrs.buffer_transform.into())),
_ => None,
}
})
.or_else(|| {
surface_data
.data_map
.get::<RendererSurfaceStateUserData>()
.and_then(|state| state.lock().unwrap().surface_size())
});
let Some(size) = size else {
return;
};
let empty = Padding::default();
let Some(padded_box) = pad_rect(bbox, padding.0.as_ref().unwrap_or(&empty)) else {
let Some(padded_box) = pad_rect(
Rectangle::from_size(size),
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()

View file

@ -38,7 +38,11 @@ use smithay::{
};
use tracing::trace;
use std::{convert::TryFrom, path::PathBuf, sync::Arc};
use std::{
convert::TryFrom,
path::PathBuf,
sync::{Arc, Mutex},
};
pub enum ImportError {
Failed,
@ -52,19 +56,28 @@ pub trait DrmHandler<R: 'static> {
}
}
#[derive(Debug, Default)]
pub struct WlDrmState<R>(std::marker::PhantomData<R>);
#[derive(Debug)]
pub struct WlDrmState<R> {
device: Arc<Mutex<DrmGlobalDeviceData>>,
global: GlobalId,
_marker: std::marker::PhantomData<R>,
}
#[derive(Debug)]
pub struct DrmGlobalDeviceData {
formats: Vec<Fourcc>,
path: PathBuf,
}
/// Data associated with a drm global.
pub struct DrmGlobalData {
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
formats: Arc<Vec<Fourcc>>,
device_path: PathBuf,
dmabuf_global: DmabufGlobal,
device: Arc<Mutex<DrmGlobalDeviceData>>,
}
pub struct DrmInstanceData {
formats: Arc<Vec<Fourcc>>,
device: Arc<Mutex<DrmGlobalDeviceData>>,
dmabuf_global: DmabufGlobal,
}
@ -86,16 +99,24 @@ where
data_init: &mut DataInit<'_, D>,
) {
let data = DrmInstanceData {
formats: global_data.formats.clone(),
device: global_data.device.clone(),
dmabuf_global: global_data.dmabuf_global,
};
let drm_instance = data_init.init(resource, data);
drm_instance.device(global_data.device_path.to_string_lossy().into_owned());
drm_instance.device(
global_data
.device
.lock()
.unwrap()
.path
.to_string_lossy()
.into_owned(),
);
if drm_instance.version() >= 2 {
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
}
for format in global_data.formats.iter() {
for format in global_data.device.lock().unwrap().formats.iter() {
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
drm_instance.format(converted as u32);
}
@ -148,7 +169,7 @@ where
} => {
let format = match Fourcc::try_from(format) {
Ok(format) => {
if !data.formats.contains(&format) {
if !data.device.lock().unwrap().formats.contains(&format) {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("Format not advertised by wl_drm"),
@ -220,13 +241,12 @@ where
}
impl<R: 'static> WlDrmState<R> {
pub fn create_global<D>(
&mut self,
pub fn new<D>(
display: &DisplayHandle,
device_path: PathBuf,
formats: FormatSet,
dmabuf_global: &DmabufGlobal,
) -> GlobalId
) -> Self
where
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
@ -234,19 +254,16 @@ impl<R: 'static> WlDrmState<R> {
+ DmabufHandler
+ 'static,
{
self.create_global_with_filter::<D, _>(display, device_path, formats, dmabuf_global, |_| {
true
})
Self::new_with_filter::<D, _>(display, device_path, formats, dmabuf_global, |_| true)
}
pub fn create_global_with_filter<D, F>(
&mut self,
pub fn new_with_filter<D, F>(
display: &DisplayHandle,
device_path: PathBuf,
formats: FormatSet,
dmabuf_global: &DmabufGlobal,
client_filter: F,
) -> GlobalId
) -> Self
where
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
@ -255,21 +272,45 @@ impl<R: 'static> WlDrmState<R> {
+ 'static,
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
{
let formats = Arc::new(
formats
let device = Arc::new(Mutex::new(DrmGlobalDeviceData {
formats: formats
.into_iter()
.filter(|f| f.modifier == Modifier::Invalid)
.map(|f| f.code)
.collect(),
);
path: device_path,
}));
let data = DrmGlobalData {
filter: Box::new(client_filter),
formats,
device_path,
device: device.clone(),
dmabuf_global: *dmabuf_global,
};
display.create_global::<D, wl_drm::WlDrm, _>(2, data)
let global = display.create_global::<D, wl_drm::WlDrm, _>(2, data);
WlDrmState {
device,
global,
_marker: Default::default(),
}
}
pub fn update_device(&mut self, device_path: PathBuf, formats: FormatSet) {
let new_device = DrmGlobalDeviceData {
formats: formats
.into_iter()
.filter(|f| f.modifier == Modifier::Invalid)
.map(|f| f.code)
.collect(),
path: device_path,
};
*self.device.lock().unwrap() = new_device;
}
pub fn global(&self) -> &GlobalId {
&self.global
}
}

View file

@ -17,8 +17,10 @@ use smithay::{
use std::mem;
use wayland_backend::server::{ClientId, GlobalId};
pub trait KeyboardLayoutHandler {
pub trait KeyboardLayoutHandler: SeatHandler {
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState;
/// Group has been changed through the protocol
fn group_changed(&mut self, keyboard: &KeyboardHandle<Self>);
}
#[derive(Debug)]
@ -57,6 +59,7 @@ impl KeyboardLayoutState {
let active_layout = handle.with_xkb_state(state, |context| {
context.xkb().lock().unwrap().active_layout()
});
state.group_changed(handle);
if *last_layout != Some(active_layout) {
keyboard_layout.group(active_layout.0);
*last_layout = Some(active_layout);

View file

@ -8,6 +8,7 @@ pub mod keyboard_layout;
pub mod output_configuration;
pub mod output_power;
pub mod overlap_notify;
pub mod session_lock_layer;
pub mod toplevel_info;
pub mod toplevel_management;
pub mod workspace;

View file

@ -1,9 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use calloop::{
LoopHandle,
timer::{TimeoutAction, Timer},
};
use calloop::LoopHandle;
use cosmic_comp_config::output::comp::AdaptiveSync;
use cosmic_protocols::output_management::v1::server::{
zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1,
@ -30,7 +27,7 @@ use smithay::{
utils::{Logical, Physical, Point, Size, Transform},
wayland::output::WlOutputData,
};
use std::{convert::TryFrom, sync::Mutex, time::Duration};
use std::{convert::TryFrom, sync::Mutex};
mod handlers;
@ -519,26 +516,6 @@ where
}
}
fn remove_global_with_timer<D: 'static>(
dh: &DisplayHandle,
event_loop_handle: &LoopHandle<D>,
id: GlobalId,
) {
dh.disable_global::<D>(id.clone());
let source = Timer::from_duration(Duration::from_secs(5));
let dh = dh.clone();
let res = event_loop_handle.insert_source(source, move |_, _, _state| {
dh.remove_global::<D>(id.clone());
TimeoutAction::Drop
});
if let Err(err) = res {
tracing::error!(
"failed to insert timer source to destroy output global: {}",
err
);
}
}
macro_rules! delegate_output_configuration {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
@ -578,4 +555,4 @@ macro_rules! delegate_output_configuration {
}
pub(crate) use delegate_output_configuration;
use crate::utils::prelude::OutputExt;
use crate::utils::{global::remove_global_with_timer, prelude::OutputExt};

View file

@ -0,0 +1,136 @@
use cosmic_protocols::session_lock_layer::v1::server::cosmic_session_lock_layer_manager_v1;
use smithay::{
reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, backend::GlobalId,
protocol::wl_surface,
},
wayland::{
Dispatch2, GlobalDispatch2,
compositor::{Cacheable, with_states},
shell::wlr_layer::WlrLayerShellHandler,
},
};
struct SessionLockLayerData;
#[derive(Debug)]
pub struct SessionLockLayerState {
global: GlobalId,
}
impl SessionLockLayerState {
pub fn new<D, F>(dh: &DisplayHandle, client_filter: F) -> Self
where
F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static,
D: GlobalDispatch<
cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
SessionLockLayerGlobalData,
> + 'static,
{
let global = dh
.create_global::<D, cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, _>(
1,
SessionLockLayerGlobalData {
filter: Box::new(client_filter.clone()),
},
);
Self { global }
}
pub fn global_id(&self) -> GlobalId {
self.global.clone()
}
}
#[doc(hidden)]
pub struct SessionLockLayerGlobalData {
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
}
impl<D> GlobalDispatch2<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, D>
for SessionLockLayerGlobalData
where
D: Dispatch<
cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
SessionLockLayerData,
>,
{
fn bind(
&self,
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: New<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1>,
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, SessionLockLayerData);
}
fn can_view(&self, client: &Client) -> bool {
(self.filter)(client)
}
}
#[derive(Clone, Copy, Default)]
struct SessionLockLayerSurfaceData {
show_on_lock: bool,
}
impl Cacheable for SessionLockLayerSurfaceData {
fn commit(&mut self, _dh: &DisplayHandle) -> Self {
*self
}
fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
*into = self;
}
}
pub fn layer_show_on_lock(wl_surface: &wl_surface::WlSurface) -> bool {
with_states(wl_surface, |states| {
if states.cached_state.has::<SessionLockLayerSurfaceData>() {
let mut state = states.cached_state.get::<SessionLockLayerSurfaceData>();
state.current().show_on_lock
} else {
false
}
})
}
impl<D> Dispatch2<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, D>
for SessionLockLayerData
where
D: WlrLayerShellHandler,
{
fn request(
&self,
state: &mut D,
_: &Client,
_: &cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
request: cosmic_session_lock_layer_manager_v1::Request,
_: &DisplayHandle,
_: &mut DataInit<'_, D>,
) {
let (layer, value) = match request {
cosmic_session_lock_layer_manager_v1::Request::SetShowOnLock { layer } => (layer, true),
cosmic_session_lock_layer_manager_v1::Request::UnsetShowOnLock { layer } => {
(layer, false)
}
cosmic_session_lock_layer_manager_v1::Request::Destroy => {
return;
}
_ => unreachable!(),
};
if let Some(layer) = state
.shell_state()
.layer_surfaces()
.find(|surface| surface.shell_surface() == &layer)
{
with_states(layer.wl_surface(), |states| {
let mut state = states.cached_state.get::<SessionLockLayerSurfaceData>();
state.pending().show_on_lock = value;
});
}
}
}

View file

@ -418,6 +418,12 @@ where
handle.closed();
}
}
// Remove the foreign handle too, so new subscribers cannot
// discover a toplevel whose window has already been destroyed.
if let Some(handle) = state.foreign_handle.take() {
self.foreign_toplevel_list.remove_toplevel(&handle);
}
*state = Default::default();
dirty = true;
false
}

View file

@ -9,7 +9,9 @@ use std::{
use crate::{
backend::render::cursor::{Cursor, load_cursor_env, load_cursor_theme},
shell::{
CosmicSurface, PendingWindow, Shell, focus::target::KeyboardFocusTarget, grabs::ReleaseMode,
CosmicSurface, PendingWindow, Shell,
focus::target::KeyboardFocusTarget,
grabs::{GrabType, ReleaseMode},
},
state::State,
utils::prelude::*,
@ -20,7 +22,7 @@ use smithay::{
backend::{
allocator::Fourcc,
drm::DrmNode,
input::{ButtonState, KeyState, Keycode},
input::{ButtonState, InputTime, KeyState, Keycode},
renderer::{
Bind, Frame, Offscreen, Renderer,
element::{
@ -32,7 +34,7 @@ use smithay::{
},
},
desktop::space::SpaceElement,
input::{keyboard::ModifiersState, pointer::CursorIcon},
input::{keyboard::ModifiersState, pointer::CursorIcon, tablet::TabletSeatTrait},
reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
utils::{
Buffer as BufferCoords, Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size, Transform,
@ -115,7 +117,7 @@ impl State {
&self.common.display_handle,
None,
std::iter::empty::<(OsString, OsString)>(),
std::iter::empty::<OsString>(),
["-enable-ei-portal"],
true,
Stdio::null(),
Stdio::null(),
@ -394,8 +396,9 @@ impl Common {
sym: Keysym,
code: Keycode,
state: KeyState,
modifiers: ModifiersState,
serial: Serial,
time: u32,
time: InputTime,
) {
let config = self.config.cosmic_conf.xwayland_eavesdropping.keyboard;
if config == EavesdroppingKeyboardMode::None {
@ -418,7 +421,6 @@ impl Common {
.last_active()
.get_keyboard()
.unwrap();
let modifiers = keyboard.modifier_state();
let is_modifier = sym.is_modifier_key();
let xstate = self.xwayland_state.as_mut().unwrap();
@ -458,7 +460,7 @@ impl Common {
tracing::trace!("Forwaring key {} {:?} to xwayland", code.raw() - 8, state);
for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
wl_keyboard.key(serial.into(), time, code.raw() - 8, state.into());
wl_keyboard.key(serial.into(), time.millis(), code.raw() - 8, state.into());
if xstate.last_modifier_state != Some(modifiers) {
xstate.last_modifier_state = Some(modifiers);
wl_keyboard.modifiers(
@ -478,7 +480,7 @@ impl Common {
button: u32,
state: ButtonState,
serial: Serial,
time: u32,
time: InputTime,
) {
if !self.config.cosmic_conf.xwayland_eavesdropping.pointer {
return;
@ -518,7 +520,7 @@ impl Common {
tracing::trace!("Forwaring ptr button {} {:?} to Xwayland", button, state);
for wl_pointer in pointer.client_pointers(&xstate.client) {
wl_pointer.button(serial.into(), time, button, state.into());
wl_pointer.button(serial.into(), time.millis(), button, state.into());
}
}
@ -808,6 +810,7 @@ impl XwmHandler for State {
);
}
let fullscreen = window.is_fullscreen().then(|| seat.active_output());
let minimized = window.is_hidden();
let maximized = window.is_maximized();
if let Some(pending) = shell
.pending_windows
@ -816,6 +819,7 @@ impl XwmHandler for State {
{
pending.seat = seat;
pending.fullscreen = fullscreen;
pending.minimized = minimized;
pending.maximized = maximized;
} else {
let surface = CosmicSurface::from(window);
@ -823,6 +827,7 @@ impl XwmHandler for State {
surface,
seat,
fullscreen,
minimized,
maximized,
sticky: false,
})
@ -858,7 +863,7 @@ impl XwmHandler for State {
if let Some(target) = res {
let seat = shell.seats.last_active().clone();
std::mem::drop(shell);
Shell::set_focus(self, Some(&target), &seat, None, false);
Shell::set_focus_on_x11_map(self, &target, &seat, false);
}
}
}
@ -1042,17 +1047,29 @@ impl XwmHandler for State {
true,
) {
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch()
.unwrap()
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
} else {
seat.get_pointer().unwrap().set_grab(
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch()
.unwrap()
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
}
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
self,
grab,
SERIAL_COUNTER.next_serial(),
focus,
)
),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(
self,
grab,
InputTime::now(),
SERIAL_COUNTER.next_serial(),
focus,
),
}
}
}
@ -1073,17 +1090,29 @@ impl XwmHandler for State {
true,
) {
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch()
.unwrap()
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
} else {
seat.get_pointer().unwrap().set_grab(
match grab.grab_type() {
GrabType::Touch => {
seat.get_touch()
.unwrap()
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
}
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
self,
grab,
SERIAL_COUNTER.next_serial(),
focus,
)
),
GrabType::TabletTool => seat
.tablet_seat()
.get_tool(grab.tool().unwrap())
.unwrap()
.set_grab(
self,
grab,
InputTime::now(),
SERIAL_COUNTER.next_serial(),
focus,
),
}
}
}