Compare commits

..

65 commits

Author SHA1 Message Date
e65b97abb8 chore: restore rust-toolchain 1.93 + refresh lockfile post-rebase
Leyoda 2026 – GPLv3
2026-07-03 15:56:20 +02:00
9141780b30 fix: absorb libcosmic theme-v2 API (private containers, text::Style, wgpu 28)
Leyoda 2026 – GPLv3
2026-07-03 15:51:55 +02:00
58b0598de8 chore: use local glyphon lockfile 2026-07-03 15:51:55 +02:00
7d24cd2e6a chore: use local cosmic-text checkout 2026-07-03 15:51:55 +02:00
6e72e4c171 yoda: refresh local dbus binding lock 2026-07-03 15:51:55 +02:00
a005fddefe fix: keep tokio runtime available for compositor 2026-07-03 15:51:55 +02:00
a5c0d7a17f chore: align compositor with local cosmic stack 2026-07-03 15:51:55 +02:00
Hojjat Abdollahi
9d52653d5c
revert: "fix: follow the focus after alt+tab to another output"
This reverts commit 56f84fba2d.
2026-07-02 17:53:03 +02:00
Alexander Daichendt
f5e0ba1594 make device DrmDevice in disable_crts 2026-07-01 21:18:43 +02:00
Alexander Daichendt
a9f2427c00 fix: internal laptop screen stuck on vendor logo with external monitor 2026-07-01 21:18:43 +02:00
Ashley Wulber
dacdc3db39 chore: workaround for Tiger Lake 2026-07-01 13:30:57 -04:00
Tom Grushka
c5775d2010 change update_focal_point to use f64 to avoid rounding errors 2026-07-01 17:56:29 +02:00
Victoria Brekenfeld
bb584aab7f backend: Don't attempt dma-copies from newer intel chips 2026-06-30 18:39:43 +02:00
Victoria Brekenfeld
ccc4f36065 chore: update smithay 2026-06-30 18:37:42 +02:00
Frederic Laing
650768211f fix: keep windows visible until overview commits a buffer 2026-06-29 15:03:40 +02:00
Ilia Malanin
aac1e19f08 fix: set_rectangle leak 2026-06-18 13:37:14 -04:00
Victoria Brekenfeld
f032e7dbd5 shell/focus: Order sticky windows before fullscreen windows 2026-06-18 18:41:27 +02:00
Victoria Brekenfeld
106b50293b wayland/activation: Fix requests without active outputs 2026-06-18 18:29:37 +02:00
Skygrango
296b6bee23 wayland: reject cursor hint requests that are outside the surface bound 2026-06-16 14:49:28 -06:00
Skygrango
b6cab38528 wayland: implement pointer_warp_v1 2026-06-16 14:49:28 -06:00
Anthony T. Lannutti
821b431973 feat: separate logind feature from systemd
Move logind-zbus to a dedicated 'logind' feature that is independent
of the 'systemd' feature. This allows non-systemd users (e.g., OpenRC
with elogind) to access lid switch inhibition and lid status detection
without requiring the full systemd stack.

The 'systemd' feature now depends on 'logind' to maintain backward
compatibility, so existing users are unaffected.

Feature configuration:
- default: ["systemd"]
- logind: ["logind-zbus"]
- systemd: ["libsystemd", "logind", "tracing-journald"]

Resolves #2473

Coding-Agent: OpenCode
Model: claude-sonnet-4-5
2026-06-16 14:41:51 -06:00
Richard Chennault
dddf51b441
kms: skip lease resume when drm device activation fails in resume_session()
When smithay's DrmDevice::activate() returns an error (e.g. because
drmSetMaster() failed during VT resume), resume_session() was only
logging the error and then proceeding to resume DRM leases on a device
that has no master. This leaves the compositor in a broken render state
where every atomic page flip returns EPERM.

Fix: continue to the next device on activate() failure. The device
surface stays inactive and lease resume is deferred. On the next
ActivateSession event (next VT switch to this TTY), resume_session()
will be called again and activate() will succeed once the seat manager
has granted DRM master.

Reproducer: hybrid GPU system (Intel iGPU render + NVIDIA display),
VT switch away from and back to COSMIC session. On return, drmSetMaster
races the seat notification; activate() fails; without this fix
the compositor floods journald with EPERM at 60fps.

Fixes: pop-os#2331, pop-os#2302
2026-06-16 14:41:01 -06:00
Sai Asish Y
aff506bb7b fix(floating): allow remapping minimized windows
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
2026-06-16 14:39:53 -06:00
SAY-5
69bb35e336 floating: don't panic if window disappears mid-unminimize
Signed-off-by: SAY-5 <say.apm35@gmail.com>
2026-06-16 14:39:53 -06:00
Victoria Brekenfeld
836ccbaa0b shell/stack: Cleanup previous_index on remove 2026-06-16 13:05:39 -04:00
Gavin John
d4c5714c10 perf(toplevel_info): optimize send_toplevel_to_client
PR #1279 tried to throttle `send_toplevel_to_client`, which was rejected. This takes (what I think is) a better approach, and instead makes some optimizations. We delay grabbing the mutex until it's actually needed, and we also checks to see if the state actually needs to be resent. This resolves some lock contention, and also avoids having to generate the state in the first place. Since `wayland-rs` uses a single mutex to lock the entire state, this both massively reduces lock contention and avoids making (apparently quite a lot of) unnecessary computation.

I decided to give this one to AI since I'm a bit busy right now (although I am still manually writing the commit messages and descriptions). I think it generally did a pretty good job, although I did have to make a few manual tweaks. While I don't have any empirical data, cosmic-comp idle CPU has gone down from ~3% all-core to 0-0.5%, even stress testing it with far more windows open. I can't say it *liked* opening 30-some windows in 2 seconds, but after it finished opening them all, it was still buttery smooth. Overall very happy with the results.

Assisted-by: Claude:claude-4-opus
2026-06-16 18:09:28 +02:00
Robin Nehls
db0b1afeb5 feat: allow naming pinned workspaces 2026-06-15 23:11:31 +02:00
Hojjat
8f5f7c897d fix: add git hash to .cargo/config when vendoring 2026-06-15 12:16:32 -06:00
Hojjat
e576a87f5b Revert "fix: use SOURCE_GIT_HASH when building for pop ci"
This reverts commit 94e9437c4e.
2026-06-15 12:16:32 -06:00
Joseph Buckingham
52b3f930a8 fix: debug build pinning panic 2026-06-12 11:45:59 -06:00
Ilia Malanin
31f317d16b fix: skip dead windows in floating set_output 2026-06-12 13:08:14 +02:00
Michael Aaron Murphy
f95b62635d feat(tiling-exceptions): add exception for Thunderbird message compose dialog 2026-06-11 09:23:38 -06:00
Michael Aaron Murphy
5ca8cc2cdf feat(tiling-exceptions): add exception for Slack huddle preview 2026-06-11 09:23:38 -06:00
Hojjat
0312f9a201 fix: draw the focused window border for the current window only 2026-06-09 23:21:26 -04:00
Ian Douglas Scott
5153d497e7 fix(dbus): Block on creation of zbus connection to avoid deadlock
A fix for the issue reported in
https://github.com/pop-os/cosmic-comp/pull/2450.

Using `block_on` in some places for zbus calls is fine (that's what
zbus's blocking API does; it has its own executor for background tasks),
but this is problematic with `async_once_cell`. If we also use that in
the calloop async executor.

I think ideally we should avoid blocking the main thread on any async
tasks, but for now we can just block in initialization here.
2026-06-09 18:09:36 -04:00
Hojjat
94e9437c4e fix: use SOURCE_GIT_HASH when building for pop ci 2026-06-09 18:07:07 -04:00
Ian Douglas Scott
651877e622 image-copy: Adjust cursor position for subsurface offset
The `themed_window` example of `smithay-client-toolkit` is a good test
for this.
2026-06-09 10:42:48 -07:00
Ian Douglas Scott
84d2a9d297 shell: Fix definition of surface_tree_offset()
Using `with_surface_tree_downward()` is wrong because it will traverse
other branches of the surface tree than the one containing `surface`,
and add their offsets.

It's simple enough to instead walk up the surface tree.
2026-06-09 10:42:48 -07:00
Ian Douglas Scott
7d0d374fd8 image-copy: Adjust toplevel capture coordinate for window geometry 2026-06-09 10:42:48 -07:00
Ian Douglas Scott
7a50625e78 image-copy: Fix cursor pos for toplevels with SSD 2026-06-09 10:42:48 -07:00
Ian Douglas Scott
8aea6cc158 image-copy: Function for duplicated toplevel cursor enter/motion code
Deduplicate this before making it more complicated.

Also allows early returns to avoid deep nesting. And avoid duplicating
calculations here for multiple sessions (though normally there won't be
multiple capture sessions of same toplevel with cursor metadata,
probably).
2026-06-09 10:42:48 -07:00
Ian Douglas Scott
82dcd9a46a image-copy: Don't send 0x0 buffer params for cursor capture
Fixes panics in `xdg-desktop-portal-cosmic` when it tries to use cursor
capture, and an application hides the cursor.
2026-06-09 10:42:48 -07:00
Ian Douglas Scott
4a8931896f image-copy: Subtract output position for pointer position sent to client 2026-06-09 10:42:48 -07:00
Ian Douglas Scott
b6b76e1d4a image-copy: Don't send redundantly pointer changes on fullscreen
The code for sending pointer position changes for toplevels should
handle this adequately; no need to also handle it here.
2026-06-09 10:42:48 -07:00
Hojjat
51dd3bc66f fix: pass the full window size to xwayland 2026-06-09 10:42:22 -07:00
Ian Douglas Scott
4eaaf4f55c dbus: Refactor to share DBus connections
We should avoid creating more than one session connection or more than
one system connection. We should also ideally avoid blocking the main
thread.

For now this still uses blocking in a couple places, but wrapping async
code (which is how `zbus::blocking` is implemented anyway).

This moves the `NameOwners` creation out of `A11yKeyboardMonitorState`,
so it can be shared with other things. We will likely want that for
https://github.com/pop-os/cosmic-comp/pull/465 to define a secured
protocol to pass an fd to the portal, and potentially any other
sensitive DBus protocols implemented by the compositor.
2026-06-08 19:03:50 +02:00
Ian Douglas Scott
c8d9ff1215 dbus: Remove unused return value from dbus::init
If we don't use this anyway, this can make things simpler.
2026-06-08 19:03:50 +02:00
Ian Douglas Scott
571565c28e dbus: Use calloop async executor
New version of callop has a fix for this, so we're no longer blocked
from using it.
2026-06-08 19:03:50 +02:00
Ian Douglas Scott
3c834e9c85 dbus: Use FutureSource instead of a channel 2026-06-08 19:03:50 +02:00
Robin Nehls
2f7c34f29a feat: add config option for xdg activation behavior 2026-06-03 08:09:29 +02:00
Hojjat
56f84fba2d fix: follow the focus after alt+tab to another output 2026-06-02 12:14:39 -06:00
Hojjat Abdollahi
28ef6bdbc8
feat: support multiple fullscreen windows per workspace 2026-06-02 17:48:33 +02:00
Ian Douglas Scott
9f28a764a7 chore: update smithay
Includes the fix in https://github.com/Smithay/smithay/pull/2038,
which helps addresses a regression in controller input for Steam games.
2026-06-01 07:52:42 -07:00
Skygrango
203cad8cbd shell: disable WindowUI detection if constraint is active 2026-06-01 07:52:42 -07:00
Skygrango
208c2128cd wayland: implement new_constraint, remove_constraint and cursor_position_hint for PointerConstraintsHandler
new_constraint: check if the surface has pointer focus, keyboard focus, and if the pointer is within toplevel geometry or constraint region. If these conditions are met, then enable the constraint.

remove_constraint: apply last cursor position hint that saved in seat.

cursor_position_hint: save the cursor position hints provided by the client to the seat.
2026-06-01 07:52:42 -07:00
Skygrango
4fb7d49316 chore: update smithay 2026-06-01 07:52:42 -07:00
Skygrango
81066d437a shell: remove constraint when target changed 2026-06-01 07:52:42 -07:00
Skygrango
9702aae523 input: when updating pointer in the confined mode, recalibrate to the valid coordinates 2026-06-01 07:52:42 -07:00
Skygrango
a533ac1ec3 input: implement apply_cursor_hint 2026-06-01 07:52:42 -07:00
Skygrango
e75c9c0c48 shell: implement surface_geometry_offset_from_toplevel for WorkspaceSet 2026-06-01 07:52:42 -07:00
Skygrango
d829ec3815 shell: implement has_surface for KeyboardFocusTarget 2026-06-01 07:52:42 -07:00
Skygrango
32b54cc31f shell: implement surface_offset and surface_tree_offset 2026-06-01 07:52:42 -07:00
devpa
22fe419747 input: allow layout switching and brightness/volume control under session lock 2026-05-27 16:43:39 +02:00
Victoria Brekenfeld
cefe8472b2
Merge pull request #2411 from weblate/weblate-pop-os-cosmic-comp 2026-05-26 13:08:07 +02:00
Hosted Weblate
8c309d5231
i18n: translation updates from weblate
Co-authored-by: therealmate <hellogaming91@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/hu/
Translation: Pop OS/COSMIC Comp
2026-05-25 23:11:40 +00:00
54 changed files with 2398 additions and 1611 deletions

838
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -12,13 +12,12 @@ members = ["cosmic-comp-config"]
[dependencies]
anyhow = { version = "1.0.102", features = ["backtrace"] }
bitflags = "2.11.0"
calloop = { version = "0.14.4", features = ["executor"] }
calloop = { version = "0.14.4", features = ["executor", "stream"] }
cosmic-comp-config = { path = "cosmic-comp-config", features = [
"libdisplay-info",
"output",
] }
cosmic-config = { path = "../libcosmic/cosmic-config", features = [
"async-std",
"calloop",
"macro",
] }
@ -44,7 +43,7 @@ iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" }
indexmap = "2.13"
keyframe = "1.1.1"
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [
"async-std",
"tokio",
"wayland",
"multi-window",
] }
@ -78,6 +77,7 @@ xdg = "^3.0"
xdg-user = "0.2.1"
xkbcommon = "0.9"
zbus = "5.14.0"
tokio = { version = "1", features = ["rt-multi-thread"] }
profiling = { version = "1.0" }
rustix = { version = "1.1.4", features = ["process"] }
rand = "0.10"
@ -85,10 +85,9 @@ rand = "0.10"
clap_lex = "1.0"
parking_lot = "0.12.5"
logind-zbus = { version = "5.3.2", optional = true }
futures-executor = { version = "0.3.32", features = ["thread-pool"] }
futures-executor = { version = "0.3.32" }
futures-util = "0.3.32"
cgmath = "0.18.0"
smallvec = "1.15.1"
[dependencies.id_tree]
branch = "feature/copy_clone"
@ -123,7 +122,8 @@ optional = true
[features]
debug = ["egui", "egui_plot", "smithay-egui", "anyhow/backtrace"]
default = ["systemd"]
systemd = ["libsystemd", "logind-zbus"]
logind = ["logind-zbus"]
systemd = ["libsystemd", "logind"]
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
profile-with-tracy-gpu = ["profile-with-tracy", "smithay/tracy_gpu_profiling"]
@ -175,4 +175,4 @@ dnd = { path = "../window_clipboard/dnd" }
mime = { path = "../window_clipboard/mime" }
[patch.crates-io]
smithay = { git = "https://github.com/smithay/smithay.git", rev = "774f2ab" }
smithay = { git = "https://github.com/smithay/smithay.git", rev = "8eb4076" }

View file

@ -39,6 +39,7 @@ vendor:
mkdir -p .cargo
cargo vendor | head -n -1 > .cargo/config
echo 'directory = "vendor"' >> .cargo/config
[ -n "$(SOURCE_GIT_HASH)" ] && printf '\n[env]\nGIT_HASH = "%s"\n' "$(SOURCE_GIT_HASH)" >> .cargo/config || true
tar pcf vendor.tar vendor
rm -rf vendor

View file

@ -101,6 +101,7 @@ 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>,
pub activation_policy: ActivationPolicy,
}
impl Default for CosmicCompConfig {
@ -138,6 +139,7 @@ impl Default for CosmicCompConfig {
accessibility_zoom: ZoomConfig::default(),
appearance_settings: AppearanceConfig::default(),
cursor_hide_timeout: None,
activation_policy: ActivationPolicy::default(),
}
}
}
@ -227,6 +229,14 @@ pub enum EavesdroppingKeyboardMode {
All,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq)]
pub enum ActivationPolicy {
#[default]
Focus,
FocusIfActiveWorkspace,
Urgent,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum XwaylandDescaling {

View file

@ -63,5 +63,5 @@ pub struct PinnedWorkspace {
pub output: OutputMatch,
pub tiling_enabled: bool,
pub id: Option<String>,
// TODO: name
pub name: Option<String>,
}

View file

@ -48,5 +48,6 @@
(appid: "zoom", titles: [".*"]),
(appid: "Tor Browser", titles: [".*"]),
(appid: "^.*?action=join.*$", titles: [".*"]),
(appid: "^(slack|com.slack.Slack)", titles: ["^.*?(Huddle Preview).*"]),
(appid: "^(thunderbird|org.mozilla.thunderbird)(-esr|_esr)*", titles: ["^(Write:).*"]),
]

View file

@ -96,7 +96,6 @@ pub struct Device {
pub inner: InnerDevice,
pub drm: GbmDrmOutputManager,
supports_atomic: bool,
pub texture_formats: FormatSet,
event_token: Option<RegistrationToken>,
pub socket: Option<Socket>,
@ -711,7 +710,6 @@ impl Device {
let (drm, notifier) = DrmDevice::new(fd.clone(), false)
.with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?;
let dev_node = DrmNode::from_dev_id(dev)?;
let supports_atomic = drm.is_atomic();
let gbm = GbmDevice::new(fd)
.with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?;
@ -831,7 +829,6 @@ impl Device {
active_clients,
},
supports_atomic,
texture_formats,
event_token: Some(token),
socket,
@ -840,8 +837,7 @@ impl Device {
pub fn enumerate_surfaces(&mut self) -> Result<OutputChanges> {
// enumerate our outputs
let config =
drm_helpers::display_configuration(self.drm.device_mut(), self.supports_atomic)?;
let config = drm_helpers::display_configuration(self.drm.device_mut())?;
let surfaces = self
.inner

View file

@ -3,6 +3,7 @@
use anyhow::{Context, Result, anyhow};
use libdisplay_info::{edid::DisplayDescriptorTag, info::Info};
use smithay::{
backend::drm::DrmDevice,
reexports::drm::control::{
AtomicCommitFlags, Device as ControlDevice, Mode, ModeFlags, PlaneType, ResourceHandle,
atomic::AtomicModeReq,
@ -16,8 +17,7 @@ use smithay::{
use std::{collections::HashMap, ops::Range};
pub fn display_configuration(
device: &mut impl ControlDevice,
supports_atomic: bool,
device: &mut DrmDevice,
) -> Result<HashMap<connector::Handle, Option<crtc::Handle>>> {
let res_handles = device.resource_handles()?;
let connectors = res_handles.connectors();
@ -73,28 +73,10 @@ pub fn display_configuration(
}
// And then cleanup
if supports_atomic {
if device.is_atomic() {
let mut req = AtomicModeReq::new();
let plane_handles = device.plane_handles()?;
for conn in connectors
.iter()
.flat_map(|conn| device.get_connector(*conn, false).ok())
.filter(|conn| {
if let Some(enc) = conn.current_encoder()
&& let Ok(enc) = device.get_encoder(enc)
&& let Some(crtc) = enc.crtc()
{
return cleanup.contains(&crtc);
}
false
})
.map(|info| info.handle())
{
let crtc_id = get_prop(device, conn, "CRTC_ID")?;
req.add_property(conn, crtc_id, property::Value::CRTC(None));
}
// We cannot just shortcut and use the legacy api for all cleanups because of this.
// (Technically a device does not need to be atomic for planes to be used, but nobody does this otherwise.)
for plane in plane_handles {
@ -108,7 +90,7 @@ pub fn display_configuration(
_ => false,
},
)?;
if cleanup.contains(&crtc) || !is_primary {
if !is_primary && !cleanup.contains(&crtc) {
let crtc_id = get_prop(device, plane, "CRTC_ID")?;
let fb_id = get_prop(device, plane, "FB_ID")?;
req.add_property(plane, crtc_id, property::Value::CRTC(None));
@ -116,27 +98,76 @@ pub fn display_configuration(
}
}
}
for crtc in cleanup {
let mode_id = get_prop(device, crtc, "MODE_ID")?;
let active = get_prop(device, crtc, "ACTIVE")?;
req.add_property(crtc, active, property::Value::Boolean(false));
req.add_property(crtc, mode_id, property::Value::Unknown(0));
}
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
} else {
for crtc in res_handles.crtcs() {
#[allow(deprecated)]
let _ = device.set_cursor(*crtc, Option::<&DumbBuffer>::None);
}
for crtc in cleanup {
}
disable_crtcs(device, &cleanup)?;
Ok(map)
}
/// Disables the given CRTCs and detaches their connectors/planes.
pub fn disable_crtcs(device: &mut DrmDevice, crtcs: &[crtc::Handle]) -> Result<()> {
if crtcs.is_empty() {
return Ok(());
}
let res_handles = device.resource_handles()?;
if device.is_atomic() {
let mut req = AtomicModeReq::new();
for conn in res_handles
.connectors()
.iter()
.flat_map(|conn| device.get_connector(*conn, false).ok())
.filter(|conn| {
if let Some(enc) = conn.current_encoder()
&& let Ok(enc) = device.get_encoder(enc)
&& let Some(crtc) = enc.crtc()
{
return crtcs.contains(&crtc);
}
false
})
.map(|info| info.handle())
{
let crtc_id = get_prop(device, conn, "CRTC_ID")?;
req.add_property(conn, crtc_id, property::Value::CRTC(None));
}
for plane in device.plane_handles()? {
let info = device.get_plane(plane)?;
if let Some(crtc) = info.crtc()
&& crtcs.contains(&crtc)
{
let crtc_id = get_prop(device, plane, "CRTC_ID")?;
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));
}
}
for crtc in crtcs {
let mode_id = get_prop(device, *crtc, "MODE_ID")?;
let active = get_prop(device, *crtc, "ACTIVE")?;
req.add_property(*crtc, active, property::Value::Boolean(false));
req.add_property(*crtc, mode_id, property::Value::Unknown(0));
}
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
} else {
for crtc in crtcs {
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
let _ = device.set_crtc(crtc, None, (0, 0), &[], None);
let _ = device.set_crtc(*crtc, None, (0, 0), &[], None);
}
}
Ok(map)
Ok(())
}
pub fn interface_name(device: &impl ControlDevice, connector: connector::Handle) -> Result<String> {

View file

@ -374,6 +374,7 @@ impl State {
for device in backend.drm_devices.values_mut() {
if let Err(err) = device.drm.lock().activate(true) {
error!(?err, "Failed to resume drm device");
continue;
}
if let Some(lease_state) = device.inner.leasing_global.as_mut() {
lease_state.resume::<State>();
@ -851,11 +852,23 @@ impl KmsGuard<'_> {
// first drop old surfaces
if !test_only {
for output in outputs.iter().filter(|o| !o.is_enabled()) {
device
.inner
.surfaces
.retain(|_, surface| surface.output != *output);
let mut disabled_crtcs = Vec::new();
device.inner.surfaces.retain(|crtc, surface| {
if outputs
.iter()
.any(|o| !o.is_enabled() && surface.output == *o)
{
disabled_crtcs.push(*crtc);
false
} else {
true
}
});
if let Err(err) =
drm_helpers::disable_crtcs(device.drm.device_mut(), &disabled_crtcs)
{
warn!("Failed to disable crtcs for disabled outputs: {err}");
}
}

View file

@ -1,21 +1,30 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::backend::{
SwapBuffersError,
allocator::{
Allocator,
dmabuf::{AnyError, Dmabuf, DmabufAllocator},
gbm::GbmAllocator,
},
drm::{CreateDrmNodeError, DrmNode},
renderer::{
RendererSuper,
gles::{GlesError, GlesRenderer},
glow::GlowRenderer,
multigpu::{ApiDevice, Error as MultiError, GraphicsApi},
use clap_lex::OsStrExt;
use rustix::path::Arg;
use smithay::{
backend::{
SwapBuffersError,
allocator::{
Allocator,
dmabuf::{AnyError, Dmabuf, DmabufAllocator},
gbm::GbmAllocator,
},
drm::{CreateDrmNodeError, DrmNode},
renderer::{
RendererSuper,
gles::{GlesError, GlesRenderer, ffi},
glow::GlowRenderer,
multigpu::{ApiDevice, Error as MultiError, GraphicsApi},
},
},
reexports::drm::control::Device,
};
use std::{
borrow::{Borrow, BorrowMut},
cell::Cell,
ffi::{CStr, c_char},
};
use std::{borrow::Borrow, cell::Cell};
use std::{
collections::HashMap,
fmt,
@ -98,7 +107,7 @@ impl<A: AsFd + Clone + Send + 'static> GbmGlowBackend<A> {
}
}
impl<A: AsFd + Clone + 'static> GraphicsApi for GbmGlowBackend<A> {
impl<A: AsFd + Device + Clone + 'static> GraphicsApi for GbmGlowBackend<A> {
type Device = GbmGlowDevice;
type Error = Error;
@ -123,11 +132,29 @@ impl<A: AsFd + Clone + 'static> GraphicsApi for GbmGlowBackend<A> {
.any(|renderer| renderer.node.dev_id() == node.dev_id())
})
.flat_map(|(node, (allocator, renderer))| {
let renderer = renderer.replace(None)?;
let mut renderer = renderer.replace(None)?;
let is_intel = allocator
.as_ref()
.get_driver()
.is_ok_and(|drv| drv.name().contains("i915"));
let intel_export_quirk = is_intel
&& BorrowMut::<GlesRenderer>::borrow_mut(&mut renderer)
.with_context(|gl| unsafe {
CStr::from_ptr(gl.GetString(ffi::RENDERER) as *const c_char)
})
.is_ok_and(|name| {
name.as_str().is_ok_and(|name| {
name.contains("TGL")
|| name.contains("MTL")
|| name.contains("ARL")
|| name.contains("LNL")
})
});
Some(GbmGlowDevice {
node: *node,
renderer,
intel_export_quirk,
allocator: Box::new(DmabufAllocator(allocator.clone())),
})
})
@ -150,6 +177,7 @@ impl<A: AsFd + Clone + 'static> GraphicsApi for GbmGlowBackend<A> {
pub struct GbmGlowDevice {
node: DrmNode,
renderer: GlowRenderer,
intel_export_quirk: bool,
allocator: Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>,
}
@ -180,9 +208,14 @@ impl ApiDevice for GbmGlowDevice {
fn can_do_cross_device_imports(&self) -> bool {
!Borrow::<GlesRenderer>::borrow(&self.renderer).is_software()
}
fn should_do_cross_device_exports(&self) -> bool {
!self.intel_export_quirk
}
}
impl<T: GraphicsApi, A: AsFd + Clone + 'static> FromGlesError for MultiError<GbmGlowBackend<A>, T>
impl<T: GraphicsApi, A: AsFd + Device + Clone + 'static> FromGlesError
for MultiError<GbmGlowBackend<A>, T>
where
T::Error: 'static,
<<T::Device as ApiDevice>::Renderer as RendererSuper>::Error: 'static,

View file

@ -1024,14 +1024,18 @@ impl SurfaceThreadState {
let animations_going = shell.animations_going();
let output = self.mirroring.as_ref().unwrap_or(&self.output);
if let Some((_, workspace)) = shell.workspaces.active(output) {
if let Some(fullscreen_surface) = workspace.get_fullscreen() {
let seat = shell.seats.last_active();
if let Some(fullscreen_surface) = workspace.get_fullscreen(seat) {
const _30_FPS: Duration = Duration::from_nanos(1_000_000_000 / 30);
(
true,
fullscreen_surface.wl_surface().is_some_and(|surface| {
recursive_frame_time_estimation(&self.clock, &surface)
.is_some_and(|dur| dur <= _30_FPS)
}),
fullscreen_surface
.surface
.wl_surface()
.is_some_and(|surface| {
recursive_frame_time_estimation(&self.clock, &surface)
.is_some_and(|dur| dur <= _30_FPS)
}),
animations_going,
)
} else {
@ -1490,18 +1494,21 @@ fn render_node_for_output(
let Some(workspace) = shell.active_space(output) else {
return *target_node;
};
let nodes = workspace
.get_fullscreen()
.map(|w| vec![w.clone()])
.unwrap_or_else(|| {
workspace
.mapped()
.map(|mapped| mapped.active_window())
.collect::<Vec<_>>()
})
.into_iter()
.flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s)))
.collect::<Vec<_>>();
let fullscreens: Vec<_> = workspace
.get_fullscreen_surfaces()
.map(|f| f.surface.clone())
.collect();
let nodes = if !fullscreens.is_empty() {
fullscreens
} else {
workspace
.mapped()
.map(|mapped| mapped.active_window())
.collect::<Vec<_>>()
}
.into_iter()
.flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s)))
.collect::<Vec<_>>();
if nodes.contains(target_node) || nodes.is_empty() {
*target_node

View file

@ -26,7 +26,7 @@ use smithay::{
calloop::{EventLoop, ping},
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::DisplayHandle,
winit::platform::pump_events::PumpStatus,
winit::event_loop::pump_events::PumpStatus,
},
utils::Transform,
wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh},

View file

@ -45,8 +45,8 @@ mod types;
use cosmic::config::CosmicTk;
pub use cosmic_comp_config::EdidProduct;
use cosmic_comp_config::{
AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig, XwaylandDescaling,
XwaylandEavesdropping, ZoomConfig,
ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig,
XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
output::comp::{
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
@ -966,6 +966,12 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
}
}
}
"activation_policy" => {
let new = get_config::<ActivationPolicy>(&config, "activation_policy");
if new != state.common.config.cosmic_conf.activation_policy {
state.common.config.cosmic_conf.activation_policy = new;
}
}
_ => {}
}
}

View file

@ -1,13 +1,12 @@
// https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.freedesktop.a11y.xml
use futures_executor::ThreadPool;
use smithay::{
backend::input::KeyState,
input::keyboard::{KeysymHandle, ModifiersState},
};
use std::{
collections::{HashMap, HashSet},
sync::{Arc, Mutex, OnceLock},
sync::{Arc, Mutex},
};
use tracing::debug;
use xkbcommon::xkb::Keysym;
@ -72,40 +71,37 @@ impl Clients {
#[derive(Debug)]
pub struct A11yKeyboardMonitorState {
executor: ThreadPool,
executor: calloop::futures::Scheduler<()>,
clients: Arc<Mutex<Clients>>,
active_virtual_mods: HashSet<Keysym>,
conn: Arc<OnceLock<zbus::Connection>>,
name_owners: Arc<OnceLock<NameOwners>>,
conn: zbus::Connection,
name_owners: NameOwners,
}
impl A11yKeyboardMonitorState {
pub fn new(executor: &ThreadPool) -> Self {
pub async fn new(
conn: &zbus::Connection,
name_owners: &NameOwners,
executor: &calloop::futures::Scheduler<()>,
) -> zbus::Result<Self> {
let clients = Arc::new(Mutex::new(Clients::default()));
let clients_clone = clients.clone();
let conn_cell = Arc::new(OnceLock::new());
let conn_cell_clone = conn_cell.clone();
let name_owners_cell = Arc::new(OnceLock::new());
let name_owners_cell_clone = name_owners_cell.clone();
let executor_clone = executor.clone();
executor.spawn_ok(async move {
match serve(clients_clone, &executor_clone).await {
Ok((conn, name_owners)) => {
conn_cell_clone.set(conn).unwrap();
name_owners_cell_clone.set(name_owners).unwrap();
}
Err(err) => {
tracing::error!("Failed to serve `org.freedesktop.a11y.Manager`: {err}");
}
}
});
Self {
let keyboard_monitor = KeyboardMonitor {
clients: clients.clone(),
name_owners: name_owners.clone(),
};
conn.object_server()
.at("/org/freedesktop/a11y/Manager", keyboard_monitor)
.await?;
conn.request_name("org.freedesktop.a11y.Manager").await?;
Ok(Self {
executor: executor.clone(),
clients,
active_virtual_mods: HashSet::new(),
conn: conn_cell,
name_owners: name_owners_cell,
}
conn: conn.clone(),
name_owners: name_owners.clone(),
})
}
pub fn has_virtual_mod(&self, keysym: Keysym) -> bool {
@ -154,10 +150,6 @@ impl A11yKeyboardMonitorState {
}
pub fn key_event(&self, modifiers: &ModifiersState, keysym: &KeysymHandle, state: KeyState) {
let Some(conn) = self.conn.get() else {
return;
};
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()) {
@ -165,7 +157,7 @@ impl A11yKeyboardMonitorState {
}
let mut signal_context =
SignalEmitter::new(conn, "/org/freedesktop/a11y/Manager").unwrap();
SignalEmitter::new(&self.conn, "/org/freedesktop/a11y/Manager").unwrap();
// Instead of sending signal to all clients, send only to authorized
// clients with registed watches.
signal_context = signal_context.set_destination(unique_name.clone().into());
@ -186,7 +178,7 @@ impl A11yKeyboardMonitorState {
unichar,
keysym.raw_code().raw() as u16,
);
self.executor.spawn_ok(async {
let _ = self.executor.schedule(async {
let _ = future.await;
});
}
@ -195,13 +187,11 @@ impl A11yKeyboardMonitorState {
pub fn refresh(&mut self) {
// Remove clients and associated grabs when unique names are no longer
// present on bus, or no longer hold approved name on bus.
if let Some(name_owners) = self.name_owners.get() {
self.clients
.lock()
.unwrap()
.0
.retain(|k, _| name_owners.check_owner_no_poll(k, ALLOWED_NAMES))
}
self.clients
.lock()
.unwrap()
.0
.retain(|k, _| self.name_owners.check_owner_no_poll(k, ALLOWED_NAMES))
}
}
@ -311,20 +301,3 @@ impl KeyboardMonitor {
keycode: u16,
) -> zbus::Result<()>;
}
async fn serve(
clients: Arc<Mutex<Clients>>,
executor: &ThreadPool,
) -> zbus::Result<(zbus::Connection, NameOwners)> {
let conn = zbus::Connection::session().await?;
let name_owners = NameOwners::new(&conn, executor).await?;
let keyboard_monitor = KeyboardMonitor {
clients,
name_owners: name_owners.clone(),
};
conn.object_server()
.at("/org/freedesktop/a11y/Manager", keyboard_monitor)
.await?;
conn.request_name("org.freedesktop.a11y.Manager").await?;
Ok((conn, name_owners))
}

View file

@ -1,24 +1,34 @@
use std::os::fd::OwnedFd;
use anyhow::{Context, Result};
use logind_zbus::manager::{InhibitType::HandleLidSwitch, ManagerProxyBlocking};
use zbus::blocking::Connection;
use logind_zbus::manager::{InhibitType::HandleLidSwitch, ManagerProxy};
pub fn inhibit_lid() -> Result<OwnedFd> {
let conn = Connection::system()?;
let proxy = ManagerProxyBlocking::new(&conn)?;
let fd = proxy.inhibit(
HandleLidSwitch,
"cosmic-comp",
"External output connected",
"block",
)?;
use crate::state::Common;
pub fn inhibit_lid(common: &Common) -> Result<OwnedFd> {
let fd = futures_executor::block_on(async {
let conn = common.dbus_state.system_conn().await?;
let manager = ManagerProxy::new(conn).await?;
manager
.inhibit(
HandleLidSwitch,
"cosmic-comp",
"External output connected",
"block",
)
.await
})?;
Ok(fd.into())
}
pub fn lid_closed() -> Result<bool> {
let conn = Connection::system()?;
let proxy = ManagerProxyBlocking::new(&conn)?;
proxy.lid_closed().context("Failed to talk to logind")
pub fn lid_closed(common: &Common) -> Result<bool> {
futures_executor::block_on(async {
let conn = common.dbus_state.system_conn().await?;
let manager = ManagerProxy::new(conn).await?;
manager
.lid_closed()
.await
.context("Failed to talk to logind")
})
}

View file

@ -3,103 +3,168 @@ use crate::{
utils::prelude::OutputExt,
};
use anyhow::{Context, Result};
use calloop::{InsertError, LoopHandle, RegistrationToken};
use calloop::{InsertError, LoopHandle, stream::StreamSource};
use cosmic_comp_config::output::comp::OutputState;
use futures_executor::{ThreadPool, block_on};
use futures_util::stream::StreamExt;
use std::collections::HashMap;
use std::{
cell::{RefCell, RefMut},
collections::HashMap,
rc::Rc,
};
use tracing::{error, warn};
use zbus::blocking::{Connection, fdo::DBusProxy};
pub mod a11y_keyboard_monitor;
#[cfg(feature = "systemd")]
use a11y_keyboard_monitor::A11yKeyboardMonitorState;
#[cfg(feature = "logind")]
pub mod logind;
mod name_owners;
mod power;
pub fn init(
evlh: &LoopHandle<'static, State>,
executor: &ThreadPool,
) -> Result<Vec<RegistrationToken>> {
let mut tokens = Vec::new();
#[derive(Clone, Debug)]
pub struct DBusState(Rc<DBusStateInner>);
match block_on(power::init()) {
#[derive(Debug)]
struct DBusStateInner {
evlh: LoopHandle<'static, State>,
executor: calloop::futures::Scheduler<()>,
session_conn: zbus::Result<zbus::Connection>,
system_conn: zbus::Result<zbus::Connection>,
a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>,
}
impl DBusState {
pub fn init(evlh: &LoopHandle<'static, State>) -> Self {
let (source, executor) = calloop::futures::executor().unwrap();
let session_conn = futures_executor::block_on(zbus::Connection::session());
let system_conn = futures_executor::block_on(zbus::Connection::system());
let state = Self(Rc::new(DBusStateInner {
evlh: evlh.clone(),
executor,
session_conn,
system_conn,
a11y_keyboard_monitor: RefCell::new(None),
}));
evlh.insert_source(source, |_, _, _| {}).unwrap();
let state_clone = state.clone();
state.spawn(async move {
if let Err(err) = init_session(&state_clone).await {
tracing::error!("Failed to initialize session DBus connection: {}", err);
}
});
let state_clone = state.clone();
state.spawn(async move {
if let Err(err) = init_system(&state_clone).await {
tracing::error!("Failed to initialize system DBus connection: {}", err);
}
});
state
}
pub fn a11y_keyboard_monitor(
&self,
) -> Option<RefMut<'_, a11y_keyboard_monitor::A11yKeyboardMonitorState>> {
RefMut::filter_map(self.0.a11y_keyboard_monitor.borrow_mut(), |x| x.as_mut()).ok()
}
// 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())
}
async fn system_conn(&self) -> zbus::Result<&zbus::Connection> {
self.0.system_conn.as_ref().map_err(|err| err.clone())
}
fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
let _ = self.0.executor.schedule(fut);
}
}
async fn init_session(state: &DBusState) -> zbus::Result<()> {
let conn = state.session_conn().await?;
let name_owners = name_owners::NameOwners::new(conn, &state.0.executor).await?;
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);
Ok(())
}
async fn init_system(state: &DBusState) -> zbus::Result<()> {
let conn = state.system_conn().await?.clone();
let evlh = state.0.evlh.clone();
state.spawn(async move {
if let Err(err) = power_hot_plug_task(conn, evlh).await {
tracing::warn!(?err, "Failed to initialize dbus handlers");
}
});
Ok(())
}
async fn power_hot_plug_task(
conn: zbus::Connection,
evlh: LoopHandle<'static, State>,
) -> Result<()> {
match power::init(&conn).await {
Ok(power_daemon) => {
let (tx, rx) = calloop::channel::channel();
let token = evlh
.insert_source(rx, |event, _, state| match event {
calloop::channel::Event::Msg(_) => {
let nodes = match &mut state.backend {
BackendData::Kms(kms) => {
kms.drm_devices.keys().cloned().collect::<Vec<_>>()
}
_ => Vec::new(),
};
let mut added = Vec::new();
for node in nodes {
match state.device_changed(node.dev_id()) {
Ok(outputs) => added.extend(outputs),
Err(err) => {
tracing::error!(?err, "Failed to update drm device {}.", node)
}
}
if let Ok(stream) = power_daemon.receive_hot_plug_detect().await {
let source = StreamSource::new(stream).unwrap();
evlh.insert_source(source, |_, _, state| {
let nodes = match &mut state.backend {
BackendData::Kms(kms) => {
kms.drm_devices.keys().cloned().collect::<Vec<_>>()
}
if let Err(err) = state.refresh_output_config() {
warn!("Unable to load output config: {}", err);
if !added.is_empty() {
for output in added {
output.config_mut().enabled = OutputState::Disabled;
}
if let Err(err) = state.refresh_output_config() {
error!("Unrecoverable config error: {}", err);
}
_ => Vec::new(),
};
let mut added = Vec::new();
for node in nodes {
match state.device_changed(node.dev_id()) {
Ok(outputs) => added.extend(outputs),
Err(err) => {
tracing::error!(?err, "Failed to update drm device {}.", node)
}
}
}
if let Err(err) = state.refresh_output_config() {
warn!("Unable to load output config: {}", err);
if !added.is_empty() {
for output in added {
output.config_mut().enabled = OutputState::Disabled;
}
if let Err(err) = state.refresh_output_config() {
error!("Unrecoverable config error: {}", err);
}
}
}
calloop::channel::Event::Closed => (),
})
.map_err(|InsertError { error, .. }| error)
.with_context(|| "Failed to add channel to event_loop")?;
// start helper thread
executor.spawn_ok(async move {
if let Ok(mut msg_iter) = power_daemon.receive_hot_plug_detect().await {
while let Some(msg) = msg_iter.next().await {
if tx.send(msg).is_err() {
break;
}
}
}
});
tokens.push(token);
}
}
Err(err) => {
tracing::info!(?err, "Failed to connect to com.system76.PowerDaemon");
}
};
Ok(tokens)
Ok(())
}
/// Updated the D-Bus activation environment with `WAYLAND_DISPLAY` and
/// `DISPLAY` variables.
pub fn ready(common: &Common) -> Result<()> {
let conn = Connection::session()?;
let proxy = DBusProxy::new(&conn)?;
proxy.update_activation_environment(HashMap::from([
("WAYLAND_DISPLAY", common.socket.to_str().unwrap()),
(
"DISPLAY",
&common
.xwayland_state
.as_ref()
.map(|s| format!(":{}", s.display))
.unwrap_or_default(),
),
]))?;
futures_executor::block_on(async {
let conn = common.dbus_state.session_conn().await?;
let dbus = zbus::fdo::DBusProxy::new(conn).await?;
dbus.update_activation_environment(HashMap::from([
("WAYLAND_DISPLAY", common.socket.to_str().unwrap()),
(
"DISPLAY",
&common
.xwayland_state
.as_ref()
.map(|s| format!(":{}", s.display))
.unwrap_or_default(),
),
]))
.await
})?;
Ok(())
}

View file

@ -2,9 +2,10 @@
//!
//! Compare to Mutter's `MetaDbusAccessChecker`
use futures_executor::ThreadPool;
use futures_util::stream::FusedStream;
use futures_util::{StreamExt, stream::FuturesUnordered};
use futures_util::{
StreamExt,
stream::{FusedStream, FuturesUnordered},
};
use std::{
collections::{HashMap, HashSet},
future::{Future, poll_fn},
@ -94,7 +95,10 @@ fn update_task(inner: Weak<Mutex<Inner>>) -> impl Future<Output = ()> {
pub struct NameOwners(Arc<Mutex<Inner>>);
impl NameOwners {
pub async fn new(connection: &zbus::Connection, executor: &ThreadPool) -> zbus::Result<Self> {
pub async fn new(
connection: &zbus::Connection,
executor: &calloop::futures::Scheduler<()>,
) -> zbus::Result<Self> {
let dbus = fdo::DBusProxy::new(connection).await?;
let stream = dbus.receive_name_owner_changed().await?;
@ -126,7 +130,7 @@ impl NameOwners {
}));
if enforce {
executor.spawn_ok(update_task(Arc::downgrade(&inner)));
let _ = executor.schedule(update_task(Arc::downgrade(&inner)));
}
Ok(NameOwners(inner))

View file

@ -79,9 +79,8 @@ pub trait PowerDaemon {
fn power_profile_switch(&self, profile: &str) -> zbus::Result<()>;
}
pub async fn init() -> anyhow::Result<PowerDaemonProxy<'static>> {
let conn = Connection::system().await?;
let proxy = PowerDaemonProxy::new(&conn).await?;
pub async fn init(conn: &Connection) -> anyhow::Result<PowerDaemonProxy<'static>> {
let proxy = PowerDaemonProxy::new(conn).await?;
proxy.0.introspect().await?;
Ok(proxy)
}

View file

@ -52,16 +52,21 @@ use smithay::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
PointerGrab, RelativeMotionEvent,
PointerGrab, PointerHandle, RelativeMotionEvent,
},
touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent},
},
output::Output,
reexports::{
input::Device as InputDevice, wayland_server::protocol::wl_shm::Format as ShmFormat,
input::Device as InputDevice,
wayland_server::{
Resource as _,
protocol::{wl_shm::Format as ShmFormat, wl_surface::WlSurface},
},
},
utils::{Point, Rectangle, SERIAL_COUNTER, Serial},
utils::{Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size},
wayland::{
compositor::CompositorHandler,
image_copy_capture::{BufferConstraints, CursorSessionRef},
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat,
pointer_constraints::{PointerConstraint, with_pointer_constraint},
@ -349,27 +354,6 @@ impl State {
_ => {}
});
}
let original_position = position;
position += event.delta().as_global();
let output = shell
.outputs()
.find(|output| output.geometry().to_f64().contains(position))
.cloned()
.unwrap_or(current_output.clone());
let output_geometry = output.geometry();
position.x = position.x.clamp(
output_geometry.loc.x as f64,
(output_geometry.loc.x + output_geometry.size.w - 1) as f64,
);
position.y = position.y.clamp(
output_geometry.loc.y as f64,
(output_geometry.loc.y + output_geometry.size.h - 1) as f64,
);
let new_under = State::surface_under(position, &output, &shell)
.map(|(target, pos)| (target, pos.as_logical()));
std::mem::drop(shell);
ptr.relative_motion(
@ -387,6 +371,25 @@ impl State {
return;
}
let original_position = position;
position += event.delta().as_global();
let shell = self.common.shell.read();
let output = shell
.outputs()
.find(|output| output.geometry().to_f64().contains(position))
.cloned()
.unwrap_or(current_output.clone());
drop(shell);
let output_geometry = output.geometry();
position.x = position.x.clamp(
output_geometry.loc.x as f64,
(output_geometry.loc.x + output_geometry.size.w - 1) as f64,
);
position.y = position.y.clamp(
output_geometry.loc.y as f64,
(output_geometry.loc.y + output_geometry.size.h - 1) as f64,
);
if ptr.is_grabbed() {
if seat
.user_data()
@ -494,50 +497,90 @@ impl State {
}
}
// If confined, don't move pointer if it would go outside surface or region
if pointer_confined && let Some((surface, surface_loc)) = &under {
if new_under.as_ref().and_then(|(under, _)| under.wl_surface())
!= surface.wl_surface()
{
ptr.frame(self);
return;
}
match surface {
PointerFocusTarget::WlSurface { surface, .. } => {
if under_from_surface_tree(
surface,
position.as_logical() - surface_loc.to_f64(),
(0, 0),
WindowSurfaceType::ALL,
)
.is_none()
{
ptr.frame(self);
return;
}
// If confined, help user to update valid coordinates can improve user experience
let shell = self.common.shell.read();
let new_under = if pointer_confined && let Some((surface, surface_loc)) = &under
{
let is_legal = |pos: Point<f64, Global>, shell: &Shell| {
let new_under = State::surface_under(pos, &output, shell)
.map(|(target, pos)| (target, pos.as_logical()));
// TODO: We might need a solution that allows constraints to bypass the surface without affecting the constraints themselves
if new_under.as_ref().and_then(|(under, _)| under.wl_surface())
!= surface.wl_surface()
{
return (false, None);
}
PointerFocusTarget::X11Surface { surface, .. } => {
if surface
.surface_under(
match surface {
PointerFocusTarget::WlSurface { surface, .. } => {
if under_from_surface_tree(
surface,
position.as_logical() - surface_loc.to_f64(),
(0, 0),
WindowSurfaceType::ALL,
)
.is_none()
{
ptr.frame(self);
return;
{
return (false, None);
}
}
PointerFocusTarget::X11Surface { surface, .. } => {
if surface
.surface_under(
position.as_logical() - surface_loc.to_f64(),
(0, 0),
WindowSurfaceType::ALL,
)
.is_none()
{
return (false, None);
}
}
_ => {}
}
if let Some(region) = &confine_region
&& !region
.contains((pos.as_logical() - *surface_loc).to_i32_round())
{
return (false, None);
}
(true, new_under)
};
match is_legal(position, &shell) {
(true, under) => under,
_ => {
let y_only_pos = Point::new(original_position.x, position.y);
let x_only_pos = Point::new(position.x, original_position.y);
match is_legal(y_only_pos, &shell) {
(true, under) => {
position = y_only_pos;
under
}
_ => match is_legal(x_only_pos, &shell) {
(true, under) => {
position = x_only_pos;
under
}
_ => {
position = original_position;
None
}
},
}
}
_ => {}
}
if let Some(region) = confine_region
&& !region
.contains((position.as_logical() - *surface_loc).to_i32_round())
{
ptr.frame(self);
return;
}
} else {
State::surface_under(position, &output, &shell)
.map(|(target, pos)| (target, pos.as_logical()))
};
drop(shell);
if pointer_confined && new_under.is_none() {
ptr.frame(self);
return;
}
let serial = SERIAL_COUNTER.next_serial();
@ -552,24 +595,32 @@ impl State {
);
ptr.frame(self);
// If pointer is now in a constraint region, activate it
// If pointer is now in a constraint region and window is in focused, activate it
if let Some((under, surface_location)) = new_under
.and_then(|(target, loc)| Some((target.wl_surface()?.into_owned(), loc)))
{
with_pointer_constraint(&under, &ptr, |constraint| match constraint {
Some(constraint) if !constraint.is_active() => {
let region = match &*constraint {
PointerConstraint::Locked(locked) => locked.region(),
PointerConstraint::Confined(confined) => confined.region(),
};
let point =
(ptr.current_location() - surface_location).to_i32_round();
if region.is_none_or(|region| region.contains(point)) {
constraint.activate();
let shell = self.common.shell.read();
let is_focused = seat
.get_keyboard()
.and_then(|k| k.current_focus())
.is_some_and(|f| f.has_surface(&shell, &under));
if is_focused {
with_pointer_constraint(&under, &ptr, |constraint| match constraint {
Some(constraint) if !constraint.is_active() => {
let region = match &*constraint {
PointerConstraint::Locked(locked) => locked.region(),
PointerConstraint::Confined(confined) => confined.region(),
};
let point =
(ptr.current_location() - surface_location).to_i32_round();
if region.is_none_or(|region| region.contains(point)) {
constraint.activate();
}
}
}
_ => {}
});
_ => {}
});
}
}
let mut shell = self.common.shell.write();
@ -589,11 +640,13 @@ impl State {
for session in cursor_sessions_for_output(&shell, &output) {
if let Some((geometry, offset)) = seat.cursor_geometry(
position.as_logical().to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output_geometry.size.to_f64().as_logical(),
),
(position - output_geometry.loc.to_f64())
.as_logical()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output_geometry.size.to_f64().as_logical(),
),
self.common.clock.now(),
) {
if session
@ -601,8 +654,13 @@ impl State {
.map(|constraint| constraint.size != geometry.size)
.unwrap_or(true)
{
let mut cursor_size = geometry.size;
// Client shouldn't try to allocate 0x0 buffer
if cursor_size == Size::new(0, 0) {
cursor_size = Size::new(1, 1);
}
session.update_constraints(BufferConstraints {
size: geometry.size,
size: cursor_size,
shm: vec![ShmFormat::Argb8888],
dma: None,
});
@ -625,11 +683,11 @@ impl State {
self.common.idle_notifier_state.notify_activity(&seat);
notify_cursor_activity(self, &seat);
let output = seat.active_output();
let geometry = output.geometry();
let position = geometry.loc.to_f64()
let output_geometry = output.geometry();
let position = output_geometry.loc.to_f64()
+ smithay::backend::input::AbsolutePositionEvent::position_transformed(
&event,
geometry.size.as_logical(),
output_geometry.size.as_logical(),
)
.as_global();
let serial = SERIAL_COUNTER.next_serial();
@ -651,11 +709,13 @@ impl State {
let shell = self.common.shell.read();
for session in cursor_sessions_for_output(&shell, &output) {
if let Some((geometry, offset)) = seat.cursor_geometry(
position.as_logical().to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&geometry.size.to_f64().as_logical(),
),
(position - output_geometry.loc.to_f64())
.as_logical()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output_geometry.size.to_f64().as_logical(),
),
self.common.clock.now(),
) {
if session
@ -663,8 +723,13 @@ impl State {
.map(|constraint| constraint.size != geometry.size)
.unwrap_or(true)
{
let mut cursor_size = geometry.size;
// Client shouldn't try to allocate 0x0 buffer
if cursor_size == Size::new(0, 0) {
cursor_size = Size::new(1, 1);
}
session.update_constraints(BufferConstraints {
size: geometry.size,
size: cursor_size,
shm: vec![ShmFormat::Argb8888],
dma: None,
});
@ -1535,7 +1600,7 @@ impl State {
}
InputEvent::Special(_) => {}
InputEvent::SwitchToggle { event } => {
#[cfg(feature = "systemd")]
#[cfg(feature = "logind")]
if event.switch() == Some(Switch::Lid) && self.common.inhibit_lid_fd.is_some() {
let backend = self.backend.lock();
let output = backend
@ -1617,9 +1682,9 @@ impl State {
.unwrap_or(false)
});
self.common
.a11y_keyboard_monitor_state
.key_event(modifiers, &handle, event.state());
if let Some(a11y_keyboard_monitor) = self.common.dbus_state.a11y_keyboard_monitor() {
a11y_keyboard_monitor.key_event(modifiers, &handle, event.state());
}
// Leave move overview mode, if any modifier was released
if let Some(Trigger::KeyboardMove(action_modifiers)) =
@ -1779,61 +1844,54 @@ impl State {
)));
}
if event.state() == KeyState::Released {
let removed = self
.common
.a11y_keyboard_monitor_state
.remove_active_virtual_mod(handle.modified_sym());
// If `Caps_Lock` is a virtual modifier, and is in locked state, clear it
if removed
&& handle.modified_sym() == Keysym::Caps_Lock
&& (modifiers.serialized.locked & 2) != 0
if let Some(mut a11y_keyboard_monitor) = self.common.dbus_state.a11y_keyboard_monitor() {
if event.state() == KeyState::Released {
let removed =
a11y_keyboard_monitor.remove_active_virtual_mod(handle.modified_sym());
// If `Caps_Lock` is a virtual modifier, and is in locked state, clear it
if removed
&& handle.modified_sym() == Keysym::Caps_Lock
&& (modifiers.serialized.locked & 2) != 0
{
let seat = seat.clone();
let key_code = event.key_code();
self.common.event_loop_handle.insert_idle(move |state| {
if let Some(keyboard) = seat.get_keyboard() {
let serial = SERIAL_COUNTER.next_serial();
let time = state.common.clock.now().as_millis();
keyboard.input(
state,
key_code,
KeyState::Pressed,
serial,
time,
|_, _, _| FilterResult::<()>::Forward,
);
let serial = SERIAL_COUNTER.next_serial();
keyboard.input(
state,
key_code,
KeyState::Released,
serial,
time,
|_, _, _| FilterResult::<()>::Forward,
);
}
});
}
} else if event.state() == KeyState::Pressed
&& a11y_keyboard_monitor.has_virtual_mod(handle.modified_sym())
{
let seat = seat.clone();
let key_code = event.key_code();
self.common.event_loop_handle.insert_idle(move |state| {
if let Some(keyboard) = seat.get_keyboard() {
let serial = SERIAL_COUNTER.next_serial();
let time = state.common.clock.now().as_millis();
keyboard.input(
state,
key_code,
KeyState::Pressed,
serial,
time,
|_, _, _| FilterResult::<()>::Forward,
);
let serial = SERIAL_COUNTER.next_serial();
keyboard.input(
state,
key_code,
KeyState::Released,
serial,
time,
|_, _, _| FilterResult::<()>::Forward,
);
}
});
a11y_keyboard_monitor.add_active_virtual_mod(handle.modified_sym());
tracing::debug!(
"active virtual mods: {:?}",
a11y_keyboard_monitor.active_virtual_mods()
);
seat.supressed_keys().add(&handle, None);
return FilterResult::Intercept(None);
}
} else if event.state() == KeyState::Pressed
&& self
.common
.a11y_keyboard_monitor_state
.has_virtual_mod(handle.modified_sym())
{
self.common
.a11y_keyboard_monitor_state
.add_active_virtual_mod(handle.modified_sym());
tracing::debug!(
"active virtual mods: {:?}",
self.common
.a11y_keyboard_monitor_state
.active_virtual_mods()
);
seat.supressed_keys().add(&handle, None);
return FilterResult::Intercept(None);
}
// Skip released events for initially surpressed keys
@ -1860,12 +1918,10 @@ impl State {
return FilterResult::Intercept(None);
}
if event.state() == KeyState::Pressed
&& (self.common.a11y_keyboard_monitor_state.has_keyboard_grab()
|| self
.common
.a11y_keyboard_monitor_state
.has_key_grab(modifiers, handle.modified_sym()))
if let Some(a11y_keyboard_monitor) = self.common.dbus_state.a11y_keyboard_monitor()
&& event.state() == KeyState::Pressed
&& (a11y_keyboard_monitor.has_keyboard_grab()
|| a11y_keyboard_monitor.has_key_grab(modifiers, handle.modified_sym()))
{
let modifiers_queue = seat.modifiers_shortcut_queue();
modifiers_queue.clear();
@ -2117,14 +2173,14 @@ impl State {
}
Stage::StickyPopups(layout) => {
if let Some(element) =
layout.popup_element_under(global_pos.to_local(output))
layout.popup_element_under(global_pos.to_local(output), seat)
{
return ControlFlow::Break(Ok(Some(element)));
}
}
Stage::Sticky(layout) => {
if let Some(element) =
layout.toplevel_element_under(global_pos.to_local(output))
layout.toplevel_element_under(global_pos.to_local(output), seat)
{
return ControlFlow::Break(Ok(Some(element)));
}
@ -2279,7 +2335,7 @@ impl State {
}
Stage::StickyPopups(floating_layer) => {
if let Some(under) = floating_layer
.popup_surface_under(relative_pos)
.popup_surface_under(relative_pos, seat)
.map(|(target, point)| (target, point.to_global(output)))
{
return ControlFlow::Break(Ok(Some(under)));
@ -2287,7 +2343,7 @@ impl State {
}
Stage::Sticky(floating_layer) => {
if let Some(under) = floating_layer
.toplevel_surface_under(relative_pos)
.toplevel_surface_under(relative_pos, seat)
.map(|(target, point)| (target, point.to_global(output)))
{
return ControlFlow::Break(Ok(Some(under)));
@ -2317,8 +2373,113 @@ impl State {
.ok()
.flatten()
}
pub fn apply_cursor_hint(
&mut self,
surface: &WlSurface,
pointer: &PointerHandle<Self>,
mut location: Point<f64, Logical>,
) {
let Some(client) = surface.client() else {
return;
};
location = location.downscale(self.client_compositor_state(&client).client_scale());
let point_and_output = {
let shell = self.common.shell.read();
let found = shell.workspaces.sets.iter().find_map(|(out, set)| {
set.surface_geometry_offset_from_toplevel(surface)
.map(|(geometry, surface_offset)| (out, geometry, surface_offset))
});
if let Some((output, geometry, surface_offset)) = found {
let pos_in_element = location + surface_offset.to_f64();
let window_size = geometry.size.to_f64();
let is_legal = |p: Point<f64, Logical>| {
let in_window =
p.x >= 0.0 && p.y >= 0.0 && p.x < window_size.w && p.y < window_size.h;
if !in_window {
return false;
}
with_pointer_constraint(surface, pointer, |constraint| {
if let Some(constraint) = constraint
&& let Some(region) = constraint.region()
{
let point_in_surface = (p - surface_offset.to_f64()).to_i32_round();
return region.contains(point_in_surface);
}
true
})
};
let workspace_origin = output.geometry().loc.to_f64();
let origin = geometry.loc.to_f64();
if is_legal(pos_in_element) {
let x = workspace_origin.x + origin.x + pos_in_element.x;
let y = workspace_origin.y + origin.y + pos_in_element.y;
Some((Point::new(x, y), output.clone()))
} else {
None
}
} else {
None
}
};
if let Some((point, output)) = point_and_output {
let original_position = pointer.current_location();
pointer.set_location(point);
let mut shell = self.common.shell.write();
shell.update_pointer_position(point.as_global().to_local(&output), &output);
let seat = shell
.seats
.iter()
.find(|s| s.get_pointer().as_ref() == Some(pointer))
.cloned();
if let Some(seat) = seat {
shell.update_focal_point(
&seat,
original_position.as_global(),
self.common.config.cosmic_conf.accessibility_zoom.view_moves,
);
let output_geometry = output.geometry();
for session in cursor_sessions_for_output(&shell, &output) {
if let Some((geometry, offset)) = seat.cursor_geometry(
point.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output_geometry.size.to_f64().as_logical(),
),
self.common.clock.now(),
) {
if session
.current_constraints()
.map(|constraint| constraint.size != geometry.size)
.unwrap_or(true)
{
session.update_constraints(BufferConstraints {
size: geometry.size,
shm: vec![ShmFormat::Argb8888],
dma: None,
});
}
session.set_cursor_hotspot(offset);
session.set_cursor_pos(Some(geometry.loc));
}
}
}
}
}
}
// Output and workspace sessions for the given output
fn cursor_sessions_for_output<'a>(
shell: &'a Shell,
output: &'a Output,
@ -2327,16 +2488,9 @@ fn cursor_sessions_for_output<'a>(
.active_space(output)
.into_iter()
.flat_map(|workspace| {
let maybe_fullscreen = workspace.get_fullscreen();
workspace
.cursor_sessions()
.into_iter()
.chain(
maybe_fullscreen
.map(|w| w.cursor_sessions())
.into_iter()
.flatten(),
)
.chain(output.cursor_sessions())
})
}

View file

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

View file

@ -254,6 +254,14 @@ impl CosmicMapped {
.any(|(w, _)| w.has_surface(surface, surface_type))
}
pub fn surface_offset(&self, surface: &WlSurface) -> Option<Point<i32, Logical>> {
self.windows().find_map(|(window, window_offset)| {
window
.surface_offset(surface)
.map(|offset| window_offset + offset)
})
}
/// Give the pointer target under a relative offset into this element.
///
/// Returns Target + Offset relative to the target
@ -261,10 +269,13 @@ impl CosmicMapped {
&self,
relative_pos: Point<f64, Logical>,
surface_type: WindowSurfaceType,
seat: &Seat<State>,
) -> Option<(PointerFocusTarget, Point<f64, Logical>)> {
match &self.element {
CosmicMappedInternal::Stack(stack) => stack.focus_under(relative_pos, surface_type),
CosmicMappedInternal::Window(window) => window.focus_under(relative_pos, surface_type),
CosmicMappedInternal::Window(window) => {
window.focus_under(relative_pos, surface_type, Some(seat))
}
_ => unreachable!(),
}
}

View file

@ -61,8 +61,8 @@ use smithay::{
PointerTarget, RelativeMotionEvent,
},
touch::{
DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget,
UpEvent,
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
},
},
output::Output,
@ -79,7 +79,7 @@ use std::{
hash::Hash,
sync::{
Arc, LazyLock, Mutex,
atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicUsize, Ordering},
},
};
@ -114,6 +114,7 @@ pub struct CosmicStackInternal {
scroll_to_focus: AtomicBool,
previous_keyboard: AtomicUsize,
pointer_entered: AtomicU8,
touch_serial: AtomicU32,
reenter: AtomicBool,
potential_drag: Mutex<Option<usize>>,
override_alive: AtomicBool,
@ -171,6 +172,7 @@ impl CosmicStack {
scroll_to_focus: AtomicBool::new(false),
previous_keyboard: AtomicUsize::new(0),
pointer_entered: AtomicU8::new(0),
touch_serial: AtomicU32::new(0),
reenter: AtomicBool::new(false),
potential_drag: Mutex::new(None),
override_alive: AtomicBool::new(true),
@ -249,6 +251,10 @@ impl CosmicStack {
window.set_tiled(false);
p.active.fetch_min(windows.len() - 1, Ordering::SeqCst);
p.previous_index
.lock()
.unwrap()
.take_if(|(_, idx)| *idx >= windows.len());
});
self.0
.resize(Size::from((self.active().geometry().size.w, TAB_HEIGHT)));
@ -276,6 +282,10 @@ impl CosmicStack {
window.set_tiled(false);
p.active.fetch_min(windows.len() - 1, Ordering::SeqCst);
p.previous_index
.lock()
.unwrap()
.take_if(|(_, idx)| *idx >= windows.len());
Some(window)
});
@ -1807,55 +1817,62 @@ impl PointerTarget<State> for CosmicStack {
}
impl TouchTarget<State> for CosmicStack {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent, seq: Serial) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
let mut event = event.clone();
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();
TouchTarget::down(&self.0, seat, data, &event, seq)
self.0
.with_program(|p| p.touch_serial.store(event.serial.into(), Ordering::Release));
TouchTarget::down(&self.0, seat, data, &event)
}
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent, seq: Serial) {
TouchTarget::up(&self.0, seat, data, event, seq)
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
TouchTarget::up(&self.0, seat, data, event)
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent, seq: Serial) {
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent) {
let mut event = event.clone();
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();
TouchTarget::motion(&self.0, seat, data, &event, seq);
TouchTarget::motion(&self.0, seat, data, &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, seq);
self.start_drag(
data,
seat,
self.0
.with_program(|p| p.touch_serial.load(Ordering::Acquire))
.into(),
);
}
}
fn frame(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
TouchTarget::frame(&self.0, seat, data, seq)
fn frame(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
TouchTarget::frame(&self.0, seat, data, frame)
}
fn cancel(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
TouchTarget::cancel(&self.0, seat, data, seq)
fn cancel(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
TouchTarget::cancel(&self.0, seat, data, frame)
}
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent, seq: Serial) {
TouchTarget::shape(&self.0, seat, data, event, seq)
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent) {
TouchTarget::shape(&self.0, seat, data, event)
}
fn orientation(
&self,
_seat: &Seat<State>,
_data: &mut State,
_event: &OrientationEvent,
_seq: Serial,
) {
fn orientation(&self, seat: &Seat<State>, data: &mut State, event: &OrientationEvent) {
TouchTarget::orientation(&self.0, seat, data, event)
}
fn last_frame(&self, seat: &Seat<State>, data: &mut State) -> Option<FrameMarker> {
TouchTarget::last_frame(&self.0, seat, data)
}
}

View file

@ -45,7 +45,10 @@ use smithay::{
IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, user_data::UserDataMap,
},
wayland::{
compositor::{SurfaceData, TraversalAction, with_states, with_surface_tree_downward},
compositor::{
SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states,
with_surface_tree_downward,
},
seat::WaylandFocus,
shell::xdg::{
SurfaceCachedState, ToplevelCachedState, ToplevelSurface, XdgToplevelSurfaceData,
@ -235,7 +238,8 @@ impl CosmicSurface {
toplevel.with_pending_state(|state| state.size = Some(geo.size.as_logical()))
}
WindowSurface::X11(surface) => {
let _ = surface.configure_with_sync(geo.as_logical(), None);
let _ =
surface.configure_with_sync(geo.as_logical() + surface.frame_extents(), None);
}
}
}
@ -342,7 +346,7 @@ impl CosmicSurface {
state.is_some_and(|state| state.states.contains(ToplevelState::Resizing))
}))
}
WindowSurface::X11(surface) => surface.pending_geometry().map(|_| true),
WindowSurface::X11(surface) => surface.pending_configure().map(|_| true),
}
}
@ -588,7 +592,7 @@ impl CosmicSurface {
}
})
}
WindowSurface::X11(surface) => surface.pending_geometry().is_none(),
WindowSurface::X11(surface) => surface.pending_configure().is_none(),
}
}
@ -652,6 +656,54 @@ impl CosmicSurface {
}
}
pub fn surface_offset(&self, surface: &WlSurface) -> Option<Point<i32, Logical>> {
match self.0.underlying_surface() {
WindowSurface::Wayland(toplevel) => {
Self::surface_tree_offset(toplevel.wl_surface(), surface)
}
WindowSurface::X11(surface_x11) => {
if surface_x11.wl_surface().as_ref() == Some(surface) {
Some(Point::default())
} else {
None
}
}
}
}
pub fn surface_tree_offset(
root: &WlSurface,
surface: &WlSurface,
) -> Option<Point<i32, Logical>> {
let mut offset = Point::<i32, Logical>::default();
let mut parent = surface.clone();
loop {
if parent == *root {
return Some(offset);
} else if let Some(s) = get_parent(&parent) {
offset += with_states(&parent, |states| {
states
.cached_state
.get::<SubsurfaceCachedState>()
.current()
.location
});
parent = s;
} else {
// `parent` is now root of subsurface tree; `surface` is not a subsurface child of `root`
break;
}
}
for (popup, popup_offset) in PopupManager::popups_for_surface(root) {
if let Some(offset) = Self::surface_tree_offset(popup.wl_surface(), surface) {
return Some(popup_offset + offset);
}
}
None
}
pub fn focus_under(
&self,
relative_pos: Point<f64, Logical>,

View file

@ -47,8 +47,8 @@ use smithay::{
GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent,
},
touch::{
DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget,
UpEvent,
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
},
},
output::Output,
@ -57,7 +57,7 @@ use smithay::{
Buffer, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, Transform,
user_data::UserDataMap,
},
wayland::seat::WaylandFocus,
wayland::{pointer_constraints::with_pointer_constraint, seat::WaylandFocus},
};
use std::{
borrow::Cow,
@ -274,12 +274,25 @@ impl CosmicWindow {
&self,
mut relative_pos: Point<f64, Logical>,
surface_type: WindowSurfaceType,
seat: Option<&Seat<State>>,
) -> Option<(PointerFocusTarget, Point<f64, Logical>)> {
let has_constraint = if let Some(seat) = seat
&& let Some(pointer) = seat.get_pointer()
&& let Some(surface) = self.wl_surface()
&& with_pointer_constraint(&surface, &pointer, |constraint| {
constraint.is_some_and(|c| c.is_active())
}) {
true
} else {
false
};
self.0.with_program(|p| {
let mut offset = Point::from((0., 0.));
let mut window_ui = None;
let has_ssd = p.has_ssd(false);
if (has_ssd || p.has_tiled_state())
if (!has_constraint && (has_ssd || p.has_tiled_state()))
&& surface_type.contains(WindowSurfaceType::TOPLEVEL)
{
let geo = p.window.geometry();
@ -877,7 +890,8 @@ impl SpaceElement for CosmicWindow {
})
}
fn is_in_input_region(&self, point: &Point<f64, Logical>) -> bool {
self.focus_under(*point, WindowSurfaceType::ALL).is_some()
self.focus_under(*point, WindowSurfaceType::ALL, None)
.is_some()
}
fn set_activate(&self, activated: bool) {
if self
@ -1176,43 +1190,42 @@ impl PointerTarget<State> for CosmicWindow {
}
impl TouchTarget<State> for CosmicWindow {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent, seq: Serial) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
let mut event = event.clone();
self.0.with_program(|p| {
event.location -= p.window.geometry().loc.to_f64();
});
TouchTarget::down(&self.0, seat, data, &event, seq)
TouchTarget::down(&self.0, seat, data, &event)
}
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent, seq: Serial) {
TouchTarget::up(&self.0, seat, data, event, seq)
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
TouchTarget::up(&self.0, seat, data, event)
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent, seq: Serial) {
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent) {
let mut event = event.clone();
event.location -= self.0.with_program(|p| p.window.geometry().loc.to_f64());
TouchTarget::motion(&self.0, seat, data, &event, seq)
TouchTarget::motion(&self.0, seat, data, &event)
}
fn frame(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
TouchTarget::frame(&self.0, seat, data, seq)
fn frame(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
TouchTarget::frame(&self.0, seat, data, frame)
}
fn cancel(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
TouchTarget::cancel(&self.0, seat, data, seq)
fn cancel(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
TouchTarget::cancel(&self.0, seat, data, frame)
}
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent, seq: Serial) {
TouchTarget::shape(&self.0, seat, data, event, seq)
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent) {
TouchTarget::shape(&self.0, seat, data, event)
}
fn orientation(
&self,
_seat: &Seat<State>,
_data: &mut State,
_event: &OrientationEvent,
_seq: Serial,
) {
fn orientation(&self, seat: &Seat<State>, data: &mut State, event: &OrientationEvent) {
TouchTarget::orientation(&self.0, seat, data, event)
}
fn last_frame(&self, seat: &Seat<State>, data: &mut State) -> Option<FrameMarker> {
TouchTarget::last_frame(&self.0, seat, data)
}
}

View file

@ -1,6 +1,6 @@
use crate::{
shell::{CosmicSurface, MinimizedWindow, Shell, Trigger, element::CosmicMapped},
state::Common,
state::{Common, State},
utils::prelude::*,
wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
};
@ -12,6 +12,7 @@ use smithay::{
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
utils::{IsAlive, Point, SERIAL_COUNTER, Serial},
wayland::{
pointer_constraints::with_pointer_constraint,
seat::WaylandFocus,
selection::{data_device::set_data_device_focus, primary_selection::set_primary_focus},
shell::wlr_layer::{KeyboardInteractivity, Layer},
@ -287,22 +288,18 @@ impl Shell {
}
let workspace = &mut set.workspaces[set.active];
if let Some(fullscreen) = workspace.get_fullscreen() {
if self.seats.iter().any(|seat| {
for fs in workspace.get_fullscreen_surfaces() {
let is_focused = self.seats.iter().any(|seat| {
if let Some(KeyboardFocusTarget::Fullscreen(s)) =
seat.get_keyboard().unwrap().current_focus()
{
&s == fullscreen
s == fs.surface
} else {
false
}
}) {
fullscreen.set_activated(true);
fullscreen.send_configure();
} else {
fullscreen.set_activated(false);
fullscreen.send_configure();
}
});
fs.surface.set_activated(is_focused);
fs.surface.send_configure();
}
for focused in focused_windows.iter() {
raise_with_children(&mut workspace.floating_layer, focused);
@ -347,6 +344,20 @@ fn update_focus_state(
) {
// update keyboard focus
if let Some(keyboard) = seat.get_keyboard() {
// remove constraint when target changed
let old_focus = keyboard.current_focus();
if let Some(old_target) = old_focus
&& target != Some(&old_target)
&& let Some(surface) = old_target.wl_surface()
&& let Some(pointer) = seat.get_pointer()
{
with_pointer_constraint(&surface, &pointer, |constraint| {
if let Some(constraint) = constraint {
constraint.deactivate();
}
});
}
if should_update_cursor
&& state.common.config.cosmic_conf.cursor_follows_focus
&& target.is_some()
@ -646,7 +657,9 @@ fn focus_target_is_valid(
.has_node(&node),
KeyboardFocusTarget::Fullscreen(window) => {
let workspace = shell.active_space(output).unwrap();
workspace.get_fullscreen().is_some_and(|w| w == &window)
workspace
.get_fullscreen_surfaces()
.any(|f| f.surface == window)
}
KeyboardFocusTarget::Popup(_) => true,
KeyboardFocusTarget::LockSurface(_) => false,
@ -699,9 +712,9 @@ fn update_focus_target(
.map(KeyboardFocusTarget::Element)
.or_else(|| {
workspace
.get_fullscreen()
.get_fullscreen(seat)
.cloned()
.map(KeyboardFocusTarget::Fullscreen)
.map(|fs| KeyboardFocusTarget::Fullscreen(fs.surface))
})
})
}

View file

@ -112,8 +112,8 @@ fn render_input_order_internal<R: 'static>(
let output_size = output.geometry().size;
// this is more hacky than I would like..
let fullscreen = workspace.fullscreen.as_ref().filter(|f| !f.is_animating());
let seat = shell.seats.last_active();
let fullscreen = workspace.get_fullscreen(seat);
let is_active_workspace = seat.focused_output().is_some_and(|output| {
shell
.active_space(&output)
@ -144,7 +144,7 @@ fn render_input_order_internal<R: 'static>(
let Some(workspace) = shell.workspaces.space_for_handle(previous) else {
return ControlFlow::Break(Err(OutputNoMode));
};
let has_fullscreen = workspace.fullscreen.is_some();
let has_fullscreen = workspace.get_fullscreen(seat).is_some();
let (forward, percentage) = match start {
WorkspaceDelta::Shortcut(st) => (
@ -220,20 +220,18 @@ fn render_input_order_internal<R: 'static>(
.rev()
.filter(|or| {
(*or)
.geometry()
.last_configure()
.as_global()
.intersection(output.geometry())
.is_some()
})
.map(|or| (or, or.geometry().loc.as_global()))
.map(|or| (or, or.last_configure().loc.as_global()))
{
callback(Stage::OverrideRedirect { surface, location })?;
}
// sticky window popups
if !has_focused_fullscreen {
callback(Stage::StickyPopups(&set.sticky_layer))?;
}
callback(Stage::StickyPopups(&set.sticky_layer))?;
}
if element_filter != ElementFilter::LayerShellOnly {
@ -313,11 +311,11 @@ fn render_input_order_internal<R: 'static>(
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
callback(Stage::LayerSurface { layer, location })?;
}
}
// sticky windows
if element_filter != ElementFilter::LayerShellOnly {
callback(Stage::Sticky(&set.sticky_layer))?;
}
// sticky windows
if element_filter != ElementFilter::LayerShellOnly {
callback(Stage::Sticky(&set.sticky_layer))?;
}
if element_filter != ElementFilter::LayerShellOnly {

View file

@ -29,15 +29,18 @@ use smithay::{
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
},
touch::{
DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget,
UpEvent,
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
},
},
reexports::wayland_server::{
Client, DisplayHandle, Resource, backend::ObjectId, protocol::wl_surface::WlSurface,
},
utils::{IsAlive, Logical, Point, Serial, Transform},
wayland::{seat::WaylandFocus, selection::data_device::WlOfferData, session_lock::LockSurface},
wayland::{
compositor::with_states, seat::WaylandFocus, selection::data_device::WlOfferData,
session_lock::LockSurface, shell::xdg::SurfaceCachedState,
},
xwayland::{
X11Surface,
xwm::{XwmId, XwmOfferData},
@ -225,6 +228,63 @@ impl PointerFocusTarget {
_ => false,
}
}
// Update image copy cursor position/hotspot for enter/motion event
fn update_image_copy_cursor_position(
&self,
seat: &Seat<State>,
data: &mut State,
event: &PointerMotionEvent,
) {
let Some(toplevel) = self.toplevel(&data.common.shell.read()) else {
return;
};
let cursor_sessions = toplevel.cursor_sessions();
if cursor_sessions.is_empty() {
return;
}
let cursor_pos = if let Some(wl_surface) = self.wl_surface() {
let surface_offset = toplevel
.surface_offset(&wl_surface)
.unwrap_or(Point::from((0, 0)))
.to_f64();
let geometry_loc = toplevel
.wl_surface()
.and_then(|s| {
with_states(&s, |states| {
states
.cached_state
.get::<SurfaceCachedState>()
.current()
.geometry
.map(|g| g.loc.to_f64())
})
})
.unwrap_or_default();
Some(
(event.location - geometry_loc + surface_offset)
.to_buffer(1.0, Transform::Normal, &toplevel.geometry().size.to_f64())
.to_i32_round(),
)
} else {
// If cursor is in SSD, instead of a `wl_surface`, it is outside the captured bounds
None
};
let cursor_hotspot = if let Some((_, hotspot)) =
seat.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into())
{
hotspot
} else {
Point::from((0, 0))
};
for session in cursor_sessions {
session.set_cursor_pos(cursor_pos);
session.set_cursor_hotspot(cursor_hotspot);
}
}
}
impl KeyboardFocusTarget {
@ -281,6 +341,18 @@ impl KeyboardFocusTarget {
false
}
}
pub fn has_surface(&self, shell: &Shell, surface: &WlSurface) -> bool {
if let Some(fe) = shell.focused_element(self) {
fe.has_surface(surface, WindowSurfaceType::ALL)
} else if let KeyboardFocusTarget::Fullscreen(s) = self {
s.has_surface(surface, WindowSurfaceType::ALL)
} else if let Some(root) = WaylandFocus::wl_surface(self) {
CosmicSurface::surface_tree_offset(&root, surface).is_some()
} else {
false
}
}
}
#[derive(Debug, Clone)]
@ -325,47 +397,11 @@ impl IsAlive for KeyboardFocusTarget {
impl PointerTarget<State> for PointerFocusTarget {
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
let toplevel = self.toplevel(&data.common.shell.read());
if let Some(element) = toplevel {
for session in element.cursor_sessions() {
session.set_cursor_pos(Some(
event
.location
.to_buffer(1.0, Transform::Normal, &element.geometry().size.to_f64())
.to_i32_round(),
));
if let Some((_, hotspot)) = seat
.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into())
{
session.set_cursor_hotspot(hotspot);
} else {
session.set_cursor_hotspot((0, 0));
}
}
}
self.update_image_copy_cursor_position(seat, data, event);
self.inner_pointer_target().enter(seat, data, event);
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
let toplevel = self.toplevel(&data.common.shell.read());
if let Some(element) = toplevel {
for session in element.cursor_sessions() {
session.set_cursor_pos(Some(
event
.location
.to_buffer(1.0, Transform::Normal, &element.geometry().size.to_f64())
.to_i32_round(),
));
if let Some((_, hotspot)) = seat
.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into())
{
session.set_cursor_hotspot(hotspot);
} else {
session.set_cursor_hotspot((0, 0));
}
}
}
self.update_image_copy_cursor_position(seat, data, event);
self.inner_pointer_target().motion(seat, data, event);
}
fn relative_motion(&self, seat: &Seat<State>, data: &mut State, event: &RelativeMotionEvent) {
@ -462,39 +498,36 @@ impl PointerTarget<State> for PointerFocusTarget {
}
impl TouchTarget<State> for PointerFocusTarget {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent, seq: Serial) {
self.inner_touch_target().down(seat, data, event, seq);
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
self.inner_touch_target().down(seat, data, event);
}
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent, seq: Serial) {
self.inner_touch_target().up(seat, data, event, seq);
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
self.inner_touch_target().up(seat, data, event);
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent, seq: Serial) {
self.inner_touch_target().motion(seat, data, event, seq);
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent) {
self.inner_touch_target().motion(seat, data, event);
}
fn frame(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
self.inner_touch_target().frame(seat, data, seq);
fn frame(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
self.inner_touch_target().frame(seat, data, frame);
}
fn cancel(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
self.inner_touch_target().cancel(seat, data, seq);
fn cancel(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
self.inner_touch_target().cancel(seat, data, frame);
}
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent, seq: Serial) {
self.inner_touch_target().shape(seat, data, event, seq);
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent) {
self.inner_touch_target().shape(seat, data, event);
}
fn orientation(
&self,
seat: &Seat<State>,
data: &mut State,
event: &OrientationEvent,
seq: Serial,
) {
self.inner_touch_target()
.orientation(seat, data, event, seq);
fn orientation(&self, seat: &Seat<State>, data: &mut State, event: &OrientationEvent) {
self.inner_touch_target().orientation(seat, data, event);
}
fn last_frame(&self, seat: &Seat<State>, data: &mut State) -> Option<FrameMarker> {
self.inner_touch_target().last_frame(seat, data)
}
}

View file

@ -202,19 +202,12 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
event: &DownEvent,
seq: Serial,
) {
handle.down(data, focus, event, seq);
handle.down(data, focus, event);
}
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &UpEvent,
seq: Serial,
) {
handle.up(data, event, seq);
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
handle.up(data, event);
if event.slot == TouchGrab::start_data(self).slot {
handle.unset_grab(self, data);
@ -227,9 +220,8 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
event: &TouchMotionEvent,
seq: Serial,
) {
handle.motion(data, focus, event, seq);
handle.motion(data, focus, event);
let distance = self.start_data.distance(event.location);
if distance >= 1.
@ -245,12 +237,12 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
}
}
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
handle.frame(data, seq)
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.frame(data)
}
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
handle.cancel(data, seq);
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.cancel(data);
handle.unset_grab(self, data);
}
@ -259,9 +251,8 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &ShapeEvent,
seq: Serial,
) {
handle.shape(data, event, seq)
handle.shape(data, event)
}
fn orientation(
@ -269,9 +260,8 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &OrientationEvent,
seq: Serial,
) {
handle.orientation(data, event, seq);
handle.orientation(data, event);
}
fn start_data(&self) -> &TouchGrabStartData<State> {

View file

@ -720,7 +720,6 @@ impl TouchGrab<State> for MenuGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
seq: Serial,
) {
{
let mut guard = self.elements.lock().unwrap();
@ -755,21 +754,15 @@ impl TouchGrab<State> for MenuGrab {
time: event.time,
};
if element.touch_entered.is_none() {
TouchTarget::down(&element.iced, &self.seat, data, &new_event, seq);
TouchTarget::down(&element.iced, &self.seat, data, &new_event);
element.touch_entered = Some(event.slot);
}
}
}
handle.down(data, None, event, seq);
handle.down(data, None, event);
}
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &UpEvent,
seq: Serial,
) {
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
{
let elements = self.elements.lock().unwrap();
for element in elements.iter().filter(|elem| {
@ -777,7 +770,7 @@ impl TouchGrab<State> for MenuGrab {
.as_ref()
.is_some_and(|slot| *slot == event.slot)
}) {
TouchTarget::up(&element.iced, &self.seat, data, event, seq);
TouchTarget::up(&element.iced, &self.seat, data, event);
}
}
handle.unset_grab(self, data);
@ -789,7 +782,6 @@ impl TouchGrab<State> for MenuGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &TouchMotionEvent,
seq: Serial,
) {
{
let elements = self.elements.lock().unwrap();
@ -798,24 +790,24 @@ impl TouchGrab<State> for MenuGrab {
.as_ref()
.is_some_and(|slot| *slot == event.slot)
}) {
TouchTarget::motion(&element.iced, &self.seat, data, event, seq);
TouchTarget::motion(&element.iced, &self.seat, data, event);
}
}
handle.motion(data, None, event, seq);
handle.motion(data, None, event);
}
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
handle.frame(data, seq);
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.frame(data);
}
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
{
let mut elements = self.elements.lock().unwrap();
for element in elements.iter_mut() {
let _ = element.touch_entered.take();
}
}
handle.cancel(data, seq);
handle.cancel(data);
}
fn shape(
@ -823,9 +815,8 @@ impl TouchGrab<State> for MenuGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &smithay::input::touch::ShapeEvent,
seq: Serial,
) {
handle.shape(data, event, seq);
handle.shape(data, event);
}
fn orientation(
@ -833,9 +824,8 @@ impl TouchGrab<State> for MenuGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &smithay::input::touch::OrientationEvent,
seq: Serial,
) {
handle.orientation(data, event, seq);
handle.orientation(data, event);
}
fn start_data(&self) -> &TouchGrabStartData<State> {

View file

@ -387,24 +387,17 @@ impl TouchGrab<State> for ResizeGrab {
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
seq: Serial,
) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event, seq),
ResizeGrab::Tiling(grab) => TouchGrab::down(grab, data, handle, focus, event, seq),
ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event),
ResizeGrab::Tiling(grab) => TouchGrab::down(grab, data, handle, focus, event),
}
}
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &UpEvent,
seq: Serial,
) {
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event, seq),
ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event, seq),
ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event),
}
}
@ -414,25 +407,24 @@ impl TouchGrab<State> for ResizeGrab {
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &TouchMotionEvent,
seq: Serial,
) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq),
ResizeGrab::Tiling(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq),
ResizeGrab::Floating(grab) => TouchGrab::motion(grab, data, handle, focus, event),
ResizeGrab::Tiling(grab) => TouchGrab::motion(grab, data, handle, focus, event),
}
}
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::frame(grab, data, handle, seq),
ResizeGrab::Tiling(grab) => TouchGrab::frame(grab, data, handle, seq),
ResizeGrab::Floating(grab) => TouchGrab::frame(grab, data, handle),
ResizeGrab::Tiling(grab) => TouchGrab::frame(grab, data, handle),
}
}
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::cancel(grab, data, handle, seq),
ResizeGrab::Tiling(grab) => TouchGrab::cancel(grab, data, handle, seq),
ResizeGrab::Floating(grab) => TouchGrab::cancel(grab, data, handle),
ResizeGrab::Tiling(grab) => TouchGrab::cancel(grab, data, handle),
}
}
@ -441,11 +433,10 @@ impl TouchGrab<State> for ResizeGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &ShapeEvent,
seq: Serial,
) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::shape(grab, data, handle, event, seq),
ResizeGrab::Tiling(grab) => TouchGrab::shape(grab, data, handle, event, seq),
ResizeGrab::Floating(grab) => TouchGrab::shape(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TouchGrab::shape(grab, data, handle, event),
}
}
@ -454,11 +445,10 @@ impl TouchGrab<State> for ResizeGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &OrientationEvent,
seq: Serial,
) {
match self {
ResizeGrab::Floating(grab) => TouchGrab::orientation(grab, data, handle, event, seq),
ResizeGrab::Tiling(grab) => TouchGrab::orientation(grab, data, handle, event, seq),
ResizeGrab::Floating(grab) => TouchGrab::orientation(grab, data, handle, event),
ResizeGrab::Tiling(grab) => TouchGrab::orientation(grab, data, handle, event),
}
}
@ -729,24 +719,17 @@ impl TouchGrab<State> for MoveGrab {
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
seq: Serial,
) {
match self {
MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event, seq),
MoveGrab::Delayed(grab) => TouchGrab::down(grab, data, handle, focus, event, seq),
MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event),
MoveGrab::Delayed(grab) => TouchGrab::down(grab, data, handle, focus, event),
}
}
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &UpEvent,
seq: Serial,
) {
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
match self {
MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event, seq),
MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event, seq),
MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event),
MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event),
}
}
@ -756,25 +739,24 @@ impl TouchGrab<State> for MoveGrab {
handle: &mut TouchInnerHandle<'_, State>,
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &TouchMotionEvent,
seq: Serial,
) {
match self {
MoveGrab::Move(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq),
MoveGrab::Delayed(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq),
MoveGrab::Move(grab) => TouchGrab::motion(grab, data, handle, focus, event),
MoveGrab::Delayed(grab) => TouchGrab::motion(grab, data, handle, focus, event),
}
}
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
match self {
MoveGrab::Move(grab) => TouchGrab::frame(grab, data, handle, seq),
MoveGrab::Delayed(grab) => TouchGrab::frame(grab, data, handle, seq),
MoveGrab::Move(grab) => TouchGrab::frame(grab, data, handle),
MoveGrab::Delayed(grab) => TouchGrab::frame(grab, data, handle),
}
}
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
match self {
MoveGrab::Move(grab) => TouchGrab::cancel(grab, data, handle, seq),
MoveGrab::Delayed(grab) => TouchGrab::cancel(grab, data, handle, seq),
MoveGrab::Move(grab) => TouchGrab::cancel(grab, data, handle),
MoveGrab::Delayed(grab) => TouchGrab::cancel(grab, data, handle),
}
}
@ -783,11 +765,10 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &ShapeEvent,
seq: Serial,
) {
match self {
MoveGrab::Move(grab) => TouchGrab::shape(grab, data, handle, event, seq),
MoveGrab::Delayed(grab) => TouchGrab::shape(grab, data, handle, event, seq),
MoveGrab::Move(grab) => TouchGrab::shape(grab, data, handle, event),
MoveGrab::Delayed(grab) => TouchGrab::shape(grab, data, handle, event),
}
}
@ -796,11 +777,10 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &OrientationEvent,
seq: Serial,
) {
match self {
MoveGrab::Move(grab) => TouchGrab::orientation(grab, data, handle, event, seq),
MoveGrab::Delayed(grab) => TouchGrab::orientation(grab, data, handle, event, seq),
MoveGrab::Move(grab) => TouchGrab::orientation(grab, data, handle, event),
MoveGrab::Delayed(grab) => TouchGrab::orientation(grab, data, handle, event),
}
}

View file

@ -40,7 +40,7 @@ use smithay::{
touch::{self, GrabStartData as TouchGrabStartData, TouchGrab, TouchInnerHandle},
},
output::Output,
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale, Serial},
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale},
};
use std::{
collections::HashSet,
@ -652,9 +652,8 @@ impl TouchGrab<State> for MoveGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &touch::DownEvent,
seq: Serial,
) {
handle.down(data, None, event, seq)
handle.down(data, None, event)
}
fn up(
@ -662,13 +661,12 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &touch::UpEvent,
seq: Serial,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
handle.unset_grab(self, data);
}
handle.up(data, event, seq);
handle.up(data, event);
}
fn motion(
@ -677,20 +675,19 @@ impl TouchGrab<State> for MoveGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &touch::MotionEvent,
seq: Serial,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
self.update_location(data, event.location);
}
handle.motion(data, None, event, seq);
handle.motion(data, None, event);
}
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
handle.frame(data, seq)
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.frame(data)
}
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, _seq: Serial) {
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.unset_grab(self, data);
}
@ -699,9 +696,8 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &touch::ShapeEvent,
seq: Serial,
) {
handle.shape(data, event, seq)
handle.shape(data, event)
}
fn orientation(
@ -709,9 +705,8 @@ impl TouchGrab<State> for MoveGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &touch::OrientationEvent,
seq: Serial,
) {
handle.orientation(data, event, seq)
handle.orientation(data, event)
}
fn start_data(&self) -> &TouchGrabStartData<State> {
@ -953,6 +948,7 @@ impl Drop for MoveGrab {
if let Some((target, offset)) = mapped.focus_under(
current_location - position.as_logical().to_f64(),
WindowSurfaceType::ALL,
&seat,
) {
pointer.motion(
state,

View file

@ -29,7 +29,7 @@ use smithay::{
},
},
output::Output,
utils::{IsAlive, Logical, Point, Rectangle, Serial, Size},
utils::{IsAlive, Logical, Point, Rectangle, Size},
};
use tracing::debug;
@ -330,23 +330,16 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
seq: Serial,
) {
handle.down(data, None, event, seq)
handle.down(data, None, event)
}
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &UpEvent,
seq: Serial,
) {
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
handle.unset_grab(self, data);
}
handle.up(data, event, seq);
handle.up(data, event);
}
fn motion(
@ -355,7 +348,6 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &TouchMotionEvent,
seq: Serial,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot
&& self.update_location(event.location.as_global())
@ -363,14 +355,14 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
handle.unset_grab(self, data);
}
handle.motion(data, None, event, seq);
handle.motion(data, None, event);
}
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
handle.frame(data, seq)
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.frame(data)
}
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, _seq: Serial) {
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.unset_grab(self, data);
}
@ -379,9 +371,8 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &ShapeEvent,
seq: Serial,
) {
handle.shape(data, event, seq)
handle.shape(data, event)
}
fn orientation(
@ -389,9 +380,8 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &OrientationEvent,
seq: Serial,
) {
handle.orientation(data, event, seq)
handle.orientation(data, event)
}
fn start_data(&self) -> &TouchGrabStartData<State> {
@ -543,7 +533,7 @@ impl ResizeSurfaceGrab {
);
}
WindowSurface::X11(surface) => {
let mut geometry = surface.geometry();
let mut geometry = surface.last_configure();
geometry.loc += (location - new_location).as_logical();
let _ = surface.configure(geometry);
}

View file

@ -295,6 +295,7 @@ impl FloatingLayout {
for mapped in self
.space
.elements()
.filter(|w| w.alive())
.cloned()
.collect::<Vec<_>>()
.into_iter()
@ -309,7 +310,10 @@ impl FloatingLayout {
None,
);
} else {
let geometry = self.space.element_geometry(&mapped).unwrap().to_f64();
let Some(geometry) = self.space.element_geometry(&mapped) else {
continue;
};
let geometry = geometry.to_f64();
let new_loc = (
((geometry.loc.x - old_output_geometry.loc.x).max(0.)
/ old_output_geometry.size.w
@ -619,6 +623,10 @@ impl FloatingLayout {
from: Rectangle<i32, Local>,
position: Point<i32, Local>,
) {
if !mapped.alive() {
return;
}
let output = self.space.outputs().next().unwrap().clone();
let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone().as_local();
@ -731,7 +739,11 @@ impl FloatingLayout {
self.space.element_geometry(elem).map(RectExt::as_local)
}
pub fn popup_element_under(&self, location: Point<f64, Local>) -> Option<KeyboardFocusTarget> {
pub fn popup_element_under(
&self,
location: Point<f64, Local>,
seat: &Seat<State>,
) -> Option<KeyboardFocusTarget> {
self.space
.elements()
.rev()
@ -752,6 +764,7 @@ impl FloatingLayout {
if e.focus_under(
point.as_logical(),
WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE,
seat,
)
.is_some()
{
@ -765,6 +778,7 @@ impl FloatingLayout {
pub fn toplevel_element_under(
&self,
location: Point<f64, Local>,
seat: &Seat<State>,
) -> Option<KeyboardFocusTarget> {
self.space
.elements()
@ -786,6 +800,7 @@ impl FloatingLayout {
if e.focus_under(
point.as_logical(),
WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE,
seat,
)
.is_some()
{
@ -799,6 +814,7 @@ impl FloatingLayout {
pub fn popup_surface_under(
&self,
location: Point<f64, Local>,
seat: &Seat<State>,
) -> Option<(PointerFocusTarget, Point<f64, Local>)> {
self.space
.elements()
@ -820,6 +836,7 @@ impl FloatingLayout {
e.focus_under(
point.as_logical(),
WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE,
seat,
)
.map(|(surface, surface_offset)| {
(surface, render_location + surface_offset.as_local())
@ -830,6 +847,7 @@ impl FloatingLayout {
pub fn toplevel_surface_under(
&self,
location: Point<f64, Local>,
seat: &Seat<State>,
) -> Option<(PointerFocusTarget, Point<f64, Local>)> {
self.space
.elements()
@ -851,6 +869,7 @@ impl FloatingLayout {
e.focus_under(
point.as_logical(),
WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE,
seat,
)
.map(|(surface, surface_offset)| {
(surface, render_location + surface_offset.as_local())

View file

@ -32,7 +32,7 @@ pub fn is_dialog(window: &CosmicSurface) -> bool {
}
WindowSurface::X11(surface) => {
if surface.is_override_redirect()
|| surface.is_popup()
|| surface.is_modal()
|| !matches!(
surface.window_type(),
None | Some(WmWindowType::Normal) | Some(WmWindowType::Utility)

View file

@ -22,12 +22,13 @@ use smithay::{
PointerTarget, RelativeMotionEvent,
},
touch::{
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, TouchTarget, UpEvent,
DownEvent, FrameMarker, GrabStartData as TouchGrabStartData,
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
TouchInnerHandle, TouchTarget, UpEvent,
},
},
output::WeakOutput,
utils::{IsAlive, Logical, Point, Serial},
utils::{IsAlive, Logical, Point},
};
use super::super::{Data, TilingLayout};
@ -126,7 +127,7 @@ impl PointerTarget<State> for ResizeForkTarget {
}
impl TouchTarget<State> for ResizeForkTarget {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent, _seq: Serial) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
let seat = seat.clone();
let node = self.node.clone();
let output = self.output.clone();
@ -157,25 +158,14 @@ impl TouchTarget<State> for ResizeForkTarget {
});
}
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &UpEvent, _seq: Serial) {}
fn motion(
&self,
_seat: &Seat<State>,
_data: &mut State,
_event: &TouchMotionEvent,
_seq: Serial,
) {
}
fn frame(&self, _seat: &Seat<State>, _data: &mut State, _seq: Serial) {}
fn cancel(&self, _seat: &Seat<State>, _data: &mut State, _seq: Serial) {}
fn shape(&self, _seat: &Seat<State>, _data: &mut State, _event: &ShapeEvent, _seq: Serial) {}
fn orientation(
&self,
_seat: &Seat<State>,
_data: &mut State,
_event: &OrientationEvent,
_seq: Serial,
) {
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &UpEvent) {}
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) {}
fn shape(&self, _seat: &Seat<State>, _data: &mut State, _event: &ShapeEvent) {}
fn orientation(&self, _seat: &Seat<State>, _data: &mut State, _event: &OrientationEvent) {}
fn last_frame(&self, _seat: &Seat<State>, _data: &mut State) -> Option<FrameMarker> {
None
}
}
@ -508,23 +498,16 @@ impl TouchGrab<State> for ResizeForkGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &DownEvent,
seq: Serial,
) {
handle.down(data, None, event, seq)
handle.down(data, None, event)
}
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &UpEvent,
seq: Serial,
) {
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
handle.unset_grab(self, data);
}
handle.up(data, event, seq);
handle.up(data, event);
}
fn motion(
@ -533,7 +516,6 @@ impl TouchGrab<State> for ResizeForkGrab {
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
event: &TouchMotionEvent,
seq: Serial,
) {
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot
&& self.update_location(data, event.location, false)
@ -541,14 +523,14 @@ impl TouchGrab<State> for ResizeForkGrab {
handle.unset_grab(self, data);
}
handle.motion(data, None, event, seq);
handle.motion(data, None, event);
}
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
handle.frame(data, seq)
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.frame(data)
}
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, _seq: Serial) {
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) {
handle.unset_grab(self, data);
}
@ -557,9 +539,8 @@ impl TouchGrab<State> for ResizeForkGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &ShapeEvent,
seq: Serial,
) {
handle.shape(data, event, seq)
handle.shape(data, event)
}
fn start_data(&self) -> &TouchGrabStartData<State> {
@ -574,9 +555,8 @@ impl TouchGrab<State> for ResizeForkGrab {
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &OrientationEvent,
seq: Serial,
) {
handle.orientation(data, event, seq)
handle.orientation(data, event)
}
fn unset(&mut self, data: &mut State) {

View file

@ -3145,6 +3145,7 @@ impl TilingLayout {
pub fn popup_element_under(
&self,
location_f64: Point<f64, Local>,
seat: &Seat<State>,
) -> Option<KeyboardFocusTarget> {
let location = location_f64.to_i32_round();
@ -3157,6 +3158,7 @@ impl TilingLayout {
.focus_under(
(location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(),
WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE,
seat,
)
.is_some()
{
@ -3170,6 +3172,7 @@ impl TilingLayout {
pub fn toplevel_element_under(
&self,
location_f64: Point<f64, Local>,
seat: &Seat<State>,
) -> Option<KeyboardFocusTarget> {
let location = location_f64.to_i32_round();
@ -3182,6 +3185,7 @@ impl TilingLayout {
.focus_under(
(location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(),
WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE,
seat,
)
.is_some()
{
@ -3196,6 +3200,7 @@ impl TilingLayout {
&self,
location_f64: Point<f64, Local>,
overview: OverviewMode,
seat: &Seat<State>,
) -> Option<(PointerFocusTarget, Point<f64, Local>)> {
let location = location_f64.to_i32_round();
@ -3210,6 +3215,7 @@ impl TilingLayout {
if let Some((target, surface_offset)) = mapped.focus_under(
(location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(),
WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE,
seat,
) {
return Some((
target,
@ -3227,6 +3233,7 @@ impl TilingLayout {
&self,
location_f64: Point<f64, Local>,
overview: OverviewMode,
seat: &Seat<State>,
) -> Option<(PointerFocusTarget, Point<f64, Local>)> {
let tree = &self.queue.trees.back().unwrap().0;
let root = tree.root_node_id()?;
@ -3243,6 +3250,7 @@ impl TilingLayout {
if let Some((target, surface_offset)) = mapped.focus_under(
(location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(),
WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE,
seat,
) {
return Some((
target,
@ -3293,6 +3301,7 @@ impl TilingLayout {
.focus_under(
test_point,
WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE,
seat,
)
.map(|(surface, surface_offset)| {
(

View file

@ -50,10 +50,13 @@ use smithay::{
},
utils::{IsAlive, Logical, Point, Rectangle, Serial, Size},
wayland::{
compositor::{SurfaceAttributes, with_states},
compositor::{SurfaceAttributes, get_parent, with_states},
seat::WaylandFocus,
session_lock::LockSurface,
shell::wlr_layer::{KeyboardInteractivity, Layer, LayerSurfaceCachedState},
shell::{
wlr_layer::{KeyboardInteractivity, Layer, LayerSurfaceCachedState},
xdg::{XDG_POPUP_ROLE, XdgPopupSurfaceData},
},
xdg_activation::XdgActivationState,
xwayland_keyboard_grab::XWaylandKeyboardGrab,
},
@ -436,6 +439,11 @@ fn create_workspace_from_pinned(
| WorkspaceCapabilities::Pin
| WorkspaceCapabilities::Move,
);
if let Some(ref name) = pinned.name {
state.set_workspace_name(&workspace_handle, name);
}
Workspace::from_pinned(
pinned,
workspace_handle,
@ -616,6 +624,7 @@ impl WorkspaceSet {
state,
self.workspaces.len() as u8 + 1,
&workspace.handle,
workspace.name.as_deref(),
// this method is only used by code paths related to dynamic workspaces, so this should be fine
);
self.workspaces.push(workspace);
@ -677,7 +686,12 @@ impl WorkspaceSet {
fn update_workspace_idxs(&self, state: &mut WorkspaceUpdateGuard<'_, State>) {
for (i, workspace) in self.workspaces.iter().enumerate() {
workspace_set_idx(state, i as u8 + 1, &workspace.handle);
workspace_set_idx(
state,
i as u8 + 1,
&workspace.handle,
workspace.name.as_deref(),
);
}
}
@ -728,6 +742,78 @@ impl WorkspaceSet {
self.post_remove_workspace(workspace_state, &previous_active_handle);
prefers
}
pub fn surface_geometry_offset_from_toplevel(
&self,
surface: &WlSurface,
) -> Option<(Rectangle<i32, Local>, Point<i32, Logical>)> {
let mut root = surface.clone();
while let Some(parent) = get_parent(&root) {
root = parent;
}
while smithay::wayland::compositor::get_role(&root) == Some(XDG_POPUP_ROLE) {
let parent = with_states(&root, |states| {
states
.data_map
.get::<XdgPopupSurfaceData>()
.and_then(|m| m.lock().unwrap().parent.as_ref().cloned())
});
if let Some(parent) = parent {
root = parent;
} else {
break;
}
}
self.sticky_layer
.mapped()
.find(|w| {
w.windows()
.any(|(w, _)| w.wl_surface().as_deref() == Some(&root))
})
.and_then(|w| {
self.sticky_layer
.element_geometry(w)
.zip(w.surface_offset(surface))
})
.or_else(|| {
self.workspaces.iter().find_map(|workspace| {
workspace
.get_fullscreen_surfaces()
.find_map(|fs| {
(fs.surface.wl_surface().as_deref() == Some(&root))
.then(|| {
fs.surface.surface_offset(surface).map(|offset| {
(workspace.fullscreen_geometry_for(fs), offset)
})
})
.flatten()
})
.or_else(|| {
workspace.mapped().find_map(|w| {
w.windows()
.any(|(w, _)| w.wl_surface().as_deref() == Some(&root))
.then(|| {
workspace.element_geometry(w).zip(w.surface_offset(surface))
})
.flatten()
})
})
})
})
.or_else(|| {
layer_map_for_output(&self.output).layers().find_map(|l| {
(l.wl_surface() == &root)
.then(|| {
CosmicSurface::surface_tree_offset(l.wl_surface(), surface)
.map(|offset| (l.geometry().as_local(), offset))
})
.flatten()
})
})
}
}
#[derive(Debug)]
@ -1502,7 +1588,9 @@ impl Common {
self.popups.cleanup();
self.toplevel_info_state.refresh(&self.workspace_state);
self.refresh_idle_inhibit();
self.a11y_keyboard_monitor_state.refresh();
if let Some(mut a11y_keyboard_monitor) = self.dbus_state.a11y_keyboard_monitor() {
a11y_keyboard_monitor.refresh();
}
self.image_copy_capture_state.cleanup();
}
@ -1562,12 +1650,13 @@ impl Common {
if let Some(mapped) = shell.element_for_surface(surface) {
mapped.on_commit(surface);
}
if let Some(surface) = shell
if let Some(fs) = shell
.workspaces
.spaces()
.find_map(|w| w.get_fullscreen().filter(|s| *s == surface))
.flat_map(|w| w.get_fullscreen_surfaces())
.find(|f| f.surface == *surface)
{
surface.on_commit()
fs.surface.on_commit()
};
}
self.popups.commit(surface);
@ -1828,7 +1917,9 @@ impl Shell {
.outputs()
.find(|output| {
let workspace = self.active_space(output).unwrap();
workspace.get_fullscreen() == Some(&elem)
workspace
.get_fullscreen_surfaces()
.any(|f| f.surface == elem)
})
.cloned(),
KeyboardFocusTarget::Group(WindowGroup { node, .. }) => self
@ -1961,8 +2052,8 @@ impl Shell {
let workspace = self.active_space(o).unwrap();
workspace
.get_fullscreen()
.is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL))
.get_fullscreen_surfaces()
.any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL))
|| workspace
.mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
@ -2017,8 +2108,8 @@ impl Shell {
.workspaces
.spaces()
.find(|w| {
w.get_fullscreen()
.is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL))
w.get_fullscreen_surfaces()
.any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL))
|| w.mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
|| w.minimized_windows.iter().any(|m| {
@ -2065,7 +2156,7 @@ impl Shell {
.mapped()
.any(|m| m.windows().any(|(s, _)| &s == surface))
|| set.workspaces.iter().any(|w| {
w.get_fullscreen().is_some_and(|s| s == surface)
w.get_fullscreen_surfaces().any(|f| &f.surface == surface)
|| w.minimized_windows
.iter()
.any(|m| m.windows().any(|s| &s == surface))
@ -2754,12 +2845,7 @@ impl Shell {
let floating_exception = layout::has_floating_exception(&self.tiling_exceptions, &window);
if should_be_fullscreen {
if let Some((surface, state, _)) = workspace.map_fullscreen(&window, &seat, None, None)
{
toplevel_leave_output(&surface, &workspace.output);
toplevel_leave_workspace(&surface, &workspace.handle);
self.remap_unfullscreened_window(surface, state, loop_handle);
}
workspace.map_fullscreen(&window, &seat, None, None);
if was_activated {
workspace_state.add_workspace_state(&workspace_handle, WState::Urgent);
}
@ -3130,9 +3216,12 @@ impl Shell {
let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above
let is_minimized = window.is_minimized();
let is_fullscreen = from_workspace.get_fullscreen().is_some_and(|f| f == window);
let is_fullscreen = from_workspace
.get_fullscreen_surfaces()
.any(|f| &f.surface == window);
let mut window_state = if is_fullscreen {
let (_, previous_state, previous_geometry) = from_workspace.take_fullscreen().unwrap();
let (_, previous_state, previous_geometry) =
from_workspace.take_fullscreen(window).unwrap();
WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map(
|(previous_state, previous_geometry)| FullscreenRestoreData {
previous_state,
@ -3288,14 +3377,12 @@ impl Shell {
);
mapped.into()
} else if let WorkspaceRestoreData::Fullscreen(previous) = window_state {
if let Some((old_surface, previous_state, _)) = to_workspace.map_fullscreen(
to_workspace.map_fullscreen(
window,
None,
previous.clone().map(|p| p.previous_state),
previous.map(|p| p.previous_geometry),
) {
self.remap_unfullscreened_window(old_surface, previous_state, evlh);
}
);
window.clone().into()
} else {
unreachable!() // TODO: sticky
@ -3513,8 +3600,12 @@ impl Shell {
} else if let Some((workspace, output)) = self.workspace_for_surface(surface) {
let workspace = self.workspaces.space_for_handle(&workspace).unwrap();
if let Some(window) = workspace.get_fullscreen().filter(|s| *s == surface) {
let global_position = (workspace.fullscreen_geometry().unwrap().loc
if let Some(fs) = workspace
.get_fullscreen_surfaces()
.find(|f| &f.surface == surface)
{
let window = &fs.surface;
let global_position = (workspace.fullscreen_geometry_for(fs).loc
+ location.as_local())
.to_global(&output);
@ -3607,10 +3698,14 @@ impl Shell {
let maybe_fullscreen_workspace = self
.workspaces
.spaces_mut()
.find(|w| w.get_fullscreen().is_some_and(|s| s == surface));
.find(|w| w.get_fullscreen_surfaces().any(|f| &f.surface == surface));
if let Some(workspace) = maybe_fullscreen_workspace {
element_geo = Some(workspace.fullscreen_geometry().unwrap());
let (surface, state, _) = workspace.remove_fullscreen().unwrap();
let fs = workspace
.get_fullscreen_surfaces()
.find(|f| &f.surface == surface)
.unwrap();
element_geo = Some(workspace.fullscreen_geometry_for(fs));
let (surface, state, _) = workspace.remove_fullscreen_surface(surface).unwrap();
self.remap_unfullscreened_window(surface, state, evlh);
};
@ -3635,7 +3730,11 @@ impl Shell {
if old_mapped.is_maximized(false) {
new_mapped.set_maximized(false);
}
start_data.set_focus(new_mapped.focus_under((0., 0.).into(), WindowSurfaceType::ALL));
start_data.set_focus(new_mapped.focus_under(
(0., 0.).into(),
WindowSurfaceType::ALL,
seat,
));
new_mapped
} else {
old_mapped.clone()
@ -3819,19 +3918,15 @@ impl Shell {
/// Get the window geometry of a keyboard focus target
pub fn focused_geometry(&self, target: &KeyboardFocusTarget) -> Option<Rectangle<i32, Global>> {
match target {
KeyboardFocusTarget::Fullscreen(surface) => {
if let Some(workspace) = surface
.wl_surface()
.and_then(|s| self.workspace_for_surface(&s))
.and_then(|(handle, _)| self.workspaces.space_for_handle(&handle))
{
KeyboardFocusTarget::Fullscreen(surface) => surface
.wl_surface()
.and_then(|s| self.workspace_for_surface(&s))
.and_then(|(handle, _)| self.workspaces.space_for_handle(&handle))
.map(|workspace| {
workspace
.fullscreen_geometry()
.map(|f| f.to_global(workspace.output()))
} else {
None
}
}
.fullscreen_geometry_for_surface(surface)
.to_global(workspace.output())
}),
_ => {
if let Some(element) = self.focused_element(target) {
self.element_geometry(&element)
@ -4106,7 +4201,7 @@ impl Shell {
let element_offset = (new_loc - geometry.loc).as_logical();
let focus = mapped
.focus_under(element_offset.to_f64(), WindowSurfaceType::ALL)
.focus_under(element_offset.to_f64(), WindowSurfaceType::ALL, seat)
.map(|(target, surface_offset)| (target, (surface_offset + element_offset.to_f64())));
start_data.set_location(new_loc.as_logical().to_f64());
start_data.set_focus(focus.clone());
@ -4183,9 +4278,8 @@ impl Shell {
self.workspaces.sets.values_mut().find_map(|set| {
set.workspaces.iter_mut().find_map(|workspace| {
let window = workspace
.get_fullscreen()
.cloned()
.into_iter()
.get_fullscreen_surfaces()
.map(|f| f.surface.clone())
.chain(workspace.mapped().map(|m| m.active_window()))
.find(|s| s == surface);
window.map(|s| (workspace, s))
@ -4685,15 +4779,16 @@ impl Shell {
&mut self,
surface: &S,
output: Output,
loop_handle: &LoopHandle<'static, State>,
_loop_handle: &LoopHandle<'static, State>,
) -> Option<KeyboardFocusTarget>
where
CosmicSurface: PartialEq<S>,
{
let mapped = self.element_for_surface(surface).cloned()?;
let seat = self.seats.last_active().clone();
let window;
let old_fullscreen = if let Some((old_output, set)) = self
if let Some((old_output, set)) = self
.workspaces
.sets
.iter_mut()
@ -4740,7 +4835,7 @@ impl Shell {
let workspace = self.active_space_mut(&output).unwrap();
workspace.map_fullscreen(
&window,
None,
&seat,
Some(FullscreenRestoreState::Sticky {
output: old_output,
state: FloatingRestoreData {
@ -4751,7 +4846,7 @@ impl Shell {
},
}),
Some(from),
)
);
} else if let Some(workspace) = self.space_for_mut(&mapped) {
if mapped.is_minimized() {
// TODO: Rewrite the `MinimizedWindow` to restore to fullscreen
@ -4777,7 +4872,7 @@ impl Shell {
workspace.map_fullscreen(
&window,
None,
&seat,
match state {
WorkspaceRestoreData::Floating(floating_state) => {
floating_state.map(|state| FullscreenRestoreState::Floating {
@ -4794,15 +4889,11 @@ impl Shell {
WorkspaceRestoreData::Fullscreen(_) => unreachable!(),
},
Some(from),
)
);
} else {
return None;
};
if let Some((old_fullscreen, restore, _)) = old_fullscreen {
self.remap_unfullscreened_window(old_fullscreen, restore, loop_handle);
}
Some(KeyboardFocusTarget::Fullscreen(window))
}
@ -4817,11 +4908,12 @@ impl Shell {
let maybe_workspace = self.workspaces.iter_mut().find_map(|(_, s)| {
s.workspaces
.iter_mut()
.find(|w| w.get_fullscreen().is_some_and(|f| f == surface))
.find(|w| w.get_fullscreen_surfaces().any(|f| &f.surface == surface))
});
if let Some(workspace) = maybe_workspace {
let (old_fullscreen, restore, _) = workspace.remove_fullscreen().unwrap();
let (old_fullscreen, restore, _) =
workspace.remove_fullscreen_surface(surface).unwrap();
toplevel_leave_output(&old_fullscreen, &workspace.output);
toplevel_leave_workspace(&old_fullscreen, &workspace.handle);
@ -4944,8 +5036,9 @@ fn workspace_set_idx(
state: &mut WorkspaceUpdateGuard<'_, State>,
idx: u8,
handle: &WorkspaceHandle,
name: Option<&str>,
) {
state.set_workspace_name(handle, format!("{}", idx));
state.set_workspace_name(handle, name.unwrap_or(&format!("{}", idx)));
state.set_workspace_coordinates(handle, &[idx as u32]);
}

View file

@ -17,8 +17,11 @@ use smithay::{
pointer::{CursorImageAttributes, CursorImageStatus},
},
output::Output,
reexports::{input::Device as InputDevice, wayland_server::DisplayHandle},
utils::{Buffer, IsAlive, Monotonic, Point, Rectangle, Serial, Time, Transform},
reexports::{
input::Device as InputDevice,
wayland_server::{DisplayHandle, protocol::wl_surface::WlSurface},
},
utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Serial, Time, Transform},
wayland::compositor::with_states,
};
use tracing::warn;
@ -178,6 +181,9 @@ struct ActiveOutput(pub Mutex<Output>);
/// The output which currently has keyboard focus
struct FocusedOutput(pub Mutex<Option<Output>>);
#[derive(Default)]
pub struct PointerConstraintHint(pub Mutex<Option<(WlSurface, Point<f64, Logical>)>>);
#[derive(Default)]
pub struct LastModifierChange(pub Mutex<Option<Serial>>);
@ -201,6 +207,7 @@ pub fn create_seat(
userdata.insert_if_missing_threadsafe(CursorState::default);
userdata.insert_if_missing_threadsafe(|| ActiveOutput(Mutex::new(output.clone())));
userdata.insert_if_missing_threadsafe(|| FocusedOutput(Mutex::new(None)));
userdata.insert_if_missing_threadsafe(PointerConstraintHint::default);
userdata.insert_if_missing_threadsafe(|| Mutex::new(CursorImageStatus::default_named()));
// A lot of clients bind keyboard and pointer unconditionally once on launch..
@ -252,6 +259,8 @@ pub trait SeatExt {
fn supressed_buttons(&self) -> &SupressedButtons;
fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue;
fn last_modifier_change(&self) -> Option<Serial>;
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)>;
fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point<f64, Logical>)>);
fn cursor_geometry(
&self,
@ -337,6 +346,23 @@ impl SeatExt for Seat<State> {
.unwrap()
}
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)> {
let lock = self.user_data().get::<PointerConstraintHint>().unwrap();
let mut hint = lock.0.lock().unwrap();
// Check if alive
if let Some((ref surface, _)) = *hint
&& !surface.alive()
{
*hint = None;
}
hint.clone()
}
fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point<f64, Logical>)>) {
let lock = self.user_data().get::<PointerConstraintHint>().unwrap();
*lock.0.lock().unwrap() = hint;
}
fn cursor_geometry(
&self,
loc: impl Into<Point<f64, Buffer>>,

View file

@ -108,9 +108,10 @@ pub struct Workspace {
pub floating_layer: FloatingLayout,
pub minimized_windows: Vec<MinimizedWindow>,
pub tiling_enabled: bool,
pub fullscreen: Option<FullscreenSurface>,
pub fullscreen_surfaces: Vec<FullscreenSurface>,
pub pinned: bool,
pub id: Option<String>,
pub name: Option<String>,
pub handle: WorkspaceHandle,
pub focus_stack: FocusStacks,
@ -383,9 +384,10 @@ impl Workspace {
floating_layer,
tiling_enabled,
minimized_windows: Vec::new(),
fullscreen: None,
fullscreen_surfaces: Vec::new(),
pinned: false,
id: None,
name: None,
handle,
focus_stack: FocusStacks::default(),
image_copy: ImageCopySessions::default(),
@ -416,9 +418,10 @@ impl Workspace {
floating_layer,
tiling_enabled: pinned.tiling_enabled,
minimized_windows: Vec::new(),
fullscreen: None,
fullscreen_surfaces: Vec::new(),
pinned: true,
id: pinned.id.clone(),
name: pinned.name.clone(),
handle,
focus_stack: FocusStacks::default(),
image_copy: ImageCopySessions::default(),
@ -436,9 +439,9 @@ impl Workspace {
}
pub fn to_pinned(&self) -> Option<PinnedWorkspace> {
debug_assert!(self.id.is_some());
let output = self.explicit_output().clone();
if self.pinned {
debug_assert!(self.id.is_some());
Some(PinnedWorkspace {
output: cosmic_comp_config::workspace::OutputMatch {
name: output.name,
@ -446,6 +449,7 @@ impl Workspace {
},
tiling_enabled: self.tiling_enabled,
id: self.id.clone(),
name: self.name.clone(),
})
} else {
None
@ -454,7 +458,10 @@ impl Workspace {
#[profiling::function]
pub fn refresh(&mut self) {
self.fullscreen.take_if(|w| !w.alive());
// seems it removes dead windows
// self.fullscreen.take_if(|w| !w.alive());
self.fullscreen_surfaces.retain(|w| w.alive());
self.floating_layer.refresh();
self.tiling_layer.refresh();
}
@ -478,14 +485,15 @@ impl Workspace {
self.is_empty() && !self.has_activation_token(xdg_activation_state) && !self.pinned
}
/// cleans up any window that is not alive anymore
pub fn refresh_focus_stack(&mut self) {
for (seat, stack) in self.focus_stack.0.iter_mut() {
let fullscreen = self
.fullscreen
.as_ref()
.filter(|f| f.alive())
.filter(|f| f.ended_at.is_none())
.map(|f| &f.surface);
let fullscreen_surfaces: Vec<&CosmicSurface> = self
.fullscreen_surfaces
.iter()
.filter(|f| f.alive() && f.ended_at.is_none())
.map(|f| &f.surface)
.collect();
// Move grab is treated as focused, so don't change focus to a
// window while grab exists.
@ -506,7 +514,7 @@ impl Workspace {
.chain(move_mapped.iter())
};
stack.retain(|w| match w {
FocusTarget::Fullscreen(s) => fullscreen.is_some_and(|f| f == s),
FocusTarget::Fullscreen(s) => fullscreen_surfaces.contains(&s),
FocusTarget::Window(w) => mapped().any(|m| w == m),
});
}
@ -515,15 +523,12 @@ impl Workspace {
pub fn animations_going(&self) -> bool {
self.tiling_layer.animations_going()
|| self.floating_layer.animations_going()
|| self
.fullscreen
.as_ref()
.is_some_and(|f| f.start_at.is_some() || f.ended_at.is_some())
|| self.fullscreen_surfaces.iter().any(|f| f.is_animating())
|| self.dirty.swap(false, Ordering::SeqCst)
}
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
if let Some(f) = self.fullscreen.as_mut() {
for f in self.fullscreen_surfaces.iter_mut() {
if let Some(start) = f.start_at.as_ref() {
let duration_since = Instant::now().duration_since(*start);
if duration_since > FULLSCREEN_ANIMATION_DURATION {
@ -531,16 +536,18 @@ impl Workspace {
self.dirty.store(true, Ordering::SeqCst);
}
}
if let Some(end) = f.ended_at {
let duration_since = Instant::now().duration_since(end);
if duration_since >= FULLSCREEN_ANIMATION_DURATION {
let _ = self.fullscreen.take();
self.dirty.store(true, Ordering::SeqCst);
}
}
}
self.fullscreen_surfaces.retain(|f| {
if let Some(end) = f.ended_at
&& Instant::now().duration_since(end) >= FULLSCREEN_ANIMATION_DURATION
{
self.dirty.store(true, Ordering::SeqCst);
return false;
}
true
});
let clients = self.tiling_layer.update_animation_state();
self.floating_layer.update_animation_state();
clients
@ -574,7 +581,11 @@ impl Workspace {
toplevel_enter_output(&surface, output);
}
}
if let Some(f) = self.fullscreen.as_ref().filter(|f| f.ended_at.is_none()) {
for f in self
.fullscreen_surfaces
.iter()
.filter(|f| f.ended_at.is_none())
{
toplevel_leave_output(&f.surface, &self.output);
toplevel_enter_output(&f.surface, output);
}
@ -665,12 +676,13 @@ impl Workspace {
where
CosmicSurface: PartialEq<S>,
{
if self
.fullscreen
.as_ref()
.is_some_and(|f| f.ended_at.is_none() && &f.surface == surface)
if let Some(idx) = self
.fullscreen_surfaces
.iter()
.position(|f| f.ended_at.is_none() && &f.surface == surface)
{
let (surface, previous_state, previous_geometry) = self.remove_fullscreen().unwrap();
let (surface, previous_state, previous_geometry) =
self.remove_fullscreen_at(idx).unwrap();
return Some((
surface,
WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map(
@ -723,24 +735,28 @@ impl Workspace {
Some((mapped.active_window(), layer))
}
pub fn fullscreen_geometry(&self) -> Option<Rectangle<i32, Local>> {
self.fullscreen.as_ref().map(|fullscreen| {
let bbox = fullscreen.surface.bbox().as_local();
pub fn fullscreen_geometry_for_surface(
&self,
surface: &CosmicSurface,
) -> Rectangle<i32, Local> {
let bbox = surface.bbox().as_local();
let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local());
if bbox != full_geo {
if bbox.size.w < full_geo.size.w {
full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2;
full_geo.size.w = bbox.size.w;
}
if bbox.size.h < full_geo.size.h {
full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2;
full_geo.size.h = bbox.size.h;
}
let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local());
if bbox != full_geo {
if bbox.size.w < full_geo.size.w {
full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2;
full_geo.size.w = bbox.size.w;
}
if bbox.size.h < full_geo.size.h {
full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2;
full_geo.size.h = bbox.size.h;
}
}
full_geo
})
full_geo
}
pub fn fullscreen_geometry_for(&self, fullscreen: &FullscreenSurface) -> Rectangle<i32, Local> {
self.fullscreen_geometry_for_surface(&fullscreen.surface)
}
pub fn element_for_surface<S>(&self, surface: &S) -> Option<&CosmicMapped>
@ -780,24 +796,23 @@ impl Workspace {
let stack = self.focus_stack.get(seat);
let last_focused = stack.last();
if let Some(fullscreen) = self.fullscreen.as_ref()
&& last_focused.is_some_and(
|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface),
)
&& !fullscreen.is_animating()
{
let geometry = self.fullscreen_geometry().unwrap();
if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|f| {
!f.is_animating()
&& last_focused
.is_some_and(|t| matches!(t, FocusTarget::Fullscreen(s) if s == &f.surface))
}) {
let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen_element_under(fullscreen, geometry);
}
self.floating_layer
.popup_element_under(location)
.or_else(|| self.tiling_layer.popup_element_under(location))
.popup_element_under(location, seat)
.or_else(|| self.tiling_layer.popup_element_under(location, seat))
.or_else(|| {
if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_)))
&& let Some(fullscreen) = self.fullscreen.as_ref()
&& let Some(fullscreen) = self.get_fullscreen(seat)
{
let geometry = self.fullscreen_geometry().unwrap();
let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen_element_under(fullscreen, geometry);
}
None
@ -830,24 +845,23 @@ impl Workspace {
let stack = self.focus_stack.get(seat);
let last_focused = stack.last();
if let Some(fullscreen) = self.fullscreen.as_ref()
&& last_focused.is_some_and(
|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface),
)
&& !fullscreen.is_animating()
{
let geometry = self.fullscreen_geometry().unwrap();
if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|fs| {
!fs.is_animating()
&& last_focused
.is_some_and(|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fs.surface))
}) {
let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen_element_under(fullscreen, geometry);
}
self.floating_layer
.toplevel_element_under(location)
.or_else(|| self.tiling_layer.toplevel_element_under(location))
.toplevel_element_under(location, seat)
.or_else(|| self.tiling_layer.toplevel_element_under(location, seat))
.or_else(|| {
if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_)))
&& let Some(fullscreen) = self.fullscreen.as_ref()
&& let Some(fullscreen) = self.get_fullscreen(seat)
{
let geometry = self.fullscreen_geometry().unwrap();
let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen_element_under(fullscreen, geometry);
}
None
@ -867,7 +881,7 @@ impl Workspace {
let check_fullscreen = |fullscreen: &FullscreenSurface| {
if !fullscreen.is_animating() {
let geometry = self.fullscreen_geometry().unwrap();
let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen
.surface
.0
@ -891,18 +905,16 @@ impl Workspace {
let stack = self.focus_stack.get(seat);
let last_focused = stack.last();
self.fullscreen
.as_ref()
.filter(|f| last_focused.is_some_and(|t| t == &f.surface))
self.fullscreen_surfaces
.iter()
.find(|f| last_focused.is_some_and(|t| t == &f.surface))
.and_then(check_fullscreen)
.or_else(|| self.floating_layer.popup_surface_under(location))
.or_else(|| self.tiling_layer.popup_surface_under(location, overview))
.or_else(|| self.floating_layer.popup_surface_under(location, seat))
.or_else(|| {
self.fullscreen
.as_ref()
.filter(|f| last_focused.is_none_or(|t| t != &f.surface))
.and_then(check_fullscreen)
self.tiling_layer
.popup_surface_under(location, overview, seat)
})
.or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen))
.map(|(m, p)| (m, p.to_global(&self.output)))
}
@ -919,7 +931,7 @@ impl Workspace {
let check_fullscreen = |fullscreen: &FullscreenSurface| {
if !fullscreen.is_animating() {
let geometry = self.fullscreen_geometry().unwrap();
let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen
.surface
.focus_under(
@ -937,18 +949,16 @@ impl Workspace {
let stack = self.focus_stack.get(seat);
let last_focused = stack.last();
self.fullscreen
.as_ref()
.filter(|f| last_focused.is_some_and(|t| t == &f.surface))
self.fullscreen_surfaces
.iter()
.find(|f| last_focused.is_some_and(|t| t == &f.surface))
.and_then(check_fullscreen)
.or_else(|| self.floating_layer.toplevel_surface_under(location))
.or_else(|| self.tiling_layer.toplevel_surface_under(location, overview))
.or_else(|| self.floating_layer.toplevel_surface_under(location, seat))
.or_else(|| {
self.fullscreen
.as_ref()
.filter(|f| last_focused.is_none_or(|t| t != &f.surface))
.and_then(check_fullscreen)
self.tiling_layer
.toplevel_surface_under(location, overview, seat)
})
.or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen))
.map(|(m, p)| (m, p.to_global(&self.output)))
}
@ -1020,10 +1030,14 @@ impl Workspace {
where
CosmicSurface: PartialEq<S>,
{
if self.get_fullscreen().is_some_and(|s| s == surface) {
let fullscreen_state = self.fullscreen.clone().unwrap();
if let Some(idx) = self
.fullscreen_surfaces
.iter()
.position(|f| f.ended_at.is_none() && &f.surface == surface)
{
let fullscreen_state = self.fullscreen_surfaces.get(idx)?.clone();
{
let f = self.fullscreen.as_mut().unwrap();
let f = self.fullscreen_surfaces.get_mut(idx)?;
f.previous_geometry = Some(to);
f.ended_at = Some(
Instant::now()
@ -1127,16 +1141,20 @@ impl Workspace {
)> {
match window {
MinimizedWindow::Fullscreen { previous, surface } => {
let old_fullscreen = self.remove_fullscreen();
surface.set_minimized(false);
self.fullscreen = Some(FullscreenSurface {
// focus it so it's the top fullscreen window
self.focus_stack
.get_mut(seat)
.append(FocusTarget::Fullscreen(surface.clone()));
self.fullscreen_surfaces.push(FullscreenSurface {
surface,
previous_state: previous.clone().map(|p| p.previous_state),
previous_geometry: previous.map(|p| p.previous_geometry),
start_at: None,
ended_at: None,
});
old_fullscreen
self.dirty.store(true, Ordering::SeqCst);
None
}
MinimizedWindow::Floating { window, previous } => {
let current_output_size = self.output.geometry().size.as_logical();
@ -1224,13 +1242,7 @@ impl Workspace {
seat: impl Into<Option<&'a Seat<State>>>,
restore: Option<FullscreenRestoreState>,
previous_geometry: Option<Rectangle<i32, Local>>,
) -> Option<(
CosmicSurface,
Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>,
)> {
let res = self.remove_fullscreen();
) {
window.set_fullscreen(true);
window.set_geometry(self.output.geometry(), 0);
window.send_configure();
@ -1243,96 +1255,137 @@ impl Workspace {
self.focus_stack.get_mut(seat).append(window.clone());
}
self.fullscreen = Some(FullscreenSurface {
self.dirty.store(true, Ordering::SeqCst);
self.fullscreen_surfaces.push(FullscreenSurface {
surface: window.clone(),
previous_state: restore,
previous_geometry,
start_at: Some(Instant::now()),
ended_at: None,
});
res
}
#[must_use]
pub fn take_fullscreen(
pub fn take_fullscreen<S>(
&mut self,
surface: &S,
) -> Option<(
CosmicSurface,
Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>,
)>
where
CosmicSurface: PartialEq<S>,
{
let idx = self
.fullscreen_surfaces
.iter()
.position(|f| f.ended_at.is_none() && &f.surface == surface)?;
let fs = self.fullscreen_surfaces.remove(idx);
for focus_stack in self.focus_stack.0.values_mut() {
focus_stack.retain(|t| t != &fs.surface);
}
Some((fs.surface, fs.previous_state, fs.previous_geometry))
}
#[must_use]
pub fn remove_fullscreen_at(
&mut self,
idx: usize,
) -> Option<(
CosmicSurface,
Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>,
)> {
let surface = self.fullscreen.take_if(|s| s.ended_at.is_none())?;
// if it doesn't exist we move on.
let surface = self.fullscreen_surfaces.get_mut(idx)?;
// if already being removed, do nothing
if surface.ended_at.is_some() {
return None;
}
if surface.surface.alive() {
surface.surface.output_leave(&self.output);
surface.surface.set_fullscreen(false);
if let Some(previous_geometry) = surface.previous_geometry.as_ref() {
surface
.surface
.set_geometry(previous_geometry.to_global(&self.output), 0);
}
surface.surface.send_configure();
}
for focus_stack in self.focus_stack.0.values_mut() {
focus_stack.retain(|t| t != &surface.surface);
}
surface.ended_at = Some(
Instant::now()
- (FULLSCREEN_ANIMATION_DURATION
- surface
.start_at
.take()
.map(|earlier| {
Instant::now()
.duration_since(earlier)
.min(FULLSCREEN_ANIMATION_DURATION)
})
.unwrap_or(FULLSCREEN_ANIMATION_DURATION)),
);
Some((
surface.surface,
surface.previous_state,
surface.surface.clone(),
surface.previous_state.clone(),
surface.previous_geometry,
))
}
#[must_use]
pub fn remove_fullscreen(
pub fn remove_fullscreen_surface<S>(
&mut self,
surface: &S,
) -> Option<(
CosmicSurface,
Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>,
)> {
if let Some(surface) = self.fullscreen.as_mut() {
if surface.ended_at.is_some() {
return None;
}
if surface.surface.alive() {
surface.surface.output_leave(&self.output);
surface.surface.set_fullscreen(false);
if let Some(previous_geometry) = surface.previous_geometry.as_ref() {
surface
.surface
.set_geometry(previous_geometry.to_global(&self.output), 0);
}
surface.surface.send_configure();
}
for focus_stack in self.focus_stack.0.values_mut() {
focus_stack.retain(|t| t != &surface.surface);
}
surface.ended_at = Some(
Instant::now()
- (FULLSCREEN_ANIMATION_DURATION
- surface
.start_at
.take()
.map(|earlier| {
Instant::now()
.duration_since(earlier)
.min(FULLSCREEN_ANIMATION_DURATION)
})
.unwrap_or(FULLSCREEN_ANIMATION_DURATION)),
);
Some((
surface.surface.clone(),
surface.previous_state.clone(),
surface.previous_geometry,
))
} else {
None
}
)>
where
CosmicSurface: PartialEq<S>,
{
let idx = self
.fullscreen_surfaces
.iter()
.position(|f| f.ended_at.is_none() && &f.surface == surface)?;
self.remove_fullscreen_at(idx)
}
pub fn get_fullscreen(&self) -> Option<&CosmicSurface> {
self.fullscreen
.as_ref()
.filter(|f| f.alive())
.filter(|f| f.ended_at.is_none())
.map(|f| &f.surface)
pub fn get_fullscreen(&self, seat: &Seat<State>) -> Option<&FullscreenSurface> {
let stack = self.focus_stack.get(seat);
stack
.iter()
.find_map(|t| {
if let FocusTarget::Fullscreen(s) = t {
self.fullscreen_surfaces
.iter()
.find(|f| f.alive() && f.ended_at.is_none() && &f.surface == s)
} else {
None
}
})
.or_else(|| {
self.fullscreen_surfaces
.iter()
.rev()
.find(|f| f.alive() && f.ended_at.is_none())
})
}
pub fn get_fullscreen_surfaces(&self) -> impl Iterator<Item = &FullscreenSurface> {
self.fullscreen_surfaces
.iter()
.filter(|f| f.alive() && f.ended_at.is_none())
}
pub fn resize(
@ -1459,14 +1512,18 @@ impl Workspace {
self.floating_layer.mapped().count()
+ self.tiling_layer.mapped().count()
+ self.minimized_windows.len()
+ if self.fullscreen.is_some() { 1 } else { 0 }
+ self
.fullscreen_surfaces
.iter()
.filter(|f| f.ended_at.is_none())
.count()
}
pub fn is_empty(&self) -> bool {
self.floating_layer.mapped().next().is_none()
&& self.tiling_layer.mapped().next().is_none()
&& self.minimized_windows.is_empty()
&& self.fullscreen.is_none()
&& self.fullscreen_surfaces.is_empty()
}
pub fn is_floating<S>(&self, surface: &S) -> bool
@ -1573,8 +1630,11 @@ impl Workspace {
};
let focused = self.focus_stack.get(last_active_seat).last().cloned();
let mut fullscreen_elements = if let Some(fullscreen) = self.fullscreen.as_ref() {
let fullscreen_geo = self.fullscreen_geometry().unwrap();
let render_fullscreen = |fullscreen: &FullscreenSurface,
renderer: &mut R,
output_scale: f64|
-> Vec<WorkspaceRenderElement<R>> {
let fullscreen_geo = self.fullscreen_geometry_for(fullscreen);
let previous_geo = fullscreen
.previous_geometry
.as_ref()
@ -1644,20 +1704,35 @@ impl Workspace {
.into_iter()
.map(animation_rescale)
.collect::<Vec<_>>()
} else {
Vec::new()
};
let top_fullscreen = self.get_fullscreen(last_active_seat);
let mut fullscreen_elements: Vec<WorkspaceRenderElement<R>> = Vec::new();
if let Some(fs) = top_fullscreen {
fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale));
}
// Also render any animating (entering/exiting) fullscreens
for fs in self.fullscreen_surfaces.iter().filter(|f| f.is_animating()) {
if top_fullscreen.is_none_or(|top| top.surface != fs.surface) {
fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale));
};
}
if matches!(focused, Some(FocusTarget::Fullscreen(_))) {
elements.append(&mut fullscreen_elements);
}
let any_fullscreen_animating = self
.fullscreen_surfaces
.iter()
.any(|f| f.start_at.is_some() || f.ended_at.is_some());
if !matches!(focused, Some(FocusTarget::Fullscreen(_)))
|| any_fullscreen_animating
|| self
.fullscreen
.as_ref()
.map(|f| f.start_at.is_some() || f.ended_at.is_some())
.unwrap_or(true)
.fullscreen_surfaces
.iter()
.all(|f| !f.alive() || f.ended_at.is_some())
{
// floating surfaces
let alpha = match &overview.0 {
@ -1682,13 +1757,17 @@ impl Workspace {
self.floating_layer
.render::<R>(
renderer,
focused.as_ref().and_then(|target| {
if let FocusTarget::Window(mapped) = target {
Some(mapped)
} else {
None
}
}),
render_focus
.then(|| {
focused.as_ref().and_then(|target| {
if let FocusTarget::Window(mapped) = target {
Some(mapped)
} else {
None
}
})
})
.flatten(),
resize_indicator.clone(),
indicator_thickness,
alpha,
@ -1774,8 +1853,12 @@ impl Workspace {
layer_map.non_exclusive_zone().as_local()
};
if let Some(fullscreen) = self.fullscreen.as_ref() {
let fullscreen_geo = self.fullscreen_geometry().unwrap();
// Render popups for the top (most recently focused) fullscreen
let focus_stack = self.focus_stack.get(last_active_seat);
let top_fullscreen = self.get_fullscreen(last_active_seat);
if let Some(fullscreen) = top_fullscreen {
let fullscreen_geo = self.fullscreen_geometry_for(fullscreen);
let previous_geo = fullscreen
.previous_geometry
.as_ref()
@ -1832,13 +1915,16 @@ impl Workspace {
);
}
let focus_stack = self.focus_stack.get(last_active_seat);
let any_fullscreen_animating = self
.fullscreen_surfaces
.iter()
.any(|f| f.start_at.is_some() || f.ended_at.is_some());
if !matches!(focus_stack.last(), Some(FocusTarget::Fullscreen(_)))
|| any_fullscreen_animating
|| self
.fullscreen
.as_ref()
.map(|f| f.start_at.is_some() || f.ended_at.is_some())
.unwrap_or(true)
.fullscreen_surfaces
.iter()
.all(|f| !f.alive() || f.ended_at.is_some())
{
// floating surfaces
let alpha = match &overview.0 {

View file

@ -22,8 +22,8 @@ use smithay::{
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
},
touch::{
DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget,
UpEvent,
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
},
},
output::Output,
@ -259,10 +259,8 @@ impl ZoomState {
original_position: Point<f64, Global>,
movement: ZoomMovement,
) {
let cursor_position = cursor_position.to_i32_round();
let original_position = original_position.to_i32_round();
let output_geometry = output.geometry();
let mut zoomed_output_geometry = output.zoomed_geometry().unwrap();
let output_geometry = output.geometry().to_f64();
let mut zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64();
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
let mut output_state_ref = output_state.lock().unwrap();
@ -275,44 +273,40 @@ impl ZoomState {
let cursor_position = cursor_position.to_local(output);
match movement {
ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position.to_f64(),
ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position,
ZoomMovement::OnEdge => {
if !zoomed_output_geometry
.overlaps_or_touches(Rectangle::new(original_position, Size::from((16, 16))))
.overlaps_or_touches(Rectangle::new(original_position, Size::from((16., 16.))))
{
zoomed_output_geometry.loc = cursor_position.to_global(output)
- zoomed_output_geometry.size.downscale(2).to_point();
- zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(output)
.upscale(
output_geometry.size.w
/ (output_geometry.size.w - zoomed_output_geometry.size.w),
)
.upscale(output_state_ref.level)
.to_global(output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x,
output_geometry.loc.x + output_geometry.size.w - 1,
output_geometry.loc.x + output_geometry.size.w - 1.,
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y,
output_geometry.loc.y + output_geometry.size.h - 1,
output_geometry.loc.y + output_geometry.size.h - 1.,
);
output_state_ref.previous_point =
Some((output_state_ref.focal_point, Instant::now()));
output_state_ref.focal_point = focal_point.to_local(output).to_f64();
output_state_ref.focal_point = focal_point.to_local(output);
} else if !zoomed_output_geometry.contains(cursor_position.to_global(output)) {
let mut diff = output_state_ref.focal_point.to_global(output)
+ (cursor_position.to_global(output) - original_position)
.to_f64()
.upscale(output_state_ref.level);
diff.x = diff.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_down(),
output_geometry.loc.x,
(output_geometry.loc.x + output_geometry.size.w).next_down(),
);
diff.y = diff.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_down(),
output_geometry.loc.y,
(output_geometry.loc.y + output_geometry.size.h).next_down(),
);
diff -= output_state_ref.focal_point.to_global(output);
@ -321,28 +315,27 @@ impl ZoomState {
}
ZoomMovement::Centered => {
zoomed_output_geometry.loc = cursor_position.to_global(output)
- zoomed_output_geometry.size.downscale(2).to_point();
- zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(output)
.upscale(
output_geometry
.size
.w
.checked_div(output_geometry.size.w - zoomed_output_geometry.size.w)
.unwrap_or(1),
(output_geometry.size.w
/ (output_geometry.size.w - zoomed_output_geometry.size.w)
.max(f64::EPSILON))
.max(1.),
)
.to_global(output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x,
output_geometry.loc.x + output_geometry.size.w - 1,
output_geometry.loc.x + output_geometry.size.w - 1.,
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y,
output_geometry.loc.y + output_geometry.size.h - 1,
output_geometry.loc.y + output_geometry.size.h - 1.,
);
output_state_ref.focal_point = focal_point.to_local(output).to_f64();
output_state_ref.focal_point = focal_point.to_local(output);
}
}
}
@ -1036,58 +1029,59 @@ impl PointerTarget<State> for ZoomFocusTarget {
}
impl TouchTarget<State> for ZoomFocusTarget {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent, seq: Serial) {
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event, seq),
ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event, seq),
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, seq: Serial) {
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event, seq),
ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event, seq),
ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event),
}
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent, seq: Serial) {
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::motion(elem, seat, data, event, seq),
ZoomFocusTarget::Menu(elem) => TouchTarget::motion(elem, seat, data, event, seq),
ZoomFocusTarget::Main(elem) => TouchTarget::motion(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::motion(elem, seat, data, event),
}
}
fn frame(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
fn frame(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::frame(elem, seat, data, seq),
ZoomFocusTarget::Menu(elem) => TouchTarget::frame(elem, seat, data, seq),
ZoomFocusTarget::Main(elem) => TouchTarget::frame(elem, seat, data, frame),
ZoomFocusTarget::Menu(elem) => TouchTarget::frame(elem, seat, data, frame),
}
}
fn cancel(&self, seat: &Seat<State>, data: &mut State, seq: Serial) {
fn cancel(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::cancel(elem, seat, data, seq),
ZoomFocusTarget::Menu(elem) => TouchTarget::cancel(elem, seat, data, seq),
ZoomFocusTarget::Main(elem) => TouchTarget::cancel(elem, seat, data, frame),
ZoomFocusTarget::Menu(elem) => TouchTarget::cancel(elem, seat, data, frame),
}
}
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent, seq: Serial) {
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::shape(elem, seat, data, event, seq),
ZoomFocusTarget::Menu(elem) => TouchTarget::shape(elem, seat, data, event, seq),
ZoomFocusTarget::Main(elem) => TouchTarget::shape(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::shape(elem, seat, data, event),
}
}
fn orientation(
&self,
seat: &Seat<State>,
data: &mut State,
event: &OrientationEvent,
seq: Serial,
) {
fn orientation(&self, seat: &Seat<State>, data: &mut State, event: &OrientationEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::orientation(elem, seat, data, event, seq),
ZoomFocusTarget::Menu(elem) => TouchTarget::orientation(elem, seat, data, event, seq),
ZoomFocusTarget::Main(elem) => TouchTarget::orientation(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::orientation(elem, seat, data, event),
}
}
fn last_frame(&self, seat: &Seat<State>, data: &mut State) -> Option<FrameMarker> {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::last_frame(elem, seat, data),
ZoomFocusTarget::Menu(elem) => TouchTarget::last_frame(elem, seat, data),
}
}
}

View file

@ -8,7 +8,7 @@ use crate::{
x11::X11State,
},
config::{CompOutputConfig, Config, ScreenFilter},
dbus::a11y_keyboard_monitor::A11yKeyboardMonitorState,
dbus::DBusState,
input::{PointerFocusState, gestures::GestureState},
shell::{CosmicSurface, SeatExt, Shell, grabs::SeatMoveGrabState},
utils::prelude::OutputExt,
@ -32,7 +32,6 @@ use crate::{
use anyhow::Context;
use calloop::RegistrationToken;
use cosmic_comp_config::output::comp::{OutputConfig, OutputState};
use futures_executor::ThreadPool;
use i18n_embed::{
DesktopLanguageRequester,
fluent::{FluentLanguageLoader, fluent_language_loader},
@ -87,6 +86,7 @@ use smithay::{
output::OutputManagerState,
pointer_constraints::PointerConstraintsState,
pointer_gestures::PointerGesturesState,
pointer_warp::PointerWarpManager,
presentation::PresentationState,
seat::WaylandFocus,
security_context::{SecurityContext, SecurityContextState},
@ -117,7 +117,7 @@ use smithay::{
};
use tracing::warn;
#[cfg(feature = "systemd")]
#[cfg(feature = "logind")]
use std::os::fd::OwnedFd;
use std::{
@ -234,7 +234,6 @@ pub struct Common {
pub display_handle: DisplayHandle,
pub event_loop_handle: LoopHandle<'static, State>,
pub event_loop_signal: LoopSignal,
pub async_executor: ThreadPool,
pub popups: PopupManager,
pub shell: Arc<parking_lot::RwLock<Shell>>,
@ -279,7 +278,7 @@ pub struct Common {
pub xdg_decoration_state: XdgDecorationState,
pub overlap_notify_state: OverlapNotifyState,
pub a11y_state: A11yState,
pub a11y_keyboard_monitor_state: A11yKeyboardMonitorState,
pub dbus_state: DBusState,
// shell-related wayland state
pub xdg_shell_state: XdgShellState,
@ -294,7 +293,7 @@ pub struct Common {
pub xwayland_shell_state: XWaylandShellState,
pub pointer_focus_state: Option<PointerFocusState>,
#[cfg(feature = "systemd")]
#[cfg(feature = "logind")]
pub inhibit_lid_fd: Option<OwnedFd>,
pub with_xwayland: bool,
@ -673,6 +672,7 @@ impl State {
XWaylandKeyboardGrabState::new::<Self>(dh);
let xwayland_shell_state = XWaylandShellState::new::<Self>(dh);
PointerConstraintsState::new::<Self>(dh);
PointerWarpManager::new::<Self>(dh);
PointerGesturesState::new::<Self>(dh);
TabletManagerState::new::<Self>(dh);
SecurityContextState::new::<Self, _>(dh, client_has_no_security_context);
@ -727,15 +727,9 @@ impl State {
);
let workspace_state = WorkspaceState::new(dh, client_not_sandboxed);
let async_executor = ThreadPool::builder().pool_size(1).create().unwrap();
if let Err(err) = crate::dbus::init(&handle, &async_executor) {
tracing::warn!(?err, "Failed to initialize dbus handlers");
}
let a11y_state = A11yState::new::<State, _>(dh, client_not_sandboxed);
let a11y_keyboard_monitor_state = A11yKeyboardMonitorState::new(&async_executor);
let dbus_state = DBusState::init(&handle);
State {
common: Common {
@ -744,7 +738,6 @@ impl State {
display_handle: dh.clone(),
event_loop_handle: handle,
event_loop_signal: signal,
async_executor,
popups: PopupManager::default(),
shell,
@ -794,13 +787,13 @@ impl State {
xdg_foreign_state,
workspace_state,
a11y_state,
a11y_keyboard_monitor_state,
xwayland_scale: None,
xwayland_state: None,
xwayland_shell_state,
pointer_focus_state: None,
dbus_state,
#[cfg(feature = "systemd")]
#[cfg(feature = "logind")]
inhibit_lid_fd: None,
with_xwayland,
@ -825,7 +818,7 @@ impl State {
}
fn update_inhibitor_locks(&mut self) {
#[cfg(feature = "systemd")]
#[cfg(feature = "logind")]
{
use smithay::backend::session::Session;
use tracing::{debug, error, warn};
@ -841,7 +834,7 @@ impl State {
if should_handle_lid {
if self.common.inhibit_lid_fd.is_none() {
match crate::dbus::logind::inhibit_lid() {
match crate::dbus::logind::inhibit_lid(&self.common) {
Ok(fd) => {
debug!("Inhibiting lid switch");
self.common.inhibit_lid_fd = Some(fd);
@ -852,7 +845,8 @@ impl State {
.iter()
.find(|o| o.is_internal())
.cloned();
let closed = crate::dbus::logind::lid_closed().unwrap_or(false);
let closed =
crate::dbus::logind::lid_closed(&self.common).unwrap_or(false);
if closed {
backend.disable_internal_output(
@ -996,8 +990,8 @@ impl Common {
// normal windows
for space in shell.workspaces.spaces() {
if let Some(window) = space.get_fullscreen() {
window.with_surfaces(processor);
if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
fs.surface.with_surfaces(processor);
}
space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() {
@ -1162,15 +1156,16 @@ impl Common {
});
if let Some(active) = shell.active_space(output) {
if let Some(window) = active.get_fullscreen()
&& let Some(feedback) = window
if let Some(fs) = active.get_fullscreen(shell.seats.last_active())
&& let Some(feedback) = fs
.surface
.wl_surface()
.and_then(|wl_surface| {
advertised_node_for_surface(&wl_surface, &self.display_handle)
})
.and_then(&mut dmabuf_feedback)
{
window.send_dmabuf_feedback(
fs.surface.send_dmabuf_feedback(
output,
&feedback,
render_element_states,
@ -1356,8 +1351,9 @@ impl Common {
});
if let Some(active) = shell.active_space(output) {
if let Some(window) = active.get_fullscreen() {
window.send_frame(output, time, throttle(window), should_send);
if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) {
fs.surface
.send_frame(output, time, throttle(&fs.surface), should_send);
}
active.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() {
@ -1377,9 +1373,9 @@ impl Common {
.spaces_for_output(output)
.filter(|w| w.handle != active.handle)
{
if let Some(window) = space.get_fullscreen() {
let throttle = min(throttle(space), throttle(window));
window.send_frame(output, time, throttle, |_, _| None);
if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
let throttle = min(throttle(space), throttle(&fs.surface));
fs.surface.send_frame(output, time, throttle, |_, _| None);
}
space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() {

View file

@ -51,13 +51,12 @@ use smithay::{
PointerTarget, RelativeMotionEvent,
},
touch::{
DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget,
UpEvent,
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
},
},
output::Output,
reexports::calloop::RegistrationToken,
reexports::calloop::{self, LoopHandle, futures::Scheduler},
reexports::calloop::{self, LoopHandle, RegistrationToken, futures::Scheduler},
utils::{
Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size,
Transform,
@ -180,6 +179,8 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
last_seat: Arc<Mutex<Option<(Seat<crate::state::State>, Serial)>>>,
cursor_pos: Option<Point<f64, Logical>>,
touch_map: HashMap<Finger, IcedPoint>,
last_touch_frame: Option<FrameMarker>,
last_touch_serial: Option<Serial>,
// iced
theme: Theme,
@ -228,6 +229,8 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
last_seat: self.last_seat.clone(),
cursor_pos: self.cursor_pos,
touch_map: self.touch_map.clone(),
last_touch_frame: None,
last_touch_serial: None,
theme: self.theme.clone(),
renderer,
state,
@ -253,6 +256,8 @@ impl<P: Program + Send + 'static> fmt::Debug for IcedElementInternal<P> {
.field("last_seat", &self.last_seat)
.field("cursor_pos", &self.cursor_pos)
.field("touch_map", &self.touch_map)
.field("last_touch_frame", &self.last_touch_frame)
.field("last_touch_serial", &self.last_touch_serial)
.field("theme", &"...")
.field("renderer", &"...")
.field("state", &"...")
@ -309,6 +314,8 @@ impl<P: Program + Send + 'static> IcedElement<P> {
cursor_pos: None,
last_seat,
touch_map: HashMap::new(),
last_touch_frame: None,
last_touch_serial: None,
theme,
renderer,
state,
@ -633,7 +640,6 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &DownEvent,
seq: Serial,
) {
let mut internal = self.0.lock().unwrap();
let id = Finger(i32::from(event.slot) as u64);
@ -644,7 +650,8 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
.queue_event(Event::Touch(TouchEvent::FingerPressed { id, position }));
internal.touch_map.insert(id, position);
internal.cursor_pos = Some(event_location);
*internal.last_seat.lock().unwrap() = Some((seat.clone(), seq));
internal.last_touch_serial = Some(event.serial);
*internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial));
internal.update(false);
}
@ -653,12 +660,12 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &UpEvent,
seq: Serial,
) {
let mut internal = self.0.lock().unwrap();
let id = Finger(i32::from(event.slot) as u64);
if let Some(position) = internal.touch_map.remove(&id) {
*internal.last_seat.lock().unwrap() = Some((seat.clone(), seq));
*internal.last_seat.lock().unwrap() =
Some((seat.clone(), internal.last_touch_serial.unwrap()));
internal
.state
.queue_event(Event::Touch(TouchEvent::FingerLifted { id, position }));
@ -671,13 +678,13 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
event: &TouchMotionEvent,
seq: Serial,
) {
let mut internal = self.0.lock().unwrap();
let id = Finger(i32::from(event.slot) as u64);
let event_location = event.location.downscale(internal.additional_scale);
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
*internal.last_seat.lock().unwrap() = Some((seat.clone(), seq));
*internal.last_seat.lock().unwrap() =
Some((seat.clone(), internal.last_touch_serial.unwrap()));
internal
.state
.queue_event(Event::Touch(TouchEvent::FingerMoved { id, position }));
@ -690,17 +697,19 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
&self,
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_seq: Serial,
frame: FrameMarker,
) {
self.0.lock().unwrap().last_touch_frame = Some(frame);
}
fn cancel(
&self,
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_seq: Serial,
frame: FrameMarker,
) {
let mut internal = self.0.lock().unwrap();
internal.last_touch_frame = Some(frame);
for (id, position) in std::mem::take(&mut internal.touch_map) {
internal
.state
@ -714,7 +723,6 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_event: &ShapeEvent,
_seq: Serial,
) {
}
@ -723,9 +731,16 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
_event: &OrientationEvent,
_seq: Serial,
) {
}
fn last_frame(
&self,
_seat: &Seat<crate::state::State>,
_data: &mut crate::state::State,
) -> Option<FrameMarker> {
self.0.lock().unwrap().last_touch_frame
}
}
impl<P: Program + Send + 'static> KeyboardTarget<crate::state::State> for IcedElement<P> {

View file

@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::{desktop::layer_map_for_output, output::Output};
use smithay::{
backend::renderer::utils::with_renderer_surface_state, desktop::layer_map_for_output,
output::Output,
};
/// Layer shell namespace used by `cosmic-workspaces`
// TODO: Avoid special case, or add protocol to expose required behavior
@ -10,5 +13,14 @@ pub const WORKSPACE_OVERVIEW_NAMESPACE: &str = "cosmic-workspace-overview";
pub fn workspace_overview_is_open(output: &Output) -> bool {
layer_map_for_output(output)
.layers()
.any(|s| s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE)
.filter(|s| s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE)
// Only consider the overview open once it has committed a buffer. The
// surface is inserted into the layer map on its initial (bufferless)
// commit, so checking for the namespace alone hides all toplevels for a
// frame before the overview has anything to draw, briefly flashing the
// bare wallpaper.
.any(|s| {
with_renderer_surface_state(s.wl_surface(), |state| state.buffer().is_some())
.unwrap_or(false)
})
}

View file

@ -51,9 +51,11 @@ impl DmabufHandler for State {
let is_fullscreen = shell
.workspaces
.space_for_handle(&handle)?
.fullscreen
.as_ref()
.is_some_and(|f| f.surface.has_surface(surface, WindowSurfaceType::all()));
.fullscreen_surfaces
.iter()
.any(|f| {
f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all())
});
let node = kms
.drm_devices

View file

@ -753,11 +753,15 @@ pub fn render_cursor_to_buffer(
seat: &Seat<State>,
) {
let buffer = frame.buffer();
let cursor_size = seat
let mut cursor_size = seat
.cursor_geometry((0.0, 0.0), state.common.clock.now())
.map(|(geo, _hotspot)| geo.size)
.unwrap_or_else(|| Size::from((64, 64)));
let buffer_size = buffer_dimensions(&buffer).unwrap();
// Client shouldn't try to allocate 0x0 buffer
if cursor_size == Size::new(0, 0) {
cursor_size = Size::new(1, 1);
}
if buffer_size != cursor_size {
let constraints = BufferConstraints {
size: cursor_size,

View file

@ -25,6 +25,7 @@ pub mod output_configuration;
pub mod output_power;
pub mod overlap_notify;
pub mod pointer_constraints;
pub mod pointer_warp;
pub mod primary_selection;
pub mod seat;
pub mod security_context;

View file

@ -1,35 +1,123 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::State;
use crate::{shell::CosmicSurface, state::State, utils::prelude::*};
use smithay::{
input::pointer::PointerHandle,
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Logical, Point},
wayland::{
pointer_constraints::{PointerConstraintsHandler, with_pointer_constraint},
seat::WaylandFocus,
},
wayland::{pointer_constraints::PointerConstraintsHandler, seat::WaylandFocus},
};
pub use smithay::wayland::pointer_constraints::{PointerConstraintRef, with_pointer_constraint};
impl PointerConstraintsHandler for State {
fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
// XXX region
if pointer
.current_focus()
.is_some_and(|x| x.wl_surface().as_deref() == Some(surface))
{
let seat = self
.common
.shell
.read()
.seats
.iter()
.find(|s| s.get_pointer().as_ref() == Some(pointer))
.cloned();
let (is_under, is_focused, surface_location) = if let Some(seat) = seat {
seat.set_pointer_constraint_hint(None);
let current_output = seat.active_output();
let position = seat.get_pointer().unwrap().current_location().as_global();
let shell = self.common.shell.read();
let under = State::surface_under(position, &current_output, &shell);
let mut surface_location = None;
let is_under = if let Some((target, target_loc)) = under
&& let Some(under_surface) = target.wl_surface()
{
if *under_surface == *surface {
surface_location = Some(target_loc);
true
} else {
CosmicSurface::surface_tree_offset(surface, &under_surface).is_some_and(
|offset| {
surface_location = Some(target_loc - offset.to_f64().as_global());
true
},
)
}
} else {
false
};
let is_focused = seat
.get_keyboard()
.and_then(|k| k.current_focus())
.is_some_and(|f| f.has_surface(&shell, surface));
(is_under, is_focused, surface_location)
} else {
(false, false, None)
};
if is_focused && is_under {
with_pointer_constraint(surface, pointer, |constraint| {
constraint.unwrap().activate();
if let Some(constraint) = constraint {
if let Some(region) = constraint.region() {
if let Some(surface_location) = surface_location
&& let position = pointer.current_location()
&& let point = (position - surface_location.as_logical()).to_i32_round()
&& region.contains(point)
{
constraint.activate();
}
} else {
constraint.activate();
}
}
});
}
}
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();
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);
}
}
}
fn cursor_position_hint(
&mut self,
_surface: &WlSurface,
_pointer: &PointerHandle<Self>,
_location: Point<f64, Logical>,
surface: &WlSurface,
pointer: &PointerHandle<Self>,
location: Point<f64, Logical>,
) {
// TODO
if with_pointer_constraint(surface, pointer, |constraint| {
constraint.is_some_and(|c| c.is_active())
}) {
let seat = self
.common
.shell
.read()
.seats
.iter()
.find(|s| s.get_pointer().as_ref() == Some(pointer))
.cloned();
if let Some(seat) = seat {
seat.set_pointer_constraint_hint(Some((surface.clone(), location)));
}
}
}
}

View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::State;
use smithay::{
input::pointer::PointerHandle,
reexports::wayland_server::protocol::{wl_pointer::WlPointer, wl_surface::WlSurface},
utils::{Logical, Point, Serial},
wayland::pointer_warp::PointerWarpHandler,
};
impl PointerWarpHandler for State {
fn warp_pointer(
&mut self,
surface: WlSurface,
pointer: WlPointer,
pos: Point<f64, Logical>,
serial: Serial,
) {
let Some(resource_handle) = PointerHandle::<State>::from_resource(&pointer) else {
return;
};
let shell = self.common.shell.read();
let pointer_handle = shell.seats.iter().find_map(|seat| {
if let Some(pointer_handle) = seat.get_pointer()
&& resource_handle == pointer_handle
&& pointer_handle.last_enter() == Some(serial)
&& let Some(keyboard) = seat.get_keyboard()
&& let Some(keyboard_focus) = keyboard.current_focus()
&& keyboard_focus.has_surface(&shell, &surface)
{
return Some(pointer_handle);
}
None
});
drop(shell);
if let Some(pointer_handle) = pointer_handle {
self.apply_cursor_hint(&surface, &pointer_handle, pos);
}
}
}

View file

@ -42,7 +42,7 @@ impl ToplevelManagementHandler for State {
.spaces_for_output(output)
.enumerate()
.find(|(_, w)| {
w.get_fullscreen().is_some_and(|f| f == window)
w.get_fullscreen_surfaces().any(|f| &f.surface == window)
|| w.mapped()
.flat_map(|m| m.windows().map(|(s, _)| s))
.any(|w| &w == window)

View file

@ -5,6 +5,7 @@ use crate::{
state::State,
wayland::protocols::workspace::{State as WState, WorkspaceHandle},
};
use cosmic_comp_config::ActivationPolicy;
use smithay::{
input::Seat,
reexports::wayland_server::protocol::wl_surface::WlSurface,
@ -20,6 +21,10 @@ pub enum ActivationContext {
Workspace(WorkspaceHandle),
}
// It may happen that we get activation-requests, while all outputs are disabled.
// In these cases we won't be able to determine workspaces for windows and thus
// need to handle the corresponding code paths defensively.
impl XdgActivationHandler for State {
fn activation_state(&mut self) -> &mut XdgActivationState {
&mut self.common.xdg_activation_state
@ -51,7 +56,12 @@ impl XdgActivationHandler for State {
});
let output = seat.active_output();
let mut shell = self.common.shell.write();
let workspace = shell.active_space_mut(&output).unwrap();
let Some(workspace) = shell.active_space_mut(&output) else {
debug!(?token, "created urgent token for privileged client");
data.user_data
.insert_if_missing(move || ActivationContext::UrgentOnly);
return true;
};
let handle = workspace.handle;
data.user_data
.insert_if_missing(move || ActivationContext::Workspace(handle));
@ -85,7 +95,11 @@ impl XdgActivationHandler for State {
if valid {
let output = seat.active_output();
let mut shell = self.common.shell.write();
let workspace = shell.active_space_mut(&output).unwrap();
let Some(workspace) = shell.active_space_mut(&output) else {
data.user_data
.insert_if_missing(|| ActivationContext::UrgentOnly);
return true;
};
let handle = workspace.handle;
data.user_data
.insert_if_missing(move || ActivationContext::Workspace(handle));
@ -115,10 +129,44 @@ impl XdgActivationHandler for State {
}
}
ActivationContext::Workspace(_) => {
self.activate_surface(
&surface,
Some((ActivationKey::Wayland(surface.clone()), *context)),
);
match self.common.config.cosmic_conf.activation_policy {
ActivationPolicy::Focus => {
self.activate_surface(
&surface,
Some((ActivationKey::Wayland(surface.clone()), *context)),
);
}
ActivationPolicy::FocusIfActiveWorkspace => {
let shell = self.common.shell.write();
let Some((target_workspace, _)) = shell.workspace_for_surface(&surface)
else {
return;
};
let seat = shell.seats.last_active().clone();
let current_output = seat.active_output();
let current_workspace = shell.active_space(&current_output).unwrap().handle;
if target_workspace == current_workspace {
std::mem::drop(shell);
self.activate_surface(
&surface,
Some((ActivationKey::Wayland(surface.clone()), *context)),
);
} else {
let mut workspace_guard = self.common.workspace_state.update();
workspace_guard.add_workspace_state(&target_workspace, WState::Urgent);
}
}
ActivationPolicy::Urgent => {
let shell = self.common.shell.write();
if let Some((workspace, _output)) = shell.workspace_for_surface(&surface) {
let mut workspace_guard = self.common.workspace_state.update();
workspace_guard.add_workspace_state(&workspace, WState::Urgent);
}
}
}
}
}
}
@ -219,8 +267,8 @@ impl State {
.workspaces
.space_for_handle(&workspace)
.unwrap()
.get_fullscreen()
.cloned()
.get_fullscreen(&seat)
.map(|f| f.surface.clone())
.map(KeyboardFocusTarget::Fullscreen)
else {
return;
@ -232,8 +280,8 @@ impl State {
if let Some(surface) = shell
.workspaces
.space_for_handle(&workspace)
.and_then(|w| w.get_fullscreen())
.cloned()
.and_then(|w| w.get_fullscreen(&seat))
.map(|f| f.surface.clone())
{
shell.append_focus_stack(surface, &seat)
}

View file

@ -68,7 +68,7 @@ impl Shell {
unconstrain_xdg_popup(surface, window_loc, output.geometry());
}
} else if let Some(output) = self.workspaces.spaces().find_map(|w| {
w.fullscreen.as_ref().and_then(|f| {
w.fullscreen_surfaces.iter().find_map(|f| {
(f.surface.wl_surface().as_deref() == Some(&parent)).then_some(w.output())
})
}) {

View file

@ -495,35 +495,18 @@ where
.unwrap()
.lock()
.unwrap();
let foreign_toplevel_handle = state.foreign_handle.as_ref();
let mut changed = false;
if handle_state.title != window.title() {
handle_state.title = window.title();
if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE {
instance.title(handle_state.title.clone());
}
if let Some(handle) = foreign_toplevel_handle {
handle.send_title(&handle_state.title);
}
changed = true;
}
if handle_state.app_id != window.app_id() {
handle_state.app_id = window.app_id();
if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE {
instance.app_id(handle_state.app_id.clone());
}
if let Some(handle) = foreign_toplevel_handle {
handle.send_app_id(&handle_state.app_id);
}
changed = true;
}
let new_title = (handle_state.title != window.title()).then(|| window.title());
let new_app_id = (handle_state.app_id != window.app_id()).then(|| window.app_id());
if handle_state.states.as_ref().is_none_or(|states| {
let new_states = if handle_state.states.as_ref().is_none_or(|states| {
(states.contains(&States::Maximized) != window.is_maximized())
|| (states.contains(&States::Fullscreen) != window.is_fullscreen())
|| (states.contains(&States::Activated) != window.is_activated())
|| (states.contains(&States::Minimized) != window.is_minimized())
|| (instance.version() >= zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE
&& states.contains(&States::Sticky) != window.is_sticky())
}) {
let mut states = Vec::new();
if window.is_maximized() {
@ -543,8 +526,64 @@ where
{
states.push(States::Sticky);
}
handle_state.states = Some(states.clone());
Some(states)
} else {
None
};
let geometry_changed = if !window.is_resizing() {
let geometry = window.global_geometry();
if handle_state.geometry != geometry {
handle_state.geometry = geometry;
true
} else {
false
}
} else {
false
};
let outputs_changed = state.outputs != handle_state.outputs
|| handle_state.wl_outputs.iter().any(|o| !o.is_alive());
let workspaces_changed = state.workspaces != handle_state.workspaces;
if new_title.is_none()
&& new_app_id.is_none()
&& new_states.is_none()
&& !geometry_changed
&& !outputs_changed
&& !workspaces_changed
{
return false;
}
let foreign_toplevel_handle = state.foreign_handle.as_ref();
if let Some(title) = new_title {
handle_state.title = title;
if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE {
instance.title(handle_state.title.clone());
}
if let Some(handle) = foreign_toplevel_handle {
handle.send_title(&handle_state.title);
}
changed = true;
}
if let Some(app_id) = new_app_id {
handle_state.app_id = app_id;
if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE {
instance.app_id(handle_state.app_id.clone());
}
if let Some(handle) = foreign_toplevel_handle {
handle.send_app_id(&handle_state.app_id);
}
changed = true;
}
if let Some(states) = new_states {
handle_state.states = Some(states.clone());
let states = states
.iter()
.flat_map(|state| (*state as u32).to_ne_bytes())
@ -553,17 +592,9 @@ where
changed = true;
}
let mut geometry_changed = false;
if !window.is_resizing() {
let geometry = window.global_geometry();
if handle_state.geometry != geometry {
handle_state.geometry = geometry;
changed = true;
geometry_changed = true;
}
}
if let Ok(client) = dh.get_client(instance.id()) {
if (outputs_changed || geometry_changed)
&& let Ok(client) = dh.get_client(instance.id())
{
handle_state.outputs = state.outputs.clone();
let handle_state = &mut *handle_state;
@ -599,27 +630,29 @@ where
});
}
for new_workspace in state
.workspaces
.iter()
.filter(|w| !handle_state.workspaces.contains(w))
{
for handle in workspace_state.raw_ext_workspace_handles(new_workspace, &instance.id()) {
instance.ext_workspace_enter(handle);
changed = true;
if workspaces_changed {
for new_workspace in state
.workspaces
.iter()
.filter(|w| !handle_state.workspaces.contains(w))
{
for handle in workspace_state.raw_ext_workspace_handles(new_workspace, &instance.id()) {
instance.ext_workspace_enter(handle);
changed = true;
}
}
}
for old_workspace in handle_state
.workspaces
.iter()
.filter(|w| !state.workspaces.contains(w))
{
for handle in workspace_state.raw_ext_workspace_handles(old_workspace, &instance.id()) {
instance.ext_workspace_leave(handle);
changed = true;
for old_workspace in handle_state
.workspaces
.iter()
.filter(|w| !state.workspaces.contains(w))
{
for handle in workspace_state.raw_ext_workspace_handles(old_workspace, &instance.id()) {
instance.ext_workspace_leave(handle);
changed = true;
}
}
handle_state.workspaces = state.workspaces.clone();
}
handle_state.workspaces = state.workspaces.clone();
if changed {
if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE {

View file

@ -237,11 +237,10 @@ where
window_from_handle::<<D as ToplevelInfoHandler>::Window>(toplevel).unwrap();
if let Some(toplevel_state) = window.user_data().get::<ToplevelState>() {
let mut toplevel_state = toplevel_state.lock().unwrap();
if width == 0 && height == 0 {
toplevel_state
.rectangles
.retain(|(s, _)| s.id() != surface.id());
} else {
toplevel_state
.rectangles
.retain(|(s, _)| s.id() != surface.id());
if width != 0 || height != 0 {
toplevel_state.rectangles.push((
surface.downgrade(),
Rectangle::new((x, y).into(), (width, height).into()),

View file

@ -115,6 +115,7 @@ impl State {
&self.common.display_handle,
None,
std::iter::empty::<(OsString, OsString)>(),
std::iter::empty::<OsString>(),
true,
Stdio::null(),
Stdio::null(),
@ -575,16 +576,15 @@ impl Common {
.filter(|(i, _)| *i != set.active),
)
.flat_map(|(_, workspace)| {
let focus_last =
workspace.focus_stack.get(seat).last().cloned();
workspace
.get_fullscreen()
.get_fullscreen_surfaces()
.filter(|f| {
workspace
.focus_stack
.get(seat)
.last()
.is_some_and(|t| &t == f)
focus_last.as_ref().is_some_and(|t| t == &f.surface)
})
.cloned()
.map(|f| f.surface.clone())
.collect::<Vec<_>>()
.into_iter()
.chain(workspace.mapped().flat_map(|mapped| {
let active = mapped.active_window();
@ -603,15 +603,14 @@ impl Common {
}))
.chain(
workspace
.get_fullscreen()
.get_fullscreen_surfaces()
.filter(|f| {
workspace
.focus_stack
.get(seat)
.last()
.is_none_or(|t| &t != f)
focus_last
.as_ref()
.is_none_or(|t| t != &f.surface)
})
.cloned()
.map(|f| f.surface.clone())
.collect::<Vec<_>>()
.into_iter(),
)
.chain(